diff options
1609 files changed, 38829 insertions, 13097 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 0ccdf37f0c2c..ab5d503eac62 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -1043,20 +1043,12 @@ aconfig_declarations { name: "device_policy_aconfig_flags", package: "android.app.admin.flags", container: "system", - exportable: true, srcs: [ "core/java/android/app/admin/flags/flags.aconfig", ], } java_aconfig_library { - name: "device_policy_exported_aconfig_flags_lib", - aconfig_declarations: "device_policy_aconfig_flags", - defaults: ["framework-minus-apex-aconfig-java-defaults"], - mode: "exported", -} - -java_aconfig_library { name: "device_policy_aconfig_flags_lib", aconfig_declarations: "device_policy_aconfig_flags", defaults: ["framework-minus-apex-aconfig-java-defaults"], diff --git a/CleanSpec.mk b/CleanSpec.mk index 02e8eecbb721..e6801034cd97 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -262,6 +262,7 @@ $(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/li $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/OsuLogin) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/app/OsuLogin) +$(call add-clean-step, rm -rf $(OUT_DIR)/host/linux-x86/testcases/ravenwood-runtime) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER # ****************************************************************** diff --git a/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 255ec924abee..412f2b746887 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -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. @@ -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/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index d59d430e0b78..ad54cd397413 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -491,8 +491,10 @@ public abstract class JobScheduler { * Returns a list of all currently-executing jobs. * @hide */ - @SuppressWarnings("HiddenAbstractMethod") - public abstract List<JobInfo> getStartedJobs(); + @Nullable + public List<JobInfo> getStartedJobs() { + return null; + } /** * <b>For internal system callers only!</b> @@ -501,8 +503,10 @@ public abstract class JobScheduler { * <p class="note">This is a slow operation, so it should be called sparingly. * @hide */ - @SuppressWarnings("HiddenAbstractMethod") - public abstract List<JobSnapshot> getAllJobSnapshots(); + @Nullable + public List<JobSnapshot> getAllJobSnapshots() { + return null; + } /** * @hide @@ -510,8 +514,8 @@ public abstract class JobScheduler { @RequiresPermission(allOf = { android.Manifest.permission.MANAGE_ACTIVITY_TASKS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - @SuppressWarnings("HiddenAbstractMethod") - public abstract void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer); + public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) { + } /** * @hide @@ -519,9 +523,10 @@ public abstract class JobScheduler { @RequiresPermission(allOf = { android.Manifest.permission.MANAGE_ACTIVITY_TASKS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - @SuppressWarnings("HiddenAbstractMethod") - public abstract void unregisterUserVisibleJobObserver( - @NonNull IUserVisibleJobObserver observer); + public void unregisterUserVisibleJobObserver( + @NonNull IUserVisibleJobObserver observer) { + + } /** * @hide @@ -529,7 +534,7 @@ public abstract class JobScheduler { @RequiresPermission(allOf = { android.Manifest.permission.MANAGE_ACTIVITY_TASKS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - @SuppressWarnings("HiddenAbstractMethod") - public abstract void notePendingUserRequestedAppStop(@NonNull String packageName, int userId, - @Nullable String debugReason); + public void notePendingUserRequestedAppStop(@NonNull String packageName, int userId, + @Nullable String debugReason) { + } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 384d78618c8b..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. */ @@ -4344,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(); 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/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/core/api/system-current.txt b/core/api/system-current.txt index eabe1f1c271a..96315ebccc49 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1143,16 +1143,13 @@ package android.app { field public static final int NAV_BAR_MODE_KIDS = 1; // 0x1 } - public static final class StatusBarManager.DisableInfo implements android.os.Parcelable { + public static final class StatusBarManager.DisableInfo { method public boolean areAllComponentsEnabled(); - method public int describeContents(); method public boolean isNavigateToHomeDisabled(); method public boolean isNotificationPeekingDisabled(); method public boolean isRecentsDisabled(); method public boolean isSearchDisabled(); method public boolean isStatusBarExpansionDisabled(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.StatusBarManager.DisableInfo> CREATOR; } public final class SystemServiceRegistry { @@ -10342,7 +10339,7 @@ package android.net.wifi.sharedconnectivity.app { method public int getDeviceType(); method @NonNull public android.os.Bundle getExtras(); method @NonNull public String getModelName(); - method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging(); + method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR; field public static final int DEVICE_TYPE_AUTO = 5; // 0x5 @@ -10356,7 +10353,7 @@ package android.net.wifi.sharedconnectivity.app { public static final class NetworkProviderInfo.Builder { ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build(); - method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean); + method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String); diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 62fc67b8dedf..78577e2b4090 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -1939,10 +1939,6 @@ UnflaggedApi: android.app.ActivityManager#getExternalHistoricalProcessStartReaso New API must be flagged with @FlaggedApi: method android.app.ActivityManager.getExternalHistoricalProcessStartReasons(String,int) UnflaggedApi: android.app.AppOpsManager#OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO: New API must be flagged with @FlaggedApi: field android.app.AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO -UnflaggedApi: android.app.StatusBarManager.DisableInfo#CREATOR: - New API must be flagged with @FlaggedApi: field android.app.StatusBarManager.DisableInfo.CREATOR -UnflaggedApi: android.app.StatusBarManager.DisableInfo#isBackDisabled(): - New API must be flagged with @FlaggedApi: method android.app.StatusBarManager.DisableInfo.isBackDisabled() UnflaggedApi: android.companion.virtual.VirtualDeviceManager.VirtualDevice#getPersistentDeviceId(): New API must be flagged with @FlaggedApi: method android.companion.virtual.VirtualDeviceManager.VirtualDevice.getPersistentDeviceId() UnflaggedApi: android.content.Context#THREAD_NETWORK_SERVICE: diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2437be836086..624227dc26f3 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -463,7 +463,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel(); } - public static final class StatusBarManager.DisableInfo implements android.os.Parcelable { + public static final class StatusBarManager.DisableInfo { method public boolean isRotationSuggestionDisabled(); } @@ -966,7 +966,6 @@ package android.content { ctor public AttributionSource(int, @Nullable String, @Nullable String); ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder); ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource); - ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource); ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource); method public void enforceCallingPid(); } @@ -1775,16 +1774,16 @@ package android.hardware.input { } public final class InputManager { - method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociation(@NonNull String, @NonNull String); method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByDescriptor(@NonNull String, @NonNull String); + method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByPort(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings(); method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors(); method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping(); method public int getMousePointerSpeed(); method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int); - method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociation(@NonNull String); method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByDescriptor(@NonNull String); + method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByPort(@NonNull String); field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL } diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index c4fe06170414..6cc71e5450ae 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -975,14 +975,14 @@ RequiresPermission: android.hardware.hdmi.HdmiControlManager#getHdmiCecVersion() Method 'getHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.hardware.hdmi.HdmiControlManager#setHdmiCecVersion(int): Method 'setHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission -RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociation(String, String): - Method 'addUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByDescriptor(String, String): Method 'addUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission -RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociation(String): - Method 'removeUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String): + Method 'addUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByDescriptor(String): Method 'removeUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String): + Method 'removeUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.hardware.location.GeofenceHardware#addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback): Method 'addGeofence' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.hardware.location.GeofenceHardware#getMonitoringTypes(): @@ -2035,6 +2035,10 @@ UnflaggedApi: android.content.pm.UserInfo#isPrivateProfile(): New API must be flagged with @FlaggedApi: method android.content.pm.UserInfo.isPrivateProfile() UnflaggedApi: android.credentials.CredentialProviderInfo#isPrimary(): New API must be flagged with @FlaggedApi: method android.credentials.CredentialProviderInfo.isPrimary() +UnflaggedApi: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String): + New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.addUniqueIdAssociationByPort(String,String) +UnflaggedApi: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String): + New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.removeUniqueIdAssociationByPort(String) UnflaggedApi: android.media.AudioManager#enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>): New API must be flagged with @FlaggedApi: method android.media.AudioManager.enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>) UnflaggedApi: android.media.AudioManager#exitAudioFocusFreezeForTest(): diff --git a/core/java/android/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/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/ActivityThread.java b/core/java/android/app/ActivityThread.java index caaaf519eaca..d4812dd612c3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -50,6 +50,7 @@ import android.app.RemoteServiceException.BadUserInitiatedJobNotificationExcepti import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException; import android.app.RemoteServiceException.CrashedByAdbException; import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException; +import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException; import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; @@ -2236,6 +2237,9 @@ public final class ActivityThread extends ClientTransactionHandler case ForegroundServiceDidNotStartInTimeException.TYPE_ID: throw generateForegroundServiceDidNotStartInTimeException(message, extras); + case ForegroundServiceDidNotStopInTimeException.TYPE_ID: + throw generateForegroundServiceDidNotStopInTimeException(message, extras); + case CannotPostForegroundServiceNotificationException.TYPE_ID: throw new CannotPostForegroundServiceNotificationException(message); @@ -2266,6 +2270,15 @@ public final class ActivityThread extends ClientTransactionHandler throw new ForegroundServiceDidNotStartInTimeException(message, inner); } + private ForegroundServiceDidNotStopInTimeException + generateForegroundServiceDidNotStopInTimeException(String message, Bundle extras) { + final String serviceClassName = + ForegroundServiceDidNotStopInTimeException.getServiceClassNameFromExtras(extras); + final Exception inner = (serviceClassName == null) ? null + : Service.getStartForegroundServiceStackTrace(serviceClassName); + throw new ForegroundServiceDidNotStopInTimeException(message, inner); + } + class H extends Handler { public static final int BIND_APPLICATION = 110; @UnsupportedAppUsage diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 7724c2369954..92543b1c7646 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -32,6 +32,11 @@ public class AppCompatTaskInfo implements Parcelable { public boolean topActivityEligibleForLetterboxEducation; /** + * Whether the letterbox education is enabled + */ + public boolean isLetterboxEducationEnabled; + + /** * Whether the direct top activity is in size compat mode on foreground. */ public boolean topActivityInSizeCompat; @@ -178,6 +183,7 @@ public class AppCompatTaskInfo implements Parcelable { == that.topActivityEligibleForUserAspectRatioButton && topActivityEligibleForLetterboxEducation == that.topActivityEligibleForLetterboxEducation + && isLetterboxEducationEnabled == that.isLetterboxEducationEnabled && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition && topActivityLetterboxHorizontalPosition == that.topActivityLetterboxHorizontalPosition @@ -192,6 +198,7 @@ public class AppCompatTaskInfo implements Parcelable { * Reads the AppCompatTaskInfo from a parcel. */ void readFromParcel(Parcel source) { + isLetterboxEducationEnabled = source.readBoolean(); topActivityInSizeCompat = source.readBoolean(); topActivityEligibleForLetterboxEducation = source.readBoolean(); isLetterboxDoubleTapEnabled = source.readBoolean(); @@ -212,6 +219,7 @@ public class AppCompatTaskInfo implements Parcelable { */ @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(isLetterboxEducationEnabled); dest.writeBoolean(topActivityInSizeCompat); dest.writeBoolean(topActivityEligibleForLetterboxEducation); dest.writeBoolean(isLetterboxDoubleTapEnabled); @@ -232,6 +240,7 @@ public class AppCompatTaskInfo implements Parcelable { return "AppCompatTaskInfo { topActivityInSizeCompat=" + topActivityInSizeCompat + " topActivityEligibleForLetterboxEducation= " + topActivityEligibleForLetterboxEducation + + "isLetterboxEducationEnabled= " + isLetterboxEducationEnabled + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled + " topActivityEligibleForUserAspectRatioButton= " + topActivityEligibleForUserAspectRatioButton diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2d0f6fccb8f2..54f69099e081 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -482,7 +482,8 @@ public class AppOpsManager { UID_STATE_FOREGROUND_SERVICE, UID_STATE_FOREGROUND, UID_STATE_BACKGROUND, - UID_STATE_CACHED + UID_STATE_CACHED, + UID_STATE_NONEXISTENT }) public @interface UidState {} @@ -566,6 +567,12 @@ public class AppOpsManager { public static final int MIN_PRIORITY_UID_STATE = UID_STATE_CACHED; /** + * Special uid state: The UID is not running + * @hide + */ + public static final int UID_STATE_NONEXISTENT = Integer.MAX_VALUE; + + /** * Resolves the first unrestricted state given an app op. * @param op The op to resolve. * @return The last restricted UID state. @@ -596,6 +603,7 @@ public class AppOpsManager { UID_STATE_FOREGROUND, UID_STATE_BACKGROUND, UID_STATE_CACHED + // UID_STATE_NONEXISTENT isn't a real UID state, so it is excluded }; /** @hide */ @@ -615,6 +623,8 @@ public class AppOpsManager { return "bg"; case UID_STATE_CACHED: return "cch"; + case UID_STATE_NONEXISTENT: + return "gone"; default: return "unknown"; } diff --git a/core/java/android/app/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/Notification.java b/core/java/android/app/Notification.java index 3c402cae98c1..0672064bd5ea 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,6 +6601,11 @@ public class Notification implements Parcelable * @hide */ public RemoteViews createCompactHeadsUpContentView() { + // Don't show compact heads up for FSI notifications. + if (mN.fullScreenIntent != null) { + return createHeadsUpContentView(/* increasedHeight= */ false); + } + if (mStyle != null) { final RemoteViews styleView = mStyle.makeCompactHeadsUpContentView(); if (styleView != null) { @@ -7148,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()) { @@ -7293,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; } @@ -9165,10 +9176,78 @@ 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; + } + } + } + } + + // This method fills title and text + fixTitleAndTextExtras(mBuilder.mN.extras); + final StandardTemplateParams p = mBuilder.mParams.reset() + .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) + .highlightExpander(isConversationLayout) + .fillTextsFrom(mBuilder) + .hideTime(true) + .summaryText(""); + p.headerTextSecondary(p.mText); + TemplateBindResult bindResult = new TemplateBindResult(); + + RemoteViews contentView = mBuilder.applyStandardTemplate( + mBuilder.getMessagingCompactHeadsUpLayoutResource(), p, bindResult); + if (conversationIcon != null) { + contentView.setViewVisibility(R.id.icon, 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); + } + + 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 */ @@ -10350,7 +10429,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..dc44764dc046 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -392,6 +392,14 @@ { "file_patterns": ["(/|^)AppOpsManager.java"], "name": "CtsAppOpsTestCases" + }, + { + "file_patterns": [ + "(/|^)Activity.*.java", + "(/|^)PendingIntent.java", + "(/|^)ComtextImpl.java" + ], + "name": "CtsWindowManagerBackgroundActivityTestCases" } ] } diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index efd5a455af89..ef8501f563dc 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -351,6 +351,12 @@ public class TaskInfo { } /** @hide */ + public boolean isFreeform() { + return configuration.windowConfiguration.getWindowingMode() + == WindowConfiguration.WINDOWING_MODE_FREEFORM; + } + + /** @hide */ @WindowConfiguration.ActivityType public int getActivityType() { return configuration.windowConfiguration.getActivityType(); diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 8c4667f15299..bb24fd19315e 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -50,3 +50,23 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "backstage_power" + name: "gate_fgs_timeout_anr_behavior" + description: "Gate the new behavior where an ANR is thrown once an FGS times out." + bug: "339315145" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "backstage_power" + name: "enable_fgs_timeout_crash_behavior" + description: "Enable the new behavior where the app is crashed once an FGS times out." + bug: "339526947" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 83daa4524696..3d6ec19299cb 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -217,6 +217,16 @@ flag { } flag { + name: "disallow_user_control_stopped_state_fix" + namespace: "enterprise" + description: "Ensure DPM.setUserControlDisabledPackages() clears FLAG_STOPPED for the app" + bug: "330688482" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "esim_management_ux_enabled" namespace: "enterprise" description: "Enable UX changes for esim management" @@ -309,7 +319,6 @@ flag { namespace: "enterprise" description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds" bug: "338050276" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 63ffaa0f0630..6ceae17d05fb 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -60,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()" @@ -153,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/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 b070742178e6..37f419d717c2 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -162,17 +162,6 @@ public final class AttributionSource implements Parcelable { /** @hide */ @TestApi - @FlaggedApi(Flags.FLAG_ATTRIBUTION_SOURCE_CONSTRUCTOR) - public AttributionSource(int uid, int pid, @Nullable String packageName, - @Nullable String attributionTag, @NonNull IBinder token, - @Nullable String[] renouncedPermissions, - @Nullable AttributionSource next) { - this(uid, pid, packageName, attributionTag, token, renouncedPermissions, - Context.DEVICE_ID_DEFAULT, next); - } - - /** @hide */ - @TestApi @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public AttributionSource(int uid, int pid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token, @@ -473,6 +462,20 @@ public final class AttributionSource implements Parcelable { } /** + * @return The next package's device Id from its context. + * This device ID is used for permissions checking during attribution source validation. + * + * @hide + */ + public int getNextDeviceId() { + if (mAttributionSourceState.next != null + && mAttributionSourceState.next.length > 0) { + return mAttributionSourceState.next[0].deviceId; + } + return Context.DEVICE_ID_DEFAULT; + } + + /** * Checks whether this attribution source can be trusted. That is whether * the app it refers to created it and provided to the attribution chain. * diff --git a/core/java/android/content/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/TEST_MAPPING b/core/java/android/content/TEST_MAPPING index a2cfbf5aa5bd..f08395ae5e40 100644 --- a/core/java/android/content/TEST_MAPPING +++ b/core/java/android/content/TEST_MAPPING @@ -63,5 +63,11 @@ "name": "CtsContentTestCasesRavenwood", "host": true } + ], + "postsubmit": [ + { + "name": "CtsWindowManagerBackgroundActivityTestCases", + "file_patterns": ["(/|^)IntentSender.java"] + } ] } 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 cee8d96fb0d7..061e7f711ba5 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -254,6 +254,14 @@ flag { } flag { + name: "wait_application_killed" + namespace: "package_manager_service" + description: "Feature flag to control whether to wait until the application is killed when clear application data" + bug: "31009094" + is_fixed_read_only: true +} + +flag { name: "component_state_changed_metrics" namespace: "package_manager_service" description: "Feature flag to log the metrics when the component state is changed." diff --git a/core/java/android/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/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java index 3d8ccaaa45cc..c70eff42c732 100644 --- a/core/java/android/credentials/GetCandidateCredentialsResponse.java +++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java @@ -18,6 +18,8 @@ package android.credentials; import android.annotation.Hide; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Intent; import android.credentials.selection.GetCredentialProviderData; import android.os.Parcel; @@ -39,6 +41,9 @@ public final class GetCandidateCredentialsResponse implements Parcelable { @NonNull private final List<GetCredentialProviderData> mCandidateProviderDataList; + @Nullable + private final ComponentName mPrimaryProviderComponentName; + @NonNull private final Intent mIntent; @@ -48,13 +53,15 @@ public final class GetCandidateCredentialsResponse implements Parcelable { @Hide public GetCandidateCredentialsResponse( @NonNull List<GetCredentialProviderData> candidateProviderDataList, - @NonNull Intent intent + @NonNull Intent intent, + @Nullable ComponentName primaryProviderComponentName ) { Preconditions.checkCollectionNotEmpty( candidateProviderDataList, /*valueName=*/ "candidateProviderDataList"); mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList); mIntent = intent; + mPrimaryProviderComponentName = primaryProviderComponentName; } /** @@ -67,6 +74,16 @@ public final class GetCandidateCredentialsResponse implements Parcelable { } /** + * Returns the primary provider component name. + * + * @hide + */ + @Nullable + public ComponentName getPrimaryProviderComponentName() { + return mPrimaryProviderComponentName; + } + + /** * Returns candidate provider data list. * * @hide @@ -83,12 +100,15 @@ public final class GetCandidateCredentialsResponse implements Parcelable { AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList); mIntent = in.readTypedObject(Intent.CREATOR); + + mPrimaryProviderComponentName = in.readTypedObject(ComponentName.CREATOR); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeTypedList(mCandidateProviderDataList); dest.writeTypedObject(mIntent, flags); + dest.writeTypedObject(mPrimaryProviderComponentName, flags); } @Override diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index a0e40f6390ee..61f1ee11e059 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -34,6 +34,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.TestApi; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; @@ -603,7 +604,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan mPromptInfo.setIsForLegacyFingerprintManager(sensorId); return this; } - // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java) /** * Set if emergency call button should show, for example if biometrics are @@ -613,12 +613,33 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ @NonNull + @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) public Builder setShowEmergencyCallButton(boolean showEmergencyCallButton) { mPromptInfo.setShowEmergencyCallButton(showEmergencyCallButton); return this; } /** + * Set caller's component name for getting logo icon/description. This should only be used + * by ConfirmDeviceCredentialActivity, see b/337082634 for more context. + * + * @param componentNameForConfirmDeviceCredentialActivity set the component name for + * ConfirmDeviceCredentialActivity. + * @return This builder. + * @hide + */ + @NonNull + @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) + public Builder setComponentNameForConfirmDeviceCredentialActivity( + ComponentName componentNameForConfirmDeviceCredentialActivity) { + mPromptInfo.setComponentNameForConfirmDeviceCredentialActivity( + componentNameForConfirmDeviceCredentialActivity); + return this; + } + + // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java) + + /** * Creates a {@link BiometricPrompt}. * * @return An instance of {@link BiometricPrompt}. diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 18b75c9b8f8f..bb07b9b881f8 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -19,6 +19,7 @@ package android.hardware.biometrics; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ComponentName; import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; @@ -56,6 +57,7 @@ public class PromptInfo implements Parcelable { private boolean mIsForLegacyFingerprintManager = false; private boolean mShowEmergencyCallButton = false; private boolean mUseParentProfileForDeviceCredential = false; + private ComponentName mComponentNameForConfirmDeviceCredentialActivity = null; public PromptInfo() { @@ -87,6 +89,8 @@ public class PromptInfo implements Parcelable { mIsForLegacyFingerprintManager = in.readBoolean(); mShowEmergencyCallButton = in.readBoolean(); mUseParentProfileForDeviceCredential = in.readBoolean(); + mComponentNameForConfirmDeviceCredentialActivity = in.readParcelable( + ComponentName.class.getClassLoader(), ComponentName.class); } public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() { @@ -132,10 +136,11 @@ public class PromptInfo implements Parcelable { dest.writeBoolean(mIsForLegacyFingerprintManager); dest.writeBoolean(mShowEmergencyCallButton); dest.writeBoolean(mUseParentProfileForDeviceCredential); + dest.writeParcelable(mComponentNameForConfirmDeviceCredentialActivity, 0); } // LINT.IfChange - public boolean containsTestConfigurations() { + public boolean requiresTestOrInternalPermission() { if (mIsForLegacyFingerprintManager && mAllowedSensorIds.size() == 1 && !mAllowBackgroundAuthentication) { @@ -148,11 +153,15 @@ public class PromptInfo implements Parcelable { return true; } else if (mIgnoreEnrollmentState) { return true; + } else if (mShowEmergencyCallButton) { + return true; + } else if (mComponentNameForConfirmDeviceCredentialActivity != null) { + return true; } return false; } - public boolean containsPrivateApiConfigurations() { + public boolean requiresInternalPermission() { if (mDisallowBiometricsIfPolicyExists) { return true; } else if (mUseDefaultTitle) { @@ -177,7 +186,7 @@ public class PromptInfo implements Parcelable { * Currently, logo res, logo bitmap, logo description, PromptContentViewWithMoreOptions needs * this permission. */ - public boolean containsAdvancedApiConfigurations() { + public boolean requiresAdvancedPermission() { if (mLogoRes != -1) { return true; } else if (mLogoBitmap != null) { @@ -305,6 +314,12 @@ public class PromptInfo implements Parcelable { mShowEmergencyCallButton = showEmergencyCallButton; } + public void setComponentNameForConfirmDeviceCredentialActivity( + ComponentName componentNameForConfirmDeviceCredentialActivity) { + mComponentNameForConfirmDeviceCredentialActivity = + componentNameForConfirmDeviceCredentialActivity; + } + public void setUseParentProfileForDeviceCredential( boolean useParentProfileForDeviceCredential) { mUseParentProfileForDeviceCredential = useParentProfileForDeviceCredential; @@ -417,6 +432,10 @@ public class PromptInfo implements Parcelable { return mShowEmergencyCallButton; } + public ComponentName getComponentNameForConfirmDeviceCredentialActivity() { + return mComponentNameForConfirmDeviceCredentialActivity; + } + private void checkOnlyOneLogoSet() { if (mLogoRes != -1 && mLogoBitmap != null) { throw new IllegalStateException( diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index a01961206319..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; @@ -838,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>(); @@ -974,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; @@ -990,7 +990,7 @@ public final class CameraManager { callback, executor, characteristics, - physicalIdsToChars, + this, mContext.getApplicationInfo().targetSdkVersion, mContext, cameraDeviceSetup); ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); @@ -1995,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. @@ -2116,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; @@ -2274,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. */ 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..e2b409fe35f1 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -31,6 +31,7 @@ import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraExtensionCharacteristics; +import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CameraOfflineSession; import android.hardware.camera2.CaptureFailure; @@ -146,7 +147,8 @@ public class CameraDeviceImpl extends CameraDevice private final String mCameraId; private final CameraCharacteristics mCharacteristics; - private final Map<String, CameraCharacteristics> mPhysicalIdsToChars; + private Map<String, CameraCharacteristics> mPhysicalIdsToChars; + private final CameraManager mCameraManager; private final int mTotalPartialCount; private final Context mContext; @@ -341,11 +343,12 @@ public class CameraDeviceImpl extends CameraDevice public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor, CameraCharacteristics characteristics, - Map<String, CameraCharacteristics> physicalIdsToChars, + @NonNull CameraManager manager, int appTargetSdkVersion, Context ctx, @Nullable CameraDevice.CameraDeviceSetup cameraDeviceSetup) { - if (cameraId == null || callback == null || executor == null || characteristics == null) { + if (cameraId == null || callback == null || executor == null || characteristics == null + || manager == null) { throw new IllegalArgumentException("Null argument given"); } mCameraId = cameraId; @@ -357,7 +360,7 @@ public class CameraDeviceImpl extends CameraDevice mDeviceExecutor = executor; } mCharacteristics = characteristics; - mPhysicalIdsToChars = physicalIdsToChars; + mCameraManager = manager; mAppTargetSdkVersion = appTargetSdkVersion; mContext = ctx; mCameraDeviceSetup = cameraDeviceSetup; @@ -379,6 +382,18 @@ public class CameraDeviceImpl extends CameraDevice } } + private Map<String, CameraCharacteristics> getPhysicalIdToChars() { + if (mPhysicalIdsToChars == null) { + try { + mPhysicalIdsToChars = mCameraManager.getPhysicalIdToCharsMap(mCharacteristics); + } catch (CameraAccessException e) { + Log.e(TAG, "Unable to query the physical characteristics map!"); + } + } + + return mPhysicalIdsToChars; + } + public CameraDeviceCallbacks getCallbacks() { return mCallbacks; } @@ -1598,7 +1613,7 @@ public class CameraDeviceImpl extends CameraDevice return true; } - for (Map.Entry<String, CameraCharacteristics> entry : mPhysicalIdsToChars.entrySet()) { + for (Map.Entry<String, CameraCharacteristics> entry : getPhysicalIdToChars().entrySet()) { configMap = entry.getValue().get(ck); if (configMap != null && @@ -2621,7 +2636,7 @@ public class CameraDeviceImpl extends CameraDevice public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration) throws CameraAccessException { HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>( - mPhysicalIdsToChars); + getPhysicalIdToChars()); characteristicsMap.put(mCameraId, mCharacteristics); boolean initializationFailed = true; IBinder token = new Binder(TAG + " : " + mNextSessionId++); diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 3ae319999e35..a4ae398782b4 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -200,10 +200,18 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { supportedCaptureSizes.put(format, supportedSizes); } } + + int captureFormat = ImageFormat.UNKNOWN; Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface( config.getOutputConfigurations(), supportedCaptureSizes); if (burstCaptureSurface != null) { suitableSurfaceCount++; + + if (Flags.analytics24q3()) { + CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo = + CameraExtensionUtils.querySurface(burstCaptureSurface); + captureFormat = burstCaptureSurfaceInfo.mFormat; + } } if (suitableSurfaceCount != config.getOutputConfigurations().size()) { @@ -258,6 +266,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { extensionChars.getAvailableCaptureResultKeys(config.getExtension()), config.getExtension()); + if (Flags.analytics24q3()) { + session.mStatsAggregator.setCaptureFormat(captureFormat); + } session.mStatsAggregator.setClientName(ctx.getOpPackageName()); session.mStatsAggregator.setExtensionType(config.getExtension()); diff --git a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java index 3050a51d7955..c75e4187b7bb 100644 --- a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java +++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java @@ -70,6 +70,23 @@ public class ExtensionSessionStatsAggregator { } /** + * Set the capture format. + * + * @param format Format of requested capture. + */ + public void setCaptureFormat(int format) { + synchronized (mLock) { + if (mIsDone) { + return; + } + if (DEBUG) { + Log.v(TAG, "Setting capture format: " + format); + } + mStats.captureFormat = format; + } + } + + /** * Set extension type. * * @param extensionType Type of extension. Must match one of @@ -116,7 +133,8 @@ public class ExtensionSessionStatsAggregator { + " cameraId: '" + stats.cameraId + "'\n" + " clientName: '" + stats.clientName + "'\n" + " type: '" + stats.type + "'\n" - + " isAdvanced: '" + stats.isAdvanced + "'\n"; + + " isAdvanced: '" + stats.isAdvanced + "'\n" + + " captureFormat: '" + stats.captureFormat + "'\n"; } /** diff --git a/core/java/android/hardware/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..45b316aa5498 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -148,8 +148,6 @@ interface IInputManager { IInputDeviceBatteryState getBatteryState(int deviceId); - void setPointerIconType(int typeId); - void setCustomPointerIcon(in PointerIcon icon); boolean setPointerIcon(in PointerIcon icon, int displayId, int deviceId, int pointerId, in IBinder inputToken); @@ -173,9 +171,9 @@ interface IInputManager { void removeUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor); // Add a runtime association between the input device and display, using device's port. - void addUniqueIdAssociation(in String inputPort, in String displayUniqueId); + void addUniqueIdAssociationByPort(in String inputPort, in String displayUniqueId); // Remove the runtime association between the input device and display, using device's port. - void removeUniqueIdAssociation(in String inputPort); + void removeUniqueIdAssociationByPort(in String inputPort); InputSensorInfo[] getSensorList(int deviceId); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index dd4ea31af17d..7527aa7197e9 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -992,21 +992,14 @@ public final class InputManager { } /** - * Changes the mouse pointer's icon shape into the specified id. - * - * @param iconId The id of the pointer graphic, as a value between - * {@link PointerIcon#TYPE_ARROW} and {@link PointerIcon#TYPE_HANDWRITING}. + * This method exists for backwards-compatibility, and is a no-op. * + * @deprecated * @hide */ @UnsupportedAppUsage public void setPointerIconType(int iconId) { - mGlobal.setPointerIconType(iconId); - } - - /** @hide */ - public void setCustomPointerIcon(PointerIcon icon) { - mGlobal.setCustomPointerIcon(icon); + Log.e(TAG, "setPointerIcon: Unsupported app usage!"); } /** @hide */ @@ -1101,10 +1094,9 @@ public final class InputManager { */ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY) @TestApi - // TODO(b/324075859): Rename to addUniqueIdAssociationByPort - public void addUniqueIdAssociation(@NonNull String inputPort, + public void addUniqueIdAssociationByPort(@NonNull String inputPort, @NonNull String displayUniqueId) { - mGlobal.addUniqueIdAssociation(inputPort, displayUniqueId); + mGlobal.addUniqueIdAssociationByPort(inputPort, displayUniqueId); } /** @@ -1117,9 +1109,8 @@ public final class InputManager { */ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY) @TestApi - // TODO(b/324075859): Rename to removeUniqueIdAssociationByPort - public void removeUniqueIdAssociation(@NonNull String inputPort) { - mGlobal.removeUniqueIdAssociation(inputPort); + public void removeUniqueIdAssociationByPort(@NonNull String inputPort) { + mGlobal.removeUniqueIdAssociationByPort(inputPort); } /** diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index a9c97b1a0e51..fcd5a3ed06c0 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -1411,28 +1411,6 @@ public final class InputManagerGlobal { } /** - * @see InputManager#setPointerIconType(int) - */ - public void setPointerIconType(int iconId) { - try { - mIm.setPointerIconType(iconId); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /** - * @see InputManager#setCustomPointerIcon(PointerIcon) - */ - public void setCustomPointerIcon(PointerIcon icon) { - try { - mIm.setCustomPointerIcon(icon); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /** * @see InputManager#setPointerIcon(PointerIcon, int, int, int, IBinder) */ public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId, @@ -1467,22 +1445,23 @@ public final class InputManagerGlobal { } /** - * @see InputManager#addUniqueIdAssociation(String, String) + * @see InputManager#addUniqueIdAssociationByPort(String, String) */ - public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) { + public void addUniqueIdAssociationByPort(@NonNull String inputPort, + @NonNull String displayUniqueId) { try { - mIm.addUniqueIdAssociation(inputPort, displayUniqueId); + mIm.addUniqueIdAssociationByPort(inputPort, displayUniqueId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * @see InputManager#removeUniqueIdAssociation(String) + * @see InputManager#removeUniqueIdAssociationByPort(String) */ - public void removeUniqueIdAssociation(@NonNull String inputPort) { + public void removeUniqueIdAssociationByPort(@NonNull String inputPort) { try { - mIm.removeUniqueIdAssociation(inputPort); + mIm.removeUniqueIdAssociationByPort(inputPort); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/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/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 9a63394d3ca1..49ab15a40a8e 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -429,10 +429,9 @@ public class BaseBundle { "Lazy values ref count below 0"); // No more lazy values in mMap, so we can recycle the parcel early rather than // waiting for the next GC run - if (mLazyValues == 0) { - Preconditions.checkState(mWeakParcelledData.get() != null, - "Parcel recycled earlier than expected"); - recycleParcel(mWeakParcelledData.get()); + Parcel parcel = mWeakParcelledData.get(); + if (mLazyValues == 0 && parcel != null) { + recycleParcel(parcel); mWeakParcelledData = null; } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c6a92033b0d4..2f0d63401e33 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1930,12 +1930,10 @@ public class UserManager { public static final String DISALLOW_THREAD_NETWORK = "no_thread_network"; /** - * This user restriction specifies if the user is able to add SIMs to the device. + * This user restriction specifies if the user is able to add embedded SIMs to the device. * * <p> - * This restriction blocks the download of embedded SIMs, and disables any physical SIMs. - * If any embedded SIMs are already on the device, then they are removed. This restriction - * does not affect SIMs provisioned to the device by device owners or profile owners. + * This restriction blocks the download of embedded SIMs. * * <p> * This restriction can only be set by a device owner or a profile owner of an @@ -1951,6 +1949,7 @@ public class UserManager { * * <p>Key for user restrictions. * <p>Type: Boolean + * * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() diff --git a/core/java/android/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/flags.aconfig b/core/java/android/permission/flags.aconfig index b58830861c5e..2ca58d16eaae 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -44,14 +44,6 @@ flag { } flag { - name: "attribution_source_constructor" - is_exported: true - namespace: "permissions" - description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)" - bug: "304478648" -} - -flag { name: "enhanced_confirmation_mode_apis_enabled" is_exported: true is_fixed_read_only: true @@ -174,6 +166,16 @@ flag { } flag { + name: "finish_running_ops_for_killed_packages" + namespace: "permissions" + description: "Finish all appops for a dead app process" + bug: "234630570" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "runtime_permission_appops_mapping_enabled" is_fixed_read_only: true namespace: "permissions" diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e6ddf3556490..4f5b67c34845 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 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..38ab590b3c46 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; @@ -571,15 +571,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 +1728,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/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java index 29a6db6a12a0..8237b20260ea 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java @@ -105,6 +105,21 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService"; + // TODO(339594686): make API + /** + * @hide + */ + public static final String REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY = + "register_model_update_callback"; + /** + * @hide + */ + public static final String MODEL_LOADED_BUNDLE_KEY = "model_loaded"; + /** + * @hide + */ + public static final String MODEL_UNLOADED_BUNDLE_KEY = "model_unloaded"; + private IRemoteStorageService mRemoteStorageService; /** diff --git a/core/java/android/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/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/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 9503f4925e14..8c14de6156a3 100644 --- a/core/java/android/view/ImeBackAnimationController.java +++ b/core/java/android/view/ImeBackAnimationController.java @@ -210,18 +210,9 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { mInsetsController.setPredictiveBackImeHideAnimInProgress(true); notifyHideIme(); } - if (mStartRootScrollY != 0) { - // RootView is panned, ensure that it is scrolled back to the intended scroll position - if (triggerBack) { - // requesting ime as invisible - mInsetsController.setRequestedVisibleTypes(0, ime()); - // changes the animation state and notifies RootView of changed insets, which - // causes it to reset its scrollY to 0f (animated) - mInsetsController.onAnimationStateChanged(ime(), /*running*/ true); - } else { - // This causes RootView to update its scroll back to the panned position - mInsetsController.getHost().notifyInsetsChanged(); - } + if (mStartRootScrollY != 0 && !triggerBack) { + // This causes RootView to update its scroll back to the panned position + mInsetsController.getHost().notifyInsetsChanged(); } } @@ -237,6 +228,12 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { // the IME away mInsetsController.getHost().getInputMethodManager() .notifyImeHidden(mInsetsController.getHost().getWindowToken(), statsToken); + + // requesting IME as invisible during post-commit + mInsetsController.setRequestedVisibleTypes(0, ime()); + // Changes the animation state. This also notifies RootView of changed insets, which causes + // it to reset its scrollY to 0f (animated) if it was panned + mInsetsController.onAnimationStateChanged(ime(), /*running*/ true); } private void reset() { 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 60ad926f2be1..1cb276568244 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -30695,21 +30695,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setPointerIcon(PointerIcon pointerIcon) { mMousePointerIcon = pointerIcon; - if (com.android.input.flags.Flags.enablePointerChoreographer()) { - final ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl == null) { - return; - } - viewRootImpl.refreshPointerIcon(); - } else { - if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) { - return; - } - try { - mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow); - } catch (RemoteException e) { - } + final ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl == null) { + return; } + viewRootImpl.refreshPointerIcon(); } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fdf3cb119a42..1df851a29b07 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -74,7 +74,6 @@ import static android.view.ViewRootImplProto.WIDTH; import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES; import static android.view.ViewRootImplProto.WIN_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -96,6 +95,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_V import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -122,7 +122,6 @@ import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; -import static com.android.input.flags.Flags.enablePointerChoreographer; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.window.flags.Flags.activityWindowInfoFlag; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; @@ -195,6 +194,7 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; @@ -268,11 +268,15 @@ import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; import com.android.internal.policy.PhoneFallbackEventHandler; +import com.android.internal.util.FastPrintWriter; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; import com.android.internal.view.SurfaceCallbackHelper; import com.android.modules.expresslog.Counter; +import libcore.io.IoUtils; + +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; @@ -7937,46 +7941,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; } @@ -8593,48 +8571,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) { @@ -10580,16 +10565,6 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } - public void updatePointerIcon(float x, float y) { - final int what = MSG_UPDATE_POINTER_ICON; - mHandler.removeMessages(what); - final long now = SystemClock.uptimeMillis(); - final MotionEvent event = MotionEvent.obtain( - 0, now, MotionEvent.ACTION_HOVER_MOVE, x, y, 0); - Message msg = mHandler.obtainMessage(what, event); - mHandler.sendMessage(msg); - } - public void dispatchCheckFocus() { if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) { // This will result in a call to checkFocus() below. @@ -11453,14 +11428,6 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void updatePointerIcon(float x, float y) { - final ViewRootImpl viewAncestor = mViewAncestor.get(); - if (viewAncestor != null) { - viewAncestor.updatePointerIcon(x, y); - } - } - - @Override public void dispatchWindowShown() { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -11483,6 +11450,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 { diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index e6367ff7b4dc..d7d764b6ce75 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -603,10 +603,6 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public void updatePointerIcon(android.view.IWindow window) { - } - - @Override public void updateTapExcludeRegion(android.view.IWindow window, android.graphics.Region region) { } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index bfe4e6f3cc9b..9cc4191d0c80 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1978,6 +1978,13 @@ public final class AutofillManager { if (Objects.equals(mLastAutofilledData.get(id), value)) { view.setAutofilled(true, hideHighlight); + try { + mService.setViewAutofilled(mSessionId, id, mContext.getUserId()); + } catch (RemoteException e) { + // The failure could be a consequence of something going wrong on the + // server side. Do nothing here since it's just logging, but it's + // possible follow-up actions may fail. + } } else { view.setAutofilled(false, false); mLastAutofilledData.remove(id); @@ -2978,6 +2985,13 @@ public final class AutofillManager { mLastAutofilledData.put(view.getAutofillId(), targetValue); } view.setAutofilled(true, hideHighlight); + try { + mService.setViewAutofilled(mSessionId, view.getAutofillId(), mContext.getUserId()); + } catch (RemoteException e) { + // The failure could be a consequence of something going wrong on the server side. + // Do nothing here since it's just logging, but it's possible follow-up actions may + // fail. + } } } diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 1a9322e6f2b8..2039b4d5c1bd 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -49,6 +49,7 @@ oneway interface IAutoFillManager { void updateSession(int sessionId, in AutofillId id, in Rect bounds, in AutofillValue value, int action, int flags, int userId); void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId); + void setViewAutofilled(int sessionId, in AutofillId id, int userId); void finishSession(int sessionId, int userId, int commitReason); void cancelSession(int sessionId, int userId); void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId); diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index c5114b9550db..fb3e0831fdc9 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -720,6 +720,20 @@ public class EditorInfo implements InputType, Parcelable { private boolean mIsStylusHandwritingEnabled; + + /** + * AndroidX Core library 1.13.0 introduced EditorInfoCompat#setStylusHandwritingEnabled and + * EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the EditorInfo + * extras bundle. These methods do not set or check the Android V property since the Android V + * SDK was not yet available. In order for EditorInfoCompat#isStylusHandwritingEnabled to return + * the correct value for EditorInfo created by Android V TextView, the extras bundle value + * should be set. This is the extras bundle key. + * + * @hide + */ + public static final String STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY = + "androidx.core.view.inputmethod.EditorInfoCompat.STYLUS_HANDWRITING_ENABLED"; + /** * Set {@code true} if the {@code Editor} has * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled. diff --git a/core/java/android/view/inputmethod/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 a073873cb2a0..cf128fbaf50f 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2530,18 +2530,6 @@ public final class InputMethodManager { view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0); } - private void startStylusHandwritingInternalAsync( - @NonNull View view, @Nullable String delegatorPackageName, - @HandwritingDelegateFlags int handwritingDelegateFlags, - @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { - Objects.requireNonNull(view); - Objects.requireNonNull(executor); - Objects.requireNonNull(callback); - - startStylusHandwritingInternal( - view, delegatorPackageName, handwritingDelegateFlags, executor, callback); - } - private void sendFailureCallback(@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { if (executor == null || callback == null) { @@ -2891,7 +2879,7 @@ public final class InputMethodManager { if (Flags.homeScreenHandwritingDelegator()) { flags = delegateView.getHandwritingDelegateFlags(); } - startStylusHandwritingInternalAsync( + acceptStylusHandwritingDelegation( delegateView, delegatorPackageName, flags, executor, callback); } @@ -2926,6 +2914,9 @@ public final class InputMethodManager { @HandwritingDelegateFlags int flags, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { Objects.requireNonNull(delegatorPackageName); + Objects.requireNonNull(delegateView); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); startStylusHandwritingInternal( delegateView, delegatorPackageName, flags, executor, callback); diff --git a/core/java/android/view/inputmethod/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/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/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index 41b6d31661ce..a2e3d40e5c6b 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,9 @@ import android.os.SystemClock; import android.view.Surface; import android.view.WindowInsetsController; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Represents a task snapshot. * @hide @@ -68,6 +72,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 +315,28 @@ 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 (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/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/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/ImeTracingPerfettoImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java index 91b80ddaa836..24cd1c9cd7de 100644 --- a/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java +++ b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java @@ -52,8 +52,14 @@ final class ImeTracingPerfettoImpl extends ImeTracing { ImeTracingPerfettoImpl() { 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) + .setNoFlush(true) + .setWillNotifyOnStop(false) + .build(); + mDataSource.register(params); } 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/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/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..1340156321b2 --- /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; + } + + public 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/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/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_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_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_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..acef609448ad 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -329,7 +329,8 @@ static void init_keyboard(JNIEnv* env, const vector<string>& keyboardPaths) { InputDeviceInfo info = InputDeviceInfo(); info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(), - "keyboard " + std::to_string(keyboardId), true, false, 0); + "keyboard " + std::to_string(keyboardId), true, false, + ui::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 70d923b8a9bb..8541704ecfb9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -299,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" /> 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/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..3b288d7038e1 --- /dev/null +++ b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/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" + /> + <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> +</FrameLayout> diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml index 2c35c9b888cf..78299ab0ea99 100644 --- a/core/res/res/layout/side_fps_toast.xml +++ b/core/res/res/layout/side_fps_toast.xml @@ -25,6 +25,7 @@ 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:textColor="@color/side_fps_text_color" android:paddingLeft="20dp"/> @@ -36,6 +37,7 @@ 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" style="?android:attr/buttonBarNegativeButtonStyle" android:textColor="@color/side_fps_button_color" diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index b65fc5f4e890..2a3c691dcd04 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Maak Boodskappe oop"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe dit werk"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Hangend …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index cef77b026c83..08a290c454f3 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"መልዕክቶች ይክፈቱ"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"እንዴት እንደሚሠራ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"በመጠባበቅ ላይ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 2a5822a5fa1c..20d491fd22d8 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -165,8 +165,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"تم الاتصال بشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\" المشفَّرة"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"أصبح الاتصال باستخدام شريحة SIM لشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\" أكثر أمانًا الآن"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"تم الاتصال بشبكة غير مشفَّرة"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"المكالمات والرسائل والبيانات هي أكثر عرضة للاختراق في الوقت الحالي أثناء استخدام شريحة SIM لشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\""</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"المكالمات والرسائل والبيانات هي أكثر عرضة للاختراق في الوقت الحالي أثناء استخدام شريحة SIM لشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\".\n\nستتلقّى إشعارًا آخر عندما يتم تشفير اتصالك مرة أخرى."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"تكون المكالمات والرسائل والبيانات في الوقت الحالي أكثر عرضة للاختراق أثناء استخدام شريحة SIM من شبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\""</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"تكون المكالمات والرسائل والبيانات في الوقت الحالي أكثر عرضة للاختراق أثناء استخدام شريحة SIM من شبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\".\n\nستتلقّى إشعارًا آخر عندما يتم تشفير اتصالك مرة أخرى."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"إعدادات أمان شبكة الجوّال"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"مزيد من المعلومات"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"حسنًا"</string> @@ -849,7 +849,7 @@ <string name="policylab_forceLock" msgid="7360335502968476434">"قفل الشاشة"</string> <string name="policydesc_forceLock" msgid="1008844760853899693">"التحكّم في طريقة ووقت قفل الشاشة"</string> <string name="policylab_wipeData" msgid="1359485247727537311">"محو جميع البيانات"</string> - <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"يمكنك محو بيانات الجهاز اللوحي بدون تحذير، وذلك عبر إجراء إعادة الضبط على الإعدادات الأصلية."</string> + <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"محو بيانات الجهاز اللوحي بدون تحذير، وذلك عبر إعادة الضبط على الإعدادات الأصلية"</string> <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"يمكنك محو بيانات جهاز Android TV بدون تحذير عن طريق تنفيذ إعادة الضبط على الإعدادات الأصلية."</string> <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"يمكنك محو بيانات \"نظام الترفيه والمعلومات\" بدون تحذير، وذلك من خلال إعادة الضبط على الإعدادات الأصلية."</string> <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"محو بيانات الهاتف بدون تحذير، وذلك من خلال إعادة ضبط البيانات على الإعدادات الأصلية"</string> @@ -2417,4 +2417,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"فتح تطبيق \"الرسائل\""</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"طريقة العمل"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"بانتظار الإزالة من الأرشيف…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index 6e0da5454350..30ced90bd5e4 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খোলক"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ই কেনেকৈ কাম কৰে"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"বিবেচনাধীন হৈ আছে..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index a3d4423ea772..49073f11f897 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajı açın"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Haqqında"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Gözləmədə..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index fff999ac57c4..ca66ef4a018f 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -161,7 +161,7 @@ <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Mreža u blizini je u <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> evidentirala jedinstveni ID vašeg uređaja (IMSI ili IMEI) dok ste koristili <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nTo znači da je evidentirala vašu lokaciju, aktivnost i identitet. To je uobičajena praksa, ali može da bude problem ljudima koji su zabrinuti za privatnost."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Povezani ste na šifrovanu mrežu <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Veza <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-a je sada bezbednija"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Povezani ste na šifrovanu mrežu"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Povezani ste na nešifrovanu mrežu"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Pozivi, poruke i podaci su trenutno ranjiviji dok koristite <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Pozivi, poruke i podaci su trenutno ranjiviji dok koristite <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nKada veza ponovo bude šifrovana, poslaćemo vam drugo obaveštenje."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Podešavanja bezbednosti na mobilnoj mreži"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Princip rada"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 19ca1c4da054..4cd150a6ebf6 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -162,7 +162,7 @@ <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"У <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> у сетцы паблізу быў запісаны ўнікальны ідэнтыфікатар вашай прылады (IMSI або IMEI) пры выкарыстанні SIM-карты <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nГэта азначае, што даныя пра ваша месцазнаходжанне, дзеянні або асобу былі зарэгістраваны. Гэта звычайная практыка, але можа быць праблемай для людзей, якія турбуюцца аб прыватнасці."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Падключана да зашыфраванай сеткі <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Цяпер падключэнне да SIM-карты <xliff:g id="NETWORK_NAME">%1$s</xliff:g> стала больш бяспечным"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Падключана да зашыфраванай сеткі"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Падключана да незашыфраванай сеткі"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Зараз выклікі, паведамленні і даныя менш абаронены пры выкарыстанні SIM-карты <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Зараз выклікі, паведамленні і даныя менш абаронены пры выкарыстанні SIM-карты <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nКалі падключэнне будзе зноў зашыфравана, вы атрымаеце апавяшчэнне."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Налады сеткавай бяспекі"</string> @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Адкрыць Паведамленні"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як гэта працуе"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"У чаканні..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 2f1ecdbbbb91..eb2e9207367f 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -161,7 +161,7 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Установена е връзка с шифрованата мрежа <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Връзката със SIM картата от <xliff:g id="NETWORK_NAME">%1$s</xliff:g> вече е по-сигурна"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Установена е връзка с нешифрована мрежа"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Понастоящем обажданията, съобщенията и данните са по-уязвими, докато използвате SIM картата си от <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Обажданията, съобщенията и данните са по-уязвими, докато използвате SIM картата си от <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Понастоящем обажданията, съобщенията и данните са по-уязвими, докато използвате SIM картата си от <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nСлед като връзката ви бъде шифрована отново, ще получите друго известие."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Настройки за сигурност на мобилната мрежа"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Научете повече"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отваряне на Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Начин на работа"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Изчаква..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index f1ebfb02faa5..6618127ada43 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -157,7 +157,7 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"এনক্রিপশন, এনক্রিপটেড নয় এমন নেটওয়ার্কের জন্য বিজ্ঞপ্তি"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ডিভাইস আইডি অ্যাক্সেস করা হয়েছে"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"আপনার <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> সিম কার্ড ব্যবহার করে <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-এ, আশেপাশের নেটওয়ার্ক আপনার ডিভাইসের অনন্য আইডি (IMSI অথবা IMEI) রেকর্ড করেছে"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"আপনার <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> সিম কার্ড ব্যবহার করে <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-এ, আশেপাশের নেটওয়ার্ক আপনার ডিভাইসের অনন্য আইডি (IMSI অথবা IMEI) রেকর্ড করেছে।\n\nএটির মানে হল আপনার লোকেশন, অ্যাক্টিভিটি বা পরিচিতি লগ-ইন করা হয়েছে। এটি সাধারণ পদ্ধতি কিন্তু সেইসব লোকজনের জন্য সমস্যা হতে পারে যারা নিজেদের গোপনীয়তা নিয়ে উদ্বেগে থাকেন।"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"আপনার <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> সিম কার্ড ব্যবহার করে <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-এ, আশেপাশের নেটওয়ার্ক আপনার ডিভাইসের অনন্য আইডি (IMSI অথবা IMEI) রেকর্ড করেছে।\n\nএটির মানে হল আপনার লোকেশন, অ্যাক্টিভিটি বা পরিচিতি লগ করা হয়েছে। এটি সাধারণ পদ্ধতি কিন্তু সেইসব লোকজনের জন্য সমস্যা হতে পারে যারা নিজেদের গোপনীয়তা নিয়ে উদ্বেগে থাকেন।"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> এনক্রিপটেড নেটওয়ার্কের সাথে কানেক্ট করা রয়েছে"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"এখন <xliff:g id="NETWORK_NAME">%1$s</xliff:g> সিম কার্ডের কানেকশন আরও সুরক্ষিত"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"এনক্রিপটেড নয় এমন নেটওয়ার্কের সাথে কানেক্ট করা রয়েছে"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খুলুন"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"এটি কীভাবে কাজ করে"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"বাকি আছে…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 1212af9c2a24..4f058bf74b41 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvorite Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako ovo funkcionira"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index f2447791a65a..77fd6b833bb8 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Obre Missatges"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Com funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendent..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 6b0426729a1d..5615f79d1013 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otevřít Zprávy"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to funguje"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Čeká na vyřízení…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 9bd374ae12e9..fd04e42c8e1f 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åbn Beskeder"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Sådan fungerer det"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Afventer…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 24a71b5ca45d..630ec7512784 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -156,12 +156,12 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sicherheit des Mobilfunknetzes"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Verschlüsselung, Benachrichtigungen für unverschlüsselte Netzwerke"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Auf Geräte-ID zugegriffen"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du deine <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast."</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du deine <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast.\n\nDaher wurden dein Standort, deine Aktivitäten oder deine Identität protokolliert. Das ist zwar üblich, kann jedoch ein Problem für Personen sein, denen ihre Privatsphäre wichtig ist."</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast.\n\nDaher wurden dein Standort, deine Aktivitäten oder deine Identität protokolliert. Das ist zwar üblich, kann jedoch ein Problem für Personen sein, denen ihre Privatsphäre wichtig ist."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Mit verschlüsseltem Netzwerk „<xliff:g id="NETWORK_NAME">%1$s</xliff:g>“ verbunden"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Verbindung der <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM ist jetzt sicherer"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Mit unverschlüsseltem Netzwerk verbunden"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Anrufe, Nachrichten und Daten sind momentan anfälliger für Angriffe, während du deine <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM verwendest"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Anrufe, Nachrichten und Daten sind anfälliger für Angriffe, während du <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM verwendest"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Anrufe, Nachrichten und Daten sind momentan anfälliger für Angriffe, während du deine <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM verwendest.\n\nWenn deine Verbindung wieder verschlüsselt ist, erhältst du eine weitere Benachrichtigung."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Einstellungen für die Sicherheit des Mobilfunknetzes"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Weitere Informationen"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages öffnen"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"So funktionierts"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ausstehend…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 32611ef20d39..54901313dad2 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Άνοιγμα Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Πώς λειτουργεί"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Σε εκκρεμότητα…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 1e1a6873ae4d..a32fcca8fc1f 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 9951afd5137f..9f06f71c147f 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -2413,4 +2413,13 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string> + <string name="fingerprint_dangling_notification_msg_1" msgid="6261149111900787302">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted to improve performance"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7688302770424064884">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted to improve performance"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Set up"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Not now"</string> </resources> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 8d5568169d33..bfcc4be89814 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index a8e397d5b054..80007323188c 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index e7f713bb8822..0fe2cccd8d58 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -2413,4 +2413,13 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string> + <string name="fingerprint_dangling_notification_msg_1" msgid="6261149111900787302">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted to improve performance"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7688302770424064884">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted to improve performance"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Set up"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Not now"</string> </resources> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 5d4e95121033..8717640a6910 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -156,7 +156,7 @@ <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no se ha remitido"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Seguridad de redes móviles"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Encriptación, notificaciones para redes no encriptadas"</string> - <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Se accedió al ID de dispositivo"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Se accedió al ID del dispositivo"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"A la(s) <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras se usaba tu SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A la(s) <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras se usaba tu SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nEsto significa que se registró tu ubicación, actividad o identidad. Esta es una práctica común, pero podría significar un problema para personas preocupadas por su privacidad."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Se conectó a la red encriptada de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensajes"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendiente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 19be81f0f4f9..6549da21376f 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -158,7 +158,7 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Cifrado, notificaciones sobre redes no cifradas"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Se ha accedido al ID del dispositivo"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"A las <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras usabas la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A las <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras usabas la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nEsto significa que se ha registrado tu ubicación, actividad o identidad. Se trata de una práctica habitual, pero puede ser un problema para aquellos a quienes les preocupa su privacidad."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A las <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras usabas la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nEsto significa que se ha registrado tu ubicación, actividad o identidad. Se trata de una práctica habitual, pero puede ser un problema para quienes les preocupa su privacidad."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conectado a la red cifrada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Ahora, la conexión con la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g> es más segura"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectado a una red no cifrada"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre Mensajes"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendiente..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 4a17e0db6c84..92f89e3ccb99 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -154,7 +154,7 @@ <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole suunatud"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole edastatud"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobiilsidevõrgu turve"</string> - <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Krüpteerimine, märguanded krüpteerimata võrkude jaoks"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Krüpteerimine, märguanded krüpteerimata võrkude kohta"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Seadme ID-le on juurde pääsetud"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Kell <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> salvestas lähedal olev võrk võrgu <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM-i kasutamise ajal teie seadme kordumatu ID (IMSI või IMEI)"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Kell <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> salvestas lähedal olev võrk võrgu <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM-i kasutamise ajal teie seadme kordumatu ID (IMSI või IMEI).\n\nSee tähendab, et teie asukoht, tegevus või isik salvestati. See on levinud tava, kuid võib osutada probleemiks inimeste jaoks, kellele on privaatsus eriti oluline."</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ava rakendus Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Tööpõhimõtted"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ootel …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 3f3404c56b8e..2d4130e23fc0 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -2144,10 +2144,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Ohitura moduaren informazio-jakinarazpena"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Bateria-aurreztailea aktibatu da"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Bateria-erabilera murrizten hasi da haren iraupena luzatzeko"</string> - <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) --> - <skip /> - <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) --> - <skip /> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Bateria-aurreztailea aktibatuta dago"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Bateria-aurreztailea aktibatuta dago, bateriaren iraupena luzatzeko"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Bateria-aurreztailea"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Desaktibatu egin da bateria-aurreztailea"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Behar adina bateria dauka telefonoak. Jada ez dago eginbiderik murriztuta."</string> @@ -2415,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ireki Mezuak"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Nola funtzionatzen du?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Zain…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 3f850c12c9c2..07b267471157 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -155,12 +155,12 @@ <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: هدایت نشده"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"امنیت شبکه تلفن همراه"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"رمزگذاری، اعلانهای شبکههای رمزگذارینشده"</string> - <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"شناسه دستگاه دردسترس قرار گرفته است"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"شناسه دستگاه مورددسترس قرار گرفت"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"ساعت <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>، یکی از شبکههای اطراف شناسه یکتای دستگاهتان (IMSI یا IMEI) را هنگام استفاده از سیمکارت <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ضبط کرده است"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"ساعت <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>، یکی از شبکههای اطراف شناسه یکتای دستگاهتان (IMSI یا IMEI) را هنگام استفاده از سیمکارت <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ضبط کرده است.\n\nاین یعنی مکان، فعالیت، یا هویت شما ثبت شده است. این رویکرد عادی است اما ممکن است برای افرادی که نگران حریم خصوصیشان هستند مشکلساز باشد."</string> - <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"به شبکه رمزگذاریشده <xliff:g id="NETWORK_NAME">%1$s</xliff:g> متصل شدید"</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"به شبکه رمزگذاریشده <xliff:g id="NETWORK_NAME">%1$s</xliff:g> متصلاید"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"اتصال سیمکارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> اکنون ایمنتر است"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"به شبکه رمزگذارینشده متصل شدید"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"به شبکه رمزگذارینشده متصلاید"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"درحالحاضر تماسها، پیامها، و دادهها هنگام استفاده از سیمکارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> آسیبپذیرتر هستند"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"درحالحاضر تماسها، پیامها، و دادهها هنگام استفاده از سیمکارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> آسیبپذیرتر هستند.\n\nوقتی اتصال شما دوباره رمزگذاری شود، اعلان دیگری دریافت خواهید کرد."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"تنظیمات امنیت شبکه تلفن همراه"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"باز کردن «پیامها»"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"روش کار"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"درحال تعلیق…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 3e1adc44b0c3..e802443e5acb 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -160,7 +160,7 @@ <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Klo <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> lähellä oleva verkko tallensi laitteesi yksilöllisen tunnuksen (IMSI tai IMEI), kun käytössä oli SIM-kortti, jonka <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> tarjoaa.\n\nTämä tarkoittaa, että sijaintisi, toimintasi tai henkilöllisyytesi on tallennettu. Tämä on tavallista mutta voi huolestuttaa ihmisiä, jotka ovat tarkkoja yksityisyydestään."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Yhdistetty salattuun verkkoon <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"SIM-yhteys, jonka <xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarjoaa, on nyt turvallisempi"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Yhdistetty salattuun verkkoon"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Yhdistetty salaamattomaan verkkoon"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Puhelut, viestit ja data ovat haavoittuvaisempia, kun käytössä on SIM-kortti, jonka <xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarjoaa"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Puhelut, viestit ja data ovat haavoittuvaisempia, kun käytössä on SIM-kortti, jonka <xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarjoaa.\n\nKun yhteys on taas salattu, saat uuden ilmoituksen."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobiiliverkkoa koskevat turvallisuusasetukset"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Avaa Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Näin se toimii"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Odottaa…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index dd968e1ec185..bf70c3cd4e97 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -162,8 +162,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Connecté à un réseau chiffré <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La connexion à la carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> est maintenant plus sûre"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connecté à un réseau non chiffré"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nUne fois que votre connexion est à nouveau chiffrée, vous recevez une nouvelle notification."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, messages et données sont plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion sera à nouveau chiffrée, vous recevrez une nouvelle notification."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Paramètres de sécurité du réseau cellulaire"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"En savoir plus"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"En attente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 96636dcd3c60..57272245224f 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -163,7 +163,7 @@ <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La connexion à la carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> est désormais plus sécurisée"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connecté à un réseau non chiffré"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion est à nouveau chiffrée, vous recevez une nouvelle notification."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion sera à nouveau chiffrée, vous recevrez une nouvelle notification."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Paramètres de sécurité du réseau mobile"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"En savoir plus"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"En attente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 85ad42fe8ff5..663ef9ace52d 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -158,9 +158,9 @@ <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Acceso ao código do dispositivo"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Á/s <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, unha rede próxima rexistrou o código exclusivo (IMSI ou IMEI) do teu dispositivo mentres se usaba a túa SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Á/s <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, unha rede próxima rexistrou o código exclusivo (IMSI ou IMEI) do teu dispositivo mentres se usaba a túa SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nIsto significa que se rexistrou a túa localización, actividade ou identidade. Aínda que se trata dunha práctica común, pode supoñer un problema para as persoas ás que lles preocupe a súa privacidade."</string> - <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conexión á rede encriptada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conectácheste á rede encriptada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"A conexión coa SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g> agora é máis segura"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conexión a unha rede non encriptada"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectácheste a unha rede non encriptada"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Cando usas a SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, as chamadas, mensaxes e datos son máis vulnerables"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Cando usas a SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, as chamadas, mensaxes e datos son máis vulnerables.\n\nRecibirás outra notificación cando se volva encriptar a túa conexión."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configuración de seguranza das redes de telefonía móbil"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensaxes"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 82ffb2f55c9d..dc4253750380 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ખોલો"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"તેની કામ કરવાની રીત"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"બાકી..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 8772cdc62a11..25f6ca1cc6f6 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -154,15 +154,15 @@ <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"मोबाइल नेटवर्क की सुरक्षा"</string> - <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"उन नेटवर्क के लिए सुरक्षा से जुड़ी सूचनाएं जो सुरक्षित नहीं हैं"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"एन्क्रिप्शन, असुरक्षित नेटवर्क के लिए सूचनाएं"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"डिवाइस आईडी को ऐक्सेस किया गया"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"आपके <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम का इस्तेमाल करके, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"आपका <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम इस्तेमाल करके, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया.\n\nइसका मतलब है कि आपकी जगह की जानकारी, गतिविधि या निजी जानकारी को लॉग इन किया गया है. यह सामान्य तरीका है. हालांकि, यह उन लोगों के लिए समस्या की वजह हो सकता है जिन्हें अपनी निजी जानकारी को लेकर चिंता रहती है."</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"आपके <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम के ज़रिए, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> पर आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"आपके <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम के ज़रिए, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> पर आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया.\n\nइसका मतलब है कि आपकी जगह की जानकारी, गतिविधि या निजी जानकारी को लॉग किया गया है. यह आम बात है. हालांकि, यह उन लोगों के लिए समस्या की वजह हो सकता है जिन्हें अपनी निजी जानकारी को लेकर चिंता रहती है."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"एन्क्रिप्ट यानी सुरक्षित नेटवर्क <xliff:g id="NETWORK_NAME">%1$s</xliff:g> से कनेक्ट किया गया"</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"अब <xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का कनेक्शन ज़्यादा सुरक्षित है"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"अब <xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का कनेक्शन ज़्यादा सुरक्षित हो गया है"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"ऐसे नेटवर्क से कनेक्ट किया गया जो सुरक्षित नहीं है"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने के दौरान, कॉल, मैसेज, और डेटा को ऐक्सेस किए जाने का खतरा हो सकता है"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने के दौरान, कॉल, मैसेज, और डेटा को ऐक्सेस किए जाने का खतरा हो सकता है.\n\nजब आपका कनेक्शन फिर से एन्क्रिप्ट यानी सुरक्षित हो जाएगा, तब आपको दोबारा सूचना भेजी जाएगी."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने पर, कॉल, मैसेज, और डेटा ऐक्सेस किए जाने का खतरा हो सकता है"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने पर, कॉल, मैसेज, और डेटा ऐक्सेस किए जाने का खतरा हो सकता है.\n\nजब आपका कनेक्शन फिर से एन्क्रिप्ट यानी सुरक्षित हो जाएगा, तब आपको नई सूचना भेजी जाएगी."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"मोबाइल नेटवर्क की सुरक्षा से जुड़ी सेटिंग"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ज़्यादा जानें"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ठीक है"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ऐप्लिकेशन खोलें"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यह सेटिंग कैसे काम करती है"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"प्रोसेस जारी है..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 88bc29fc3a3c..117d4e5632c6 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Poruke"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako to funkcionira"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index f7e4a8f6ffe9..de8fa848edd6 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"A Messages megnyitása"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hogyan működik?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Függőben…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 251646c15031..d37993442bcf 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -153,9 +153,9 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> վայրկյանից"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string> - <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Ցանցային անվտանգություն"</string> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Բջջային ցանցի անվտանգություն"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Գաղտնագրում, ծանուցումներ չգաղտնագրված ցանցերի համար"</string> - <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Սարքի նույնացույցիչը հասանելի է դարձել"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Սարքի նույնացուցիչը հասանելի է դարձել"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Ժամը <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-ին մոտակա ցանցը գրանցել է ձեր սարքի եզակի նույնացուցիչը (IMSI-ը կամ IMEI-ը) <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-ի ձեր SIM քարտի օգտագործման ժամանակ"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Ժամը <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-ին մոտակա ցանցը գրանցել է ձեր սարքի եզակի նույնացուցիչը (IMSI-ը կամ IMEI-ը) <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-ի ձեր SIM քարտի օգտագործման ժամանակ։\n\nԴա նշանակում է, որ ձեր տեղադրությունը, գործողությունները կամ անձը նույնականացնող տվյալները գրանցվել են։ Սա սովորական գործելակերպ է, սակայն կարող է խնդիր լինել այն մարդկանց համար, որոնք մտահոգված են իրենց գաղտնիությամբ։"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Հեռախոսը միացավ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> գաղտնագրված ցանցին"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Բացել Messages-ը"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ինչպես է դա աշխատում"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Առկախ է…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 139b4ab8338b..e6634d23fd62 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -157,12 +157,12 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Enkripsi, notifikasi untuk jaringan yang tidak terenkripsi"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID perangkat diakses"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, jaringan di sekitar merekam ID unik perangkat Anda (IMSI atau IMEI) saat menggunakan kartu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> Anda"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, jaringan di sekitar merekam ID unik perangkat Anda (IMSI atau IMEI) saat menggunakan kartu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> Anda.\n\nHal ini berarti lokasi, aktifitas, atau identitas Anda telah dicatat dalam log. Tindakan ini adalah praktik umum tetapi dapat menjadi masalah bagi orang yang mengkhawatirkan privasi."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, jaringan di sekitar merekam ID unik perangkat Anda (IMSI atau IMEI) saat menggunakan kartu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> Anda.\n\nHal ini berarti lokasi, aktivitas, atau identitas Anda telah dicatat dalam log. Tindakan ini adalah praktik umum tetapi dapat menjadi masalah bagi orang yang mengkhawatirkan privasi."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Terhubung ke jaringan yang terenkripsi <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Koneksi kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> kini lebih aman"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Terhubung ke jaringan yang tidak terenkripsi"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Panggilan, pesan, dan data saat ini lebih rentan saat menggunakan kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> Anda"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Panggilan, pesan, dan data saat ini lebih rentan saat menggunakan kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> Anda.\n\nKetika koneksi Anda terenkripsi lagi, Anda akan menerima notifikasi lainnya."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Panggilan, pesan, dan data saat ini lebih rentan saat menggunakan kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> Anda.\n\nKetika koneksi Anda terenkripsi lagi, Anda akan kembali menerima notifikasi."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Setelan keamanan jaringan seluler"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Pelajari lebih lanjut"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Oke"</string> @@ -639,7 +639,7 @@ <string name="permdesc_mediaLocation" msgid="597912899423578138">"Mengizinkan aplikasi untuk membaca lokasi dari koleksi media Anda."</string> <string name="biometric_app_setting_name" msgid="3339209978734534457">"Gunakan biometrik"</string> <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Gunakan biometrik atau kunci layar"</string> - <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifikasi bahwa ini memang Anda"</string> + <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifikasi diri Anda"</string> <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Gunakan biometrik untuk melanjutkan"</string> <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Gunakan biometrik atau kunci layar untuk melanjutkan"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrik tidak tersedia"</string> @@ -2221,7 +2221,7 @@ <string name="miniresolver_call_information" msgid="6739417525304184083">"Organisasi Anda hanya mengizinkan menelepon dari aplikasi kerja"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organisasi Anda hanya mengizinkan pengiriman pesan dari aplikasi kerja"</string> <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Anda hanya dapat melakukan panggilan telepon dari aplikasi Telepon pribadi. Panggilan yang dilakukan dengan aplikasi Telepon pribadi akan ditambahkan ke histori panggilan pribadi."</string> - <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Anda hanya dapat mengirim pesan SMS dari aplikasi Message pribadi."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Anda hanya dapat mengirim pesan SMS dari aplikasi Pesan pribadi."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Gunakan browser pribadi"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Gunakan browser kerja"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Telepon"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Message"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara kerjanya"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Tertunda..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 1f55241f1363..f77a18a3911b 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Opna Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Svona virkar þetta"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Í bið…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 2f066b5f46cb..95ccad54d63f 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Apri Messaggi"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Come funziona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"In attesa…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 3fc962acc9a7..fee437af96cf 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -162,9 +162,9 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"יש חיבור לרשת המוצפנת <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"החיבור של כרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g> מאובטח יותר עכשיו"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"יש חיבור לרשת לא מוצפנת"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"השיחות, ההודעות והנתונים שלך פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g> שלך"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"שיחות, הודעות ונתונים פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g> שלך.\n\nכשהחיבור שלך יוצפן שוב, תישלח אליך התראה נוספת."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"הגדרות אבטחה של רשת סלולרית"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"השיחות, ההודעות והנתונים שלך פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"השיחות, ההודעות והנתונים שלך פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nכשהחיבור שלך יוצפן שוב, תישלח אליך התראה נוספת."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"הגדרות אבטחה של הרשת הסלולרית"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"מידע נוסף"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"הבנתי"</string> <string name="fcComplete" msgid="1080909484660507044">"קוד תכונה הושלם."</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"לפתיחת Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"איך זה עובד"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"בהמתנה..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index db3ba5a49246..726db1c4ea89 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -154,7 +154,7 @@ <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"モバイル ネットワーク セキュリティ"</string> - <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"暗号化(ネットワークが暗号化されていない場合に通知)"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"暗号化、暗号化されていないネットワークに関する通知"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"デバイス ID へのアクセスが発生しました"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>、<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> の SIM の使用中に付近のネットワークでお使いのデバイスの一意の ID(IMSI または IMEI)が記録されました"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>、<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> の SIM の使用中に付近のネットワークでお使いのデバイスの一意の ID(IMSI または IMEI)が記録されました。\n\nつまり、あなたの位置情報、アクティビティ、身元などが記録されことになります。これはよくある事象ですが、プライバシーに不安を持たれている人にとっては問題になる可能性があります。"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"メッセージ アプリを開く"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"仕組み"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"保留中..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 97003c4fd11b..bdf6c489326b 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages-ის გახსნა"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"მუშაობის პრინციპი"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"მომლოდინე..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 116166d5c3c9..55cf92bb3280 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -162,7 +162,7 @@ <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Енді <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасымен қосылу қорғалған."</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Шифрланбаған желіге қосылды"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын пайдаланған кезде, қазіргі уақытта қоңырауларға, хабарларға және деректерге қауіп төнеді."</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын пайдаланған кезде, қазіргі уақытта қоңырауларға, хабарларға және деректерге қауіп төнеді.\n\nБайланыс қайта шифрланғанда, тағы бір хабарландыру келеді."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын пайдаланған кезде, қазір қоңырауларға, хабарларға және деректерге зиян тию қаупі жоғары.\n\nБайланыс қайта шифрланғанда, тағы бір хабарландыру келеді."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Мобильдік желінің қауіпсіздік параметрлері"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Толық ақпарат"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Түсінікті"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages қолданбасын ашу"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Бұл қалай орындалады?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Дайын емес…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 456256c1432c..6a826db47006 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -156,8 +156,8 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"សុវត្ថិភាពបណ្ដាញទូរសព្ទចល័ត"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ការអ៊ីនគ្រីប ការជូនដំណឹងសម្រាប់បណ្ដាញដែលមិនបានអ៊ីនគ្រីប"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"បានចូលប្រើប្រាស់លេខសម្គាល់ឧបករណ៍"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខសម្គាល់ពិសេស (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខសម្គាល់ពិសេស (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក។\n\nនេះមានន័យថា ទីតាំង សកម្មភាព ឬអត្តសញ្ញាណរបស់អ្នកត្រូវបានចុះកំណត់ហេតុ។ នេះគឺជាការអនុវត្តទូទៅ ប៉ុន្តែអាចនឹងមានបញ្ហាសម្រាប់អ្នកដែលមានកង្វល់ពាក់ព័ន្ធនឹងឯកជនភាព។"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខកូដសម្គាល់ខុសពីគេ (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខកូដសម្គាល់ខុសពីគេ (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក។\n\nនេះមានន័យថា ទីតាំង សកម្មភាព ឬអត្តសញ្ញាណរបស់អ្នកត្រូវបានកត់ត្រាទុក។ នេះគឺជាការអនុវត្តទូទៅ ប៉ុន្តែអាចនឹងមានបញ្ហាសម្រាប់អ្នកដែលមានកង្វល់ពាក់ព័ន្ធនឹងឯកជនភាព។"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"បានភ្ជាប់ទៅបណ្ដាញដែលបានអ៊ីនគ្រីប <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"ការតភ្ជាប់ស៊ីម <xliff:g id="NETWORK_NAME">%1$s</xliff:g> កាន់តែមានសុវត្ថិភាពឥឡូវនេះ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"បានភ្ជាប់ទៅបណ្ដាញដែលមិនបានអ៊ីនគ្រីប"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"បើកកម្មវិធី Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"របៀបដែលវាដំណើរការ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"កំពុងរង់ចាំ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 97f013638877..40d513893dda 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -156,12 +156,12 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಸೆಕ್ಯೂರಿಟಿ"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ಎನ್ಕ್ರಿಪ್ಶನ್, ಎನ್ಕ್ರಿಪ್ಟ್ ಮಾಡದ ನೆಟ್ವರ್ಕ್ಗಳಿಗೆ ನೋಟಿಫಿಕೇಶನ್ಗಳು"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ಸಾಧನದ ID ಅನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲಾಗಿದೆ"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ, ನಿಮ್ಮ ಸಾಧನದ ಅನನ್ಯ ID (IMSI ಅಥವಾ IMEI) ಅನ್ನು ನಿಮ್ಮ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಹತ್ತಿರದ ನೆಟ್ವರ್ಕ್ನಿಂದ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗಿದೆ"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ಕ್ಕೆ, <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ಬಳಸುವಾಗ ಸಾಧನದ ಅನನ್ಯ ID (IMSI ಅಥವಾ IMEI)ಯನ್ನು ಹತ್ತಿರದ ನೆಟ್ವರ್ಕ್ ರೆಕಾರ್ಡ್ ಮಾಡಿದೆ"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ, ನಿಮ್ಮ ಸಾಧನದ ಅನನ್ಯ ID (IMSI ಅಥವಾ IMEI) ಅನ್ನು ನಿಮ್ಮ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಹತ್ತಿರದ ನೆಟ್ವರ್ಕ್ನಿಂದ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗಿದೆ.\n\nಇದರರ್ಥ ನಿಮ್ಮ ಸ್ಥಳ, ಚಟುವಟಿಕೆ ಅಥವಾ ಗುರುತನ್ನು ಲಾಗ್ ಮಾಡಲಾಗಿದೆ. ಇದು ಸಾಮಾನ್ಯ ವಿಧಾನವಾಗಿದೆ ಆದರೆ ಗೌಪ್ಯತೆಯ ಕುರಿತು ಕಾಳಜಿವಹಿಸುವವರಿಗೆ ಸಮಸ್ಯೆಯಾಗಬಹುದು."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ಎನ್ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ನೆಟ್ವರ್ಕ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕನೆಕ್ಷನ್ ಈಗ ಹೆಚ್ಚು ಸುರಕ್ಷಿತವಾಗಿದೆ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"ಎನ್ಕ್ರಿಪ್ಟ್ ಮಾಡದ ನೆಟ್ವರ್ಕ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ನಿಮ್ಮ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಕರೆಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಡೇಟಾ ಪ್ರಸ್ತುತ ಹೆಚ್ಚು ಸೂಕ್ಷ್ಮವಾಗಿರುತ್ತದೆ."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ನಿಮ್ಮ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಕರೆಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಬೇರೆಯವರು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದಾದ ಅಪಾಯವಿರುತ್ತದೆ"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"ನಿಮ್ಮ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಕರೆಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಡೇಟಾ ಪ್ರಸ್ತುತ ಹೆಚ್ಚು ಸೂಕ್ಷ್ಮವಾಗಿರುತ್ತದೆ.\n\nನಿಮ್ಮ ಕನೆಕ್ಷನ್ ಅನ್ನು ಮತ್ತೆ ಎನ್ಕ್ರಿಪ್ಟ್ ಮಾಡಿದಾಗ, ನೀವು ಇನ್ನೊಂದು ನೋಟಿಫಿಕೇಶನ್ ಅನ್ನು ಪಡೆಯುತ್ತೀರಿ."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಸೆಕ್ಯೂರಿಟಿ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ಅನ್ನು ತೆರೆಯಿರಿ"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ಇದು ಹೇಗೆ ಕೆಲಸ ಮಾಡುತ್ತದೆ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ಬಾಕಿ ಉಳಿದಿದೆ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index df29128d17ed..dd476281eec9 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"메시지 열기"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"작동 방식"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"대기 중…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 9511db4fd9d3..bb29c36a0410 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -156,13 +156,13 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Мобилдик тармактын коопсуздугу"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрлөө, шифрленбеген тармактар жөнүндө билдирмелер"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Түзмөктүн идентификатору колдонулду"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасын колдонуп жатканда жакын жердеги тармакта түзмөгүңүздүн өзгөчө идентификатору (IMSI же IMEI) жазылды"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасын колдонуп жатканда жакын жердеги тармакта түзмөгүңүздүн өзгөчө идентификатору (IMSI же IMEI) жазылды.\n\nЖүргөн жериңиз, аракеттериңиз же өздүгүңүз тууралуу маалымат катталды. Бул адаттагы көрүнүш болсо да, купуялыгы жөнүндө тынчсызданган адамдарга маселе жаратышы мүмкүн."</string> - <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Шифрленген тармакка (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) туташты"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Саат <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасы колдонулуп жатканда, жакын жердеги тармак түзмөгүңүздүн өзгөчө идентификаторун (IMSI же IMEI) жазып алды."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Саат <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасы колдонулуп жатканда, жакын жердеги тармак түзмөгүңүздүн өзгөчө идентификаторун (IMSI же IMEI) жазып алды.\n\nТактап айтканда, жүргөн жериңиз, аракеттериңиз же өздүгүңүз катталды. Бул адаттагы көрүнүш болсо да, купуялыгы жөнүндө тынчсызданган адамдарга маселе жаратышы мүмкүн."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Шифрленген <xliff:g id="NETWORK_NAME">%1$s</xliff:g> тармагына туташты"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Эми <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасы менен туташуу коопсуз болуп калды"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Шифрленбеген тармакка туташты"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Учурда <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын колдонсоңуз, чалууларга, билдирүүлөргө жана маалыматтарга коркунуч жаралышы мүмкүн"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Учурда <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын колдонсоңуз, чалууларга, билдирүүлөргө жана маалыматтарга коркунуч жаралышы мүмкүн.\n\nТуташууңуз кайра шифрленгенде дагы бир билдирме аласыз."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Учурда <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын колдонсоңуз, чалууларга, билдирүүлөргө жана маалыматтарга коркунуч жаралышы мүмкүн.\n\nБайланышыңыз кайра шифрленгенде дагы бир билдирме аласыз."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Мобилдик тармактын коопсуздук параметрлери"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Кеңири маалымат"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Түшүндүм"</string> @@ -1334,7 +1334,7 @@ <!-- no translation found for network_available_sign_in_detailed (7520423801613396556) --> <skip /> <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> Интернетке туташуусу жок"</string> - <string name="wifi_no_internet_detailed" msgid="634938444133558942">"Параметрлерди ачуу үчүн таптап коюңуз"</string> + <string name="wifi_no_internet_detailed" msgid="634938444133558942">"Параметрлерди ачуу үчүн тийип коюңуз"</string> <string name="mobile_no_internet" msgid="4014455157529909781">"Мобилдик Интернет жок"</string> <string name="other_networks_no_internet" msgid="6698711684200067033">"Тармактын Интернет жок"</string> <string name="private_dns_broken_detailed" msgid="3709388271074611847">"Жеке DNS сервери жеткиликсиз"</string> @@ -1396,7 +1396,7 @@ <string name="usb_midi_notification_title" msgid="7404506788950595557">"USB аркылуу MIDI режими күйгүзүлдү"</string> <string name="usb_uvc_notification_title" msgid="2030032862673400008">"Түзмөк веб-камера катары туташты"</string> <string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB шайманы туташты"</string> - <string name="usb_notification_message" msgid="4715163067192110676">"Кошумча параметрлерди ачуу үчүн таптап коюңуз."</string> + <string name="usb_notification_message" msgid="4715163067192110676">"Кошумча параметрлерди ачуу үчүн тийип коюңуз."</string> <string name="usb_power_notification_message" msgid="7284765627437897702">"Туташкан түзмөк кубатталууда. Дагы параметрлерди көрүү үчүн таптап коюңуз."</string> <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Аналогдук аудио жабдуу табылды"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Тиркелген түзмөк бул телефонго шайкеш келбейт. Көбүрөөк маалымат алуу үчүн таптап коюңуз."</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Жазышуулар колдонмосун ачуу"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ал кантип иштейт"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Кезекте турат..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index a0aded4e183d..5fa0fc135612 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"ເປີດ Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ມັນເຮັດວຽກແນວໃດ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ລໍຖ້າດຳເນີນການ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 837bff76ecf2..e06c2efe31b8 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atidaryti programą „Messages“"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kaip tai veikia"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Laukiama..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 4e244734f340..d2a6cd9af5a6 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atvērt lietotni Ziņojumi"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Darbības principi"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Gaida…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 3790be062ec2..796f9efe1bbc 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -153,7 +153,7 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> по <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е препратено"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е проследен"</string> - <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Обезбедување на мобилна мрежа"</string> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Безбедност на мобилна мрежа"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрирање, известувања за нешифрирани мрежи"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Пристапено е до ID на уредот"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Во <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, мрежа во близина го сними уникатниот ID (IMSI или IMEI) на вашиот телефон со користење на вашата SIM-картичка на <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> @@ -161,9 +161,9 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Поврзано со шифрирана мрежа <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Врската со SIM-картичката на <xliff:g id="NETWORK_NAME">%1$s</xliff:g> сега е побезбедна"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Поврзано со нешифрирана мрежа"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Повиците, пораките и податоците во моментов се почувствителни додека ја користите вашата SIM-картичка на <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Повиците, пораките и податоците се почувствителни кога го користите вашиот SIM од <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Повиците, пораките и податоците во моментов се почувствителни додека ја користите вашата SIM-картичка на <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nКога вашата врска ќе биде шифрирана повторно, ќе добиете уште едно известување."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Поставки за обезбедување на мобилна мрежа"</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Поставки за безбедност на мобилната мрежа"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Дознајте повеќе"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Сфатив"</string> <string name="fcComplete" msgid="1080909484660507044">"Кодот за карактеристиката заврши."</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отворете ја Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Дознајте како функционира"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Во фаза на чекање…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 563efdb47efd..a0b4c8d80e96 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages തുറക്കുക"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ഇത് പ്രവർത്തിക്കുന്നത് എങ്ങനെയാണ്"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"തീർപ്പാക്കിയിട്ടില്ല..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index afde064d48f9..5ba16fe87ffd 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1762,7 +1762,7 @@ <string name="user_switched" msgid="7249833311585228097">"Одоогийн хэрэглэгч <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> руу сэлгэж байна…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>-с гарч байна…"</string> - <string name="owner_name" msgid="8713560351570795743">"Эзэмшигч"</string> + <string name="owner_name" msgid="8713560351570795743">"Өмчлөгч"</string> <string name="guest_name" msgid="8502103277839834324">"Зочин"</string> <string name="error_message_title" msgid="4082495589294631966">"Алдаа"</string> <string name="error_message_change_not_allowed" msgid="843159705042381454">"Энэ өөрчлөлтийг админ зөвшөөрөөгүй байна"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Мессежийг нээх"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Энэ хэрхэн ажилладаг вэ?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Хүлээгдэж буй..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 8e8d354a3a5b..6c19cc592880 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages उघडा"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ते कसे काम करते"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"प्रलंबित आहे..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index fe4ab56b7975..82c1a5974c62 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -157,7 +157,7 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Penyulitan, pemberitahuan untuk rangkaian yang tidak disulitkan"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID peranti diakses"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, rangkaian berdekatan merekodkan ID unik peranti anda (IMSI atau IMEI) semasa menggunakan SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> anda"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, rangkaian berdekatan merekodkan ID unik peranti anda (IMSI atau IMEI) semasa menggunakan SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> anda.\n\nHal ini bermaksud bahawa lokasi, aktiviti atau identiti anda telah dilog. Amalan ini biasa dilakukan tetapi mungkin sukar dilakukan oleh pengguna yang bimbang tentang privasi."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, rangkaian berdekatan merekodkan ID unik peranti anda (IMSI atau IMEI) semasa menggunakan SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> anda.\n\nHal ini bermaksud bahawa lokasi, aktiviti atau identiti anda telah dilog. Amalan ini biasa dilakukan tetapi mungkin menjadi hal kepada pengguna yang bimbang tentang privasi."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Disambungkan kepada rangkaian <xliff:g id="NETWORK_NAME">%1$s</xliff:g> yang disulitkan"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Sambungan SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> kini lebih selamat"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Disambungkan kepada rangkaian yang tidak disulitkan"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara ciri ini berfungsi"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Belum selesai..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 5f5b0ab61661..43b7ffafd093 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -157,12 +157,12 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"အသွင်ဝှက်ခြင်း၊ အသွင်ဝှက်မထားသော ကွန်ရက်များအတွက် အကြောင်းကြားချက်များ"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"စက် ID သုံးထားသည်"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> တွင် သင့် <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် အနီးရှိ ကွန်ရက်သည် စက်ပစ္စည်း၏ သီးသန့် ID (IMSI (သို့) IMEI) ကို မှတ်တမ်းတင်ထားသည်"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> တွင် သင့် <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် အနီးရှိ ကွန်ရက်သည် စက်ပစ္စည်း၏ သီးသန့် ID (IMSI (သို့) IMEI) ကို မှတ်တမ်းတင်ထားသည်။\n\nဆိုလိုသည်မှာ သင့်တည်နေရာ၊ လုပ်ဆောင်ချက် (သို့) အထောက်အထားကို မှတ်တမ်းတင်ထားသည်။ ၎င်းသည် သာမန်လုပ်ဆောင်မှုဖြစ်သော်လည်း ကိုယ်ရေးအချက်အလက်လုံခြုံမှုနှင့်ပတ်သက်၍ စိုးရိမ်သောသူများအတွက် ပြဿနာတစ်ခု ဖြစ်နိုင်သည်။"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> တွင် သင့် <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် အနီးရှိ ကွန်ရက်သည် စက်ပစ္စည်း၏ သီးသန့် ID (IMSI (သို့) IMEI) ကို မှတ်တမ်းတင်ထားသည်။\n\nဆိုလိုသည်မှာ သင့်တည်နေရာ၊ လုပ်ဆောင်ချက် (သို့) အထောက်အထားကို မှတ်တမ်းတင်ထားသည်။ ၎င်းသည် သာမန်လုပ်ဆောင်မှုဖြစ်သော်လည်း ကိုယ်ရေးအချက်အလက်လုံခြုံမှုနှင့် ပတ်သက်၍ အချို့သူများအတွက် စိုးရိမ်စရာ ဖြစ်နိုင်သည်။"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"အသွင်ဝှက်ထားသော ကွန်ရက် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> သို့ ချိတ်ဆက်ထားသည်"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ်ကတ် ချိတ်ဆက်မှုသည် ယခု ပိုမိုလုံခြုံပါပြီ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"အသွင်ဝှက်မထားသော ကွန်ရက်သို့ ချိတ်ဆက်ထားသည်"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် ဖုန်းခေါ်ဆိုမှု၊ မက်ဆေ့ဂျ်နှင့် ဒေတာများသည် လက်ရှိတွင် ပိုမိုထိခိုက်လွယ်သည်"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် ဖုန်းခေါ်ဆိုမှု၊ မက်ဆေ့ဂျ်နှင့် ဒေတာများသည် လက်ရှိတွင် ပိုမိုထိခိုက်လွယ်သည်။\n\nသင့်ချိတ်ဆက်မှုကို ထပ်မံအသွင်ဝှက်လိုက်သောအခါ အကြောင်းကြားချက်နောက်တစ်ခု ရရှိပါမည်။"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ် သုံးချိန်တွင် ဖုန်း၊ မက်ဆေ့ဂျ်၊ ဒေတာ လုံခြုံမှုအားနည်းသည်"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ် သုံးချိန်တွင် ဖုန်း၊ မက်ဆေ့ဂျ်၊ ဒေတာ လုံခြုံမှုအားနည်းသည်။\n\nသင့်ချိတ်ဆက်မှုကို ထပ်မံအသွင်ဝှက်ပါက အကြောင်းကြားချက်နောက်တစ်ခု ရရှိပါမည်။"</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"မိုဘိုင်းကွန်ရက် လုံခြုံရေး ဆက်တင်များ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ပိုမိုလေ့လာရန်"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"နားလည်ပြီ"</string> @@ -644,7 +644,7 @@ <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ရှေ့ဆက်ရန် သင်၏ ဇီဝမက်ထရစ်အချက်အလက် (သို့) ဖန်သားပြင်လော့ခ်ကို သုံးပါ"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ဇီဝအချက်အလက်သုံး ကွန်ပျူတာစက်ပစ္စည်း မရရှိနိုင်ပါ"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string> - <string name="biometric_not_recognized" msgid="5106687642694635888">"မသိ"</string> + <string name="biometric_not_recognized" msgid="5106687642694635888">"မသိပါ"</string> <string name="biometric_face_not_recognized" msgid="5535599455744525200">"မျက်နှာကို မသိရှိပါ"</string> <string name="biometric_error_canceled" msgid="8266582404844179778">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string> <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ပင်နံပါတ်၊ လော့ခ်ပုံစံ သို့မဟုတ် စကားဝှက် သတ်မှတ်မထားပါ"</string> @@ -2220,7 +2220,7 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"အလုပ်သုံးအက်ပ်သို့ ပြောင်းမလား။"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"သင့်အဖွဲ့အစည်းသည် သင့်အား အလုပ်သုံးအက်ပ်များမှသာ ဖုန်းဆက်ခွင့်ပြုသည်"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"သင့်အဖွဲ့အစည်းသည် သင့်အား အလုပ်သုံးအက်ပ်များမှသာ မက်ဆေ့ဂျ်ပို့ခွင့်ပြုသည်"</string> - <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"သင့်ကိုယ်ပိုင် ‘ဖုန်းအက်ပ်’ မှသာ ဖုန်းခေါ်ဆိုနိုင်သည်။ ကိုယ်ပိုင် ‘ဖုန်း’ ဖြင့် ပြုလုပ်သော ခေါ်ဆိုမှုများကို သင်၏ကိုယ်ပိုင် ခေါ်ဆိုမှုမှတ်တမ်းသို့ ထည့်ပါမည်။"</string> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"သင့်ကိုယ်ပိုင် ‘ဖုန်းအက်ပ်’ မှသာ ဖုန်းခေါ်ဆိုနိုင်သည်။ ကိုယ်ပိုင် ‘ဖုန်း’ ဖြင့် ခေါ်ဆိုမှုများကို သင်၏ ကိုယ်ပိုင် ခေါ်ဆိုမှုမှတ်တမ်းသို့ ထည့်ပါမည်။"</string> <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"သင့်ကိုယ်ပိုင် Messages အက်ပ်မှသာ SMS မက်ဆေ့ဂျ်များကို ပို့နိုင်သည်။"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ကိုယ်ပိုင်ဘရောင်ဇာ သုံးရန်"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"အလုပ်သုံးဘရောင်ဇာ သုံးရန်"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ဖွင့်ရန်"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"အလုပ်လုပ်ပုံ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ဆိုင်းငံ့ထားသည်…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index c4c705c5a942..ad4ffc202466 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -156,7 +156,7 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sikkerhet for mobilnettverk"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Kryptering, varsler for ukrypterte nettverk"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Enhets-ID-en er lest"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerte et nettverk i nærheten den unike ID-en (IMSI eller IMEI) til enheten din mens du brukte SIM-kortet fra <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerte et nettverk i nærheten enhetens unike ID (IMSI eller IMEI) mens du brukte <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM-kortet"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerte et nettverk i nærheten den unike ID-en (IMSI eller IMEI) til enheten din mens du brukte SIM-kortet fra <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nDette betyr at posisjonen, aktiviteten eller identiteten din er registrert. Dette er en vanlig praksis, men kan være et problem for folk som er opptatt av personvern."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Koblet til det krypterte nettverket <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Tilkoblingen til SIM-kortet fra <xliff:g id="NETWORK_NAME">%1$s</xliff:g> er sikrere nå"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åpne Meldinger"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Slik fungerer det"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Venter …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 559f07a3e6da..1cde247403c3 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -156,8 +156,8 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"मोबाइल नेटवर्कको सुरक्षा"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"इन्क्रिप्सन, इन्क्रिप्ट नगरिएका नेटवर्कसम्बन्धी सूचना"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"डिभाइसको ID एक्सेस गरियो"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> बजे तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> बजे तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ।\n\nयसको अर्थ तपाईंको लोकेसन, गतिविधि वा पहिचान लग गरिएको छ। यसो गर्नु सामान्य हो तर आफ्नो गोपनीयताका बारेमा चिन्ता लिने मान्छेहरूका लागि यो समस्याको विषय हुन सक्छ।"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> मा तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> मा तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ।\n\nयसको अर्थ तपाईंको लोकेसन, गतिविधि वा पहिचान लग गरिएको छ। यसो गर्नु सामान्य हो तर आफ्नो गोपनीयताका बारेमा चिन्ता लिने मान्छेहरूका लागि यो समस्याको विषय हुन सक्छ।"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"इन्क्रिप्ट गरिएको नेटवर्क <xliff:g id="NETWORK_NAME">%1$s</xliff:g> मा कनेक्ट गरियो"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM को कनेक्सन अब सुरक्षित छ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"इन्क्रिप्ट नगरिएको नेटवर्कमा कनेक्ट गरियो"</string> @@ -466,11 +466,11 @@ <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"औपचारिक प्रसारणलाई पठाउनको लागि एउटा एपलाई अनुमति दिन्छ, जुन प्रसारण समाप्त भएपछि बाँकी रहन्छ। अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग गरेको कारणले ट्याब्लेटलाई ढिलो र अस्थिर बनाउन सक्छ।"</string> <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"एपलाई प्रसारण समाप्त भइसकेपछि पनि रहिरहने स्टिकी प्रसारणहरू पठाउने अनुमति दिन्छ। यो सुविधाको अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग हुने भएकाले तपाईंको Android टिभी यन्त्र सुस्त वा अस्थिर हुन सक्छ।"</string> <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"औपचारिक प्रसारणलाई पठाउनको लागि एक एपलाई अनुमति दिन्छ, जुन प्रसारण समाप्त भएपछि बाँकी रहन्छ। अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग गरेको कारणले फोनलाई ढिलो र अस्थिर बनाउन सक्छ।"</string> - <string name="permlab_readContacts" msgid="8776395111787429099">"तपाईँका सम्पर्कहरू पढ्नुहोस्"</string> + <string name="permlab_readContacts" msgid="8776395111787429099">"तपाईँका कन्ट्याक्टहरू पढ्नुहोस्"</string> <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"एपलाई तपाईंको ट्याब्लेटमा भण्डारण गरिएका कन्ट्याक्टहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। एपहरूले कन्ट्याक्टहरू बनाउने तपाईंको ट्याब्लेटमा भण्डारण गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले एपहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सेभ गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"एपलाई तपाईंको Android टिभी डिभाइसमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा पढ्न अनुमति दिन्छ। एपहरूले कन्ट्याक्टहरू बनाउने तपाईंको Android टिभी डिभाइसमा भण्डारण गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले एपहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सेभ गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"एपलाई तपाईंको फोनमा भण्डारण गरिएका कन्ट्याक्टहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। एपहरूले कन्ट्याक्टहरू बनाउने तपाईंको फोनमा भण्डारण गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले एपहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सेभ गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> - <string name="permlab_writeContacts" msgid="8919430536404830430">"तपाईँका सम्पर्कहरू परिवर्तन गर्नुहोस्"</string> + <string name="permlab_writeContacts" msgid="8919430536404830430">"तपाईँका कन्ट्याक्टहरू परिवर्तन गर्नुहोस्"</string> <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"एपलाई तपाईंको ट्याब्लेटमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"एपलाई तपाईंको Android टिभी डिभाइसमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"एपलाई तपाईंको फोनमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> @@ -645,7 +645,7 @@ <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेयर उपलब्ध छैन"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"प्रमाणीकरण रद्द गरियो"</string> <string name="biometric_not_recognized" msgid="5106687642694635888">"पहिचान भएन"</string> - <string name="biometric_face_not_recognized" msgid="5535599455744525200">"अनुहार पहिचान गर्न सकिएन"</string> + <string name="biometric_face_not_recognized" msgid="5535599455744525200">"अनुहार मिलेन"</string> <string name="biometric_error_canceled" msgid="8266582404844179778">"प्रमाणीकरण रद्द गरियो"</string> <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"कुनै पनि PIN, ढाँचा वा पासवर्ड सेट गरिएको छैन"</string> <string name="biometric_error_generic" msgid="6784371929985434439">"प्रमाणित गर्ने क्रममा त्रुटि भयो"</string> @@ -666,7 +666,7 @@ </string-array> <string name="fingerprint_error_not_match" msgid="4599441812893438961">"फिंगरप्रिन्ट मिलेन"</string> <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"फिंगरप्रिन्ट मिलेन"</string> - <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5590293588784953188">"अनुहार पहिचान गर्न सकिएन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string> + <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5590293588784953188">"अनुहार मिलेन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string> <string name="fingerprint_authenticated" msgid="2024862866860283100">"फिंगरप्रिन्ट प्रमाणीकरण गरियो"</string> <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"अनुहार प्रमाणीकरण गरियो"</string> <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"अनुहार प्रमाणीकरण गरियो, कृपया पुष्टि गर्नुहोस् थिच्नुहोस्"</string> @@ -716,7 +716,7 @@ <string name="face_acquired_not_detected" msgid="1057966913397548150">"तपाईंको अनुहार देखिएन। तपाईंको फोन आफ्नो आँखाअघि राखी समात्नुहोस्।"</string> <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"अत्यधिक हल्लियो। फोन स्थिर राख्नुहोस्।"</string> <string name="face_acquired_recalibrate" msgid="8724013080976469746">"कृपया आफ्नो अनुहार पुनः दर्ता गर्नुहोस्।"</string> - <string name="face_acquired_too_different" msgid="4505278456634706967">"अनुहार पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string> + <string name="face_acquired_too_different" msgid="4505278456634706967">"अनुहार मिलेन। फेरि प्रयास गर्नुहोस्।"</string> <string name="face_acquired_too_similar" msgid="8882920552674125694">"आफ्नो टाउको थोरै यताउता सार्नुहोस्"</string> <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string> <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string> @@ -1507,7 +1507,7 @@ <string name="ime_action_previous" msgid="6548799326860401611">"अघिल्लो"</string> <string name="ime_action_default" msgid="8265027027659800121">"चलाउनुहोस्"</string> <string name="dial_number_using" msgid="6060769078933953531">\n"नम्बर डायल गर्नुहोस् <xliff:g id="NUMBER">%s</xliff:g> प्रयोग गरेर"</string> - <string name="create_contact_using" msgid="6200708808003692594">"सम्पर्क सिर्जना गर्नुहोस्\nयो <xliff:g id="NUMBER">%s</xliff:g> प्रयोग गरेर"</string> + <string name="create_contact_using" msgid="6200708808003692594">"कन्ट्याक्ट हाल्नुहोस्\nयो <xliff:g id="NUMBER">%s</xliff:g> प्रयोग गरेर"</string> <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"निम्न एउटा वा धेरै एपहरूले तपाईँको खातामा पहुँचको लागि अनुमति अहिले र भविष्यमा अनुरोध गर्छन्।"</string> <string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"के तपाईं यस अनुरोधलाई अनुमति दिन चाहनुहुन्छ?"</string> <string name="grant_permissions_header_text" msgid="3420736827804657201">"अनुरोध पहुँच गर्नुहोस्"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages खोल्नुहोस्"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यसले काम गर्ने तरिका"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"विचाराधीन..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 5c393af52d37..7bae96e6ab88 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -159,9 +159,9 @@ <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Om <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> heeft een netwerk in de buurt de unieke ID van je apparaat (IMSI of IMEI) geregistreerd toen je je simkaart van <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> gebruikte"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Om <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> heeft een netwerk in de buurt de unieke ID van je apparaat (IMSI of IMEI) geregistreerd toen je je simkaart van <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> gebruikte.\n\nDit betekent dat je locatie, activiteit of identiteit is geregistreerd. Dit is gebruikelijk, maar kan een probleem zijn voor mensen die zich zorgen maken over privacy."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Verbonden met versleuteld netwerk <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"De verbinding van de simkaart van <xliff:g id="NETWORK_NAME">%1$s</xliff:g> is nu beter beveiligd"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"De verbinding van de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-sim is nu beter beveiligd"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Verbonden met een niet-versleuteld netwerk"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Gesprekken, berichten en gegevens zijn op dit moment kwetsbaarder tijdens het gebruik van je simkaart van <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Gesprekken, berichten en gegevens zijn op dit moment kwetsbaarder tijdens het gebruik van je <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-sim"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Gesprekken, berichten en gegevens zijn op dit moment kwetsbaarder tijdens het gebruik van je simkaart van <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nAls je verbinding weer versleuteld is, krijg je opnieuw een melding."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Instellingen voor beveiliging van mobiel netwerk"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Meer informatie"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Berichten openen"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe het werkt"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"In behandeling…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index e6fa56248b0f..c00998acbe16 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -156,7 +156,7 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"ମୋବାଇଲ ନେଟୱାର୍କ ସୁରକ୍ଷା"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ଏନକ୍ରିପସନ, ଏନକ୍ରିପ୍ଟ କରାଯାଇନଥିବା ନେଟୱାର୍କ ପାଇଁ ବିଜ୍ଞପ୍ତି"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ଡିଭାଇସ ID ଆକ୍ସେସ କରାଯାଇଛି"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"ଆପଣଙ୍କ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ରେକର୍ଡ କରିବା ସମୟରେ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ରେ ଏକ ଆଖପାଖର ନେଟୱାର୍କ ଆପଣଙ୍କ ଡିଭାଇସର ସ୍ୱତନ୍ତ୍ର ID (IMSI କିମ୍ବା IMEI) ରେକର୍ଡ କରିଛି"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"ଆପଣଙ୍କ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ବ୍ୟବହାର କରିବା ସମୟରେ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ରେ ଏକ ଆଖପାଖର ନେଟୱାର୍କ ଆପଣଙ୍କ ଡିଭାଇସର ସ୍ୱତନ୍ତ୍ର ID (IMSI କିମ୍ବା IMEI) ରେକର୍ଡ କରିଛି"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"ଆପଣଙ୍କ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ବ୍ୟବହାର କରିବା ସମୟରେ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ରେ ଏକ ଆଖପାଖର ନେଟୱାର୍କ ଆପଣଙ୍କ ଡିଭାଇସର ସ୍ୱତନ୍ତ୍ର ID (IMSI କିମ୍ବା IMEI) ରେକର୍ଡ କରିଛି। \n\nଏହାର ଅର୍ଥ ହେଉଛି ଯେ ଆପଣଙ୍କ ଲୋକେସନ, କାର୍ଯ୍ୟକଳାପ କିମ୍ବା ପରିଚୟକୁ ଲଗ କରାଯାଇଛି। ଏହା ସାଧାରଣ ଅଭ୍ୟାସ ଅଟେ କିନ୍ତୁ ଗୋପନୀୟତା ବିଷୟରେ ଚିନ୍ତିତ ଲୋକମାନଙ୍କ ପାଇଁ ଏହା ଏକ ସମସ୍ୟା ହୋଇପାରେ।"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ଏନକ୍ରିପ୍ଟ କରାଯାଇଥିବା ନେଟୱାର୍କ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ସହ କନେକ୍ଟ କରାଯାଇଛି"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM କନେକ୍ସନ ବର୍ତ୍ତମାନ ଅଧିକ ସୁରକ୍ଷିତ ଅଟେ"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ଖୋଲନ୍ତୁ"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ଏହା କିପରି କାମ କରେ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ବାକି ଅଛି…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index e2586e6a9b78..77020acff650 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -161,8 +161,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ਇਨਕ੍ਰਿਪਟਡ ਨੈੱਟਵਰਕ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਕਨੈਕਸ਼ਨ ਹੁਣ ਜ਼ਿਆਦਾ ਸੁਰੱਖਿਅਤ ਹੈ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"ਇਨਕ੍ਰਿਪਟਡ ਨੈੱਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟਾ ਜ਼ਿਆਦਾ ਖਤਰੇ ਵਿੱਚ ਹਨ"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟਾ ਜ਼ਿਆਦਾ ਖਤਰੇ ਵਿੱਚ ਹਨ।\n\nਤੁਹਾਡਾ ਕਨੈਕਸ਼ਨ ਦੁਬਾਰਾ ਇਨਕ੍ਰਿਪਟਡ ਹੋਣ \'ਤੇ, ਤੁਹਾਨੂੰ ਹੋਰ ਸੂਚਨਾ ਪ੍ਰਾਪਤ ਹੋਵੇਗੀ।"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟੇ ਤੱਕ ਪਹੁੰਚ ਕੀਤੇ ਜਾਣ ਦਾ ਖਤਰਾ ਰਹਿੰਦਾ ਹੈ"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟੇ ਤੱਕ ਪਹੁੰਚ ਕੀਤੇ ਜਾਣ ਦਾ ਖਤਰਾ ਰਹਿੰਦਾ ਹੈ।\n\nਤੁਹਾਡਾ ਕਨੈਕਸ਼ਨ ਦੁਬਾਰਾ ਇਨਕ੍ਰਿਪਟਡ ਹੋਣ \'ਤੇ, ਤੁਹਾਨੂੰ ਹੋਰ ਸੂਚਨਾ ਪ੍ਰਾਪਤ ਹੋਵੇਗੀ।"</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਸੁਰੱਖਿਆ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ਹੋਰ ਜਾਣੋ"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ਸਮਝ ਲਿਆ"</string> @@ -316,7 +316,7 @@ <string name="managed_profile_app_label" msgid="367401088383965725">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਦੇ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਜਾਓ"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"ਸੰਪਰਕ"</string> <string name="permgroupdesc_contacts" msgid="9163927941244182567">"ਆਪਣੇ ਸੰਪਰਕਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ"</string> - <string name="permgrouplab_location" msgid="1858277002233964394">"ਟਿਕਾਣਾ"</string> + <string name="permgrouplab_location" msgid="1858277002233964394">"ਟਿਕਾਣੇ ਦੀ ਜਾਣਕਾਰੀ"</string> <string name="permgroupdesc_location" msgid="1995955142118450685">"ਇਸ ਡੀਵਾਈਸ ਦੇ ਨਿਰਧਾਰਤ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚੋ"</string> <string name="permgrouplab_calendar" msgid="6426860926123033230">"ਕੈਲੰਡਰ"</string> <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ਤੁਹਾਡੇ ਕੈਲੰਡਰ ਤੱਕ ਪਹੁੰਚ ਕਰਨ"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ਐਪ ਖੋਲ੍ਹੋ"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ਇਹ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ਵਿਚਾਰ-ਅਧੀਨ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index d5c8d763bf0e..0971ae40d500 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -161,9 +161,9 @@ <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"O godzinie <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> pobliska sieć zarejestrowała unikalny identyfikator Twojego urządzenia (IMSI lub IMEI) podczas korzystania z karty SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"O godzinie <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> pobliska sieć zarejestrowała unikalny identyfikator Twojego urządzenia (IMSI lub IMEI) podczas korzystania z karty SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nOznacza to, że zarejestrowano Twoją lokalizację, aktywność lub tożsamość. Jest to powszechna praktyka, ale może stanowić problem dla osób dbających o prywatność."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Połączono z szyfrowaną siecią <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Połączenie SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> jest teraz bezpieczniejsze"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Połączenie przy użyciu karty SIM w sieci <xliff:g id="NETWORK_NAME">%1$s</xliff:g> jest teraz bezpieczniejsze"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Połączono z niezaszyfrowaną siecią"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Połączenia, wiadomości i dane są obecnie bardziej podatne na ataki podczas korzystania z karty SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Gdy korzystasz z karty SIM w sieci <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, połączenia, wiadomości i dane są bardziej narażone na ataki"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Połączenia, wiadomości i dane są obecnie bardziej podatne na ataki podczas korzystania z karty SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nGdy połączenie zostanie ponownie zaszyfrowane, otrzymasz kolejne powiadomienie."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Ustawienia bezpieczeństwa sieci komórkowej"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Więcej informacji"</string> @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otwórz Wiadomości"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to działa"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Oczekiwanie…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 4d9e208233a8..b04cd4c6ed98 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -164,7 +164,7 @@ <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectado à rede não criptografada"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Suas ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nQuando a conexão for criptografada novamente, você vai receber outra notificação."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança de rede móvel"</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança da rede móvel"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saiba mais"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendi"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de recurso concluído."</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 2074d1e85868..90f9eda9c32e 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1389,7 +1389,7 @@ <string name="no_permissions" msgid="5729199278862516390">"Não são necessárias permissões"</string> <string name="perm_costs_money" msgid="749054595022779685">"isto poderá estar sujeito a custos"</string> <string name="dlg_ok" msgid="5103447663504839312">"OK"</string> - <string name="usb_charging_notification_title" msgid="1674124518282666955">"O dispositivo está a carregar por USB"</string> + <string name="usb_charging_notification_title" msgid="1674124518282666955">"O dispositivo está a ser carregado por USB"</string> <string name="usb_supplying_notification_title" msgid="5378546632408101811">"A carregar o dispositivo ligado por USB"</string> <string name="usb_mtp_notification_title" msgid="1065989144124499810">"A transferência de ficheiros por USB está ativada"</string> <string name="usb_ptp_notification_title" msgid="5043437571863443281">"O PTP por USB está ativado"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre a app Mensagens"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 4d9e208233a8..b04cd4c6ed98 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -164,7 +164,7 @@ <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectado à rede não criptografada"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Suas ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nQuando a conexão for criptografada novamente, você vai receber outra notificação."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança de rede móvel"</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança da rede móvel"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saiba mais"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendi"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de recurso concluído."</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 10aa0207bf85..c43df4fa6e41 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Deschide Mesaje"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cum funcționează"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"În așteptare..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index ff0d1b8818ca..32a2338b284d 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -158,11 +158,11 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Безопасность мобильной сети"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрование, уведомления о незашифрованных сетях"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Получен доступ к идентификатору устройства"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> при использовании SIM-карты (<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>) в сети поблизости был записан уникальный идентификатор вашего устройства (IMSI или IMEI)."</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> при использовании SIM-карты (<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>) в сети поблизости был записан уникальный идентификатор вашего устройства (IMSI или IMEI).\n\nВаше местоположение, действия или личность были зарегистрированы. Хотя в этом нет ничего необычного, раскрытие данных может доставлять проблемы людям, которые беспокоятся о своей конфиденциальности."</string> - <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Подключение к зашифрованной сети \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\""</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Теперь подключение SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) более безопасное."</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Подключение к незашифрованной сети"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, когда вы использовали SIM-карту \"<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>\", сетью поблизости от вас был записан уникальный идентификатор вашего устройства (IMSI или IMEI)."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, когда вы использовали SIM-карту \"<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>\", сетью поблизости от вас был записан уникальный идентификатор вашего устройства (IMSI или IMEI).\n\nЭто значит, что было зарегистрировано ваше местоположение, действия или личность. Это распространенная практика, однако она может вызывать беспокойство у тех, кто заботится о своей конфиденциальности."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Подключено к зашифрованной сети <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Теперь подключение SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) лучше защищено."</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Подключено к незашифрованной сети"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Сейчас звонки, сообщения и данные более уязвимы при использовании SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>)."</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Сейчас звонки, сообщения и данные более уязвимы при использовании SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>).\n\nКогда соединение будет снова зашифровано, вы получите ещё одно уведомление."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Настройки безопасности мобильной сети"</string> @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Открыть Сообщения"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Узнать принцип работы"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Обработка…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 18b26f27816b..9539cef0f26c 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages විවෘත කරන්න"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"එය ක්රියා කරන ආකාරය"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"පොරොත්තුයි..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 3b144d27905f..c72a426d0436 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -163,8 +163,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Pripojené k šifrovanej sieti <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Pripojenie SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g> je teraz zabezpečenejšie"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Pripojené k nešifrovanej sieti"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Hovory, správy a údaje sú momentálne zraniteľnejšie vzhľadom na nedostatok zabezpečenia pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Hovory, správy a údaje sú momentálne zraniteľnejšie vzhľadom na nedostatok zabezpečenia pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKeď bude vaše pripojenie znova šifrované, dostanete ďalšie upozornenie."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g> sú vaše hovory, správy a údaje zraniteľnejšie"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nsú vaše hovory, správy a údaje zraniteľnejšie. Keď bude vaše pripojenie znova šifrované, dostanete ďalšie upozornenie."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Nastavenia zabezpečenia mobilnej siete"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Ďalšie informácie"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Dobre"</string> @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvoriť Správy"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ako to funguje"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Nespracovaná…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 1b7a2a145f80..e1be803cce1b 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Odpri Sporočila"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako deluje"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"V teku …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 142ab0d663d6..4d79ce707787 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -157,12 +157,12 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Enkriptimi, njoftimet për rrjetet e paenkriptuara"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Pati qasje tek ID-ja e pajisjes"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Në <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, një rrjet në afërsi ka regjistruar ID-në unike (IMSI ose IMEI) të pajisjes sate gjatë përdorimit të kartës sate SIM të <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Në <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, një rrjet në afërsi ka regjistruar ID-në unike (IMSI ose IMEI) të pajisjes sate gjatë përdorimit të kartës sate SIM të <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nKjo do të thotë që vendndodhja, aktiviteti ose identiteti yt janë regjistruar. Kjo është një praktikë të zakonshme, por mund të jetë problem për personat që janë të shqetësuar në lidhje me privatësinë."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Në <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, një rrjet në afërsi ka regjistruar ID-në unike (IMSI ose IMEI) të pajisjes sate gjatë përdorimit të kartës sate SIM të <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nKjo do të thotë që vendndodhja, aktiviteti ose identiteti yt janë regjistruar. Kjo është një praktikë e zakonshme, por mund të jetë problem për personat që janë të shqetësuar në lidhje me privatësinë."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Lidhur me rrjetin e enkriptuar <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Lidhja e kartës SIM të <xliff:g id="NETWORK_NAME">%1$s</xliff:g> është më e sigurt tani"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Lidhur me një rrjet të paenkriptuar"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme ndërkohë që përdoret karta jote SIM të <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme ndërkohë që përdoret karta jote SIM të <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKur lidhja jote të jetë përsëri e enkriptuar, do të marrësh një njoftim tjetër."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme teksa përdoret karta SIM e <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme teksa përdoret karta SIM e <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKur lidhja jote të jetë përsëri e enkriptuar, do të marrësh një njoftim tjetër."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Cilësimet e sigurisë së rrjetit celular"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Mëso më shumë"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"E kuptova"</string> @@ -2144,10 +2144,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Njoftimi i informacionit të \"Modalitetit rutinë\""</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"\"Kursyesi i baterisë\" u aktivizua"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Po reduktohet përdorimi i baterisë për të rritur kohëzgjatjen e baterisë"</string> - <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) --> - <skip /> - <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) --> - <skip /> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"\"Kursyesi i baterisë\" është aktiv"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"\"Kursyesi i baterisë\" është aktivizuar për të zgjatur kohëzgjatjen e baterisë"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Kursyesi i baterisë"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"\"Kursyesi i baterisë\" është çaktivizuar"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefoni ka nivel të mjaftueshëm baterie. Funksionet nuk janë më të kufizuara."</string> @@ -2415,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Hap \"Mesazhet\""</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Si funksionon"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Në pritje..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 87d10860a0b0..95ccead79ce2 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -161,7 +161,7 @@ <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Мрежа у близини је у <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> евидентирала јединствени ИД вашег уређаја (IMSI или IMEI) док сте користили <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nТо значи да је евидентирала вашу локацију, активност и идентитет. То је уобичајена пракса, али може да буде проблем људима који су забринути за приватност."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Повезани сте на шифровану мрежу <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Веза <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-а је сада безбеднија"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Повезани сте на шифровану мрежу"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Повезани сте на нешифровану мрежу"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Позиви, поруке и подаци су тренутно рањивији док користите <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Позиви, поруке и подаци су тренутно рањивији док користите <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nКада веза поново буде шифрована, послаћемо вам друго обавештење."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Подешавања безбедности на мобилној мрежи"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отвори Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Принцип рада"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"На чекању..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 3ca5c9fcb4ab..0cbdda1a4c27 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Öppna Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Så fungerar det"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Väntar …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 2efca2b4a891..10f55af3dc47 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -161,8 +161,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Imeunganishwa kwenye mtandao uliosimbwa kwa njia fiche wa <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Sasa muunganisho wa SIM wa <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ni salama zaidi"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Imeunganishwa kwenye mtandao ambao haujasimbwa kwa njia fiche"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kwa sasa maudhui ya simu, ujumbe na data yako katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Kwa sasa maudhui ya simu, ujumbe na data yako katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nMuunganisho wako ukisimbwa kwa njia fiche tena, utapata arifa nyingine."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kwa sasa maudhui ya simu, ujumbe na data ipo katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Kwa sasa maudhui ya simu, ujumbe na data ipo katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nMuunganisho wako ukisimbwa kwa njia fiche tena, utapata arifa nyingine."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mipangilio ya usalama wa mtandao wa simu"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Pata maelezo zaidi"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Nimeelewa"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Fungua Programu ya Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Utaratibu wake"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Inashughulikiwa..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 0c704e353836..466e29a7e9b1 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ஆப்ஸைத் திறக்கவும்"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"இது செயல்படும் விதம்"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"நிலுவையிலுள்ளது..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index b465000e9690..db75aac8b027 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -156,7 +156,7 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"మొబైల్ నెట్వర్క్ సెక్యూరిటీ"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ఎన్క్రిప్షన్, ఎన్క్రిప్ట్ చేయని నెట్వర్క్లకు సంబంధించిన నోటిఫికేషన్లు"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"పరికర IDని యాక్సెస్ చేశారు"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"మీ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIMను ఉపయోగిస్తున్నప్పుడు, సమీపంలోని నెట్వర్క్ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> సమయానికి మీ పరికర యూనిక్ ID (IMSI లేదా IMEI)ని రికార్డ్ చేసింది"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"మీ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIMను వాడేటప్పుడు, <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>కి దగ్గర్లోని ఒక నెట్వర్క్, మీ పరికర యూనిక్ IDని (IMSI లేదా IMEI) రికార్డ్ చేసింది"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"మీ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIMను ఉపయోగిస్తున్నప్పుడు, సమీపంలోని నెట్వర్క్ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> సమయానికి మీ పరికర యూనిక్ ID (IMSI లేదా IMEI)ని రికార్డ్ చేసింది.\n\nమీ లొకేషన్, యాక్టివిటీ, లేదా గుర్తింపు లాగ్ అయ్యాయని దీని అర్థం. దీనిని తరచుగా అమలు చేస్తుంటారు, కానీ గోప్యత గురించి ఆందోళనపడే వ్యక్తులకు ఇది సమస్య కావచ్చు,"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ఎన్క్రిప్ట్ చేసిన నెట్వర్క్ <xliff:g id="NETWORK_NAME">%1$s</xliff:g>కు కనెక్ట్ అయింది"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM కనెక్షన్ ఇప్పుడు మరింత సురక్షితంగా ఉంది"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messagesను తెరవండి"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ఇది ఎలా పని చేస్తుంది"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"పెండింగ్లో ఉంది..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 535396de7936..284a5f273f22 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1619,7 +1619,7 @@ <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"เกินปริมาณเน็ตมือถือที่กำหนดไว้"</string> <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"เกินขีดจำกัดของข้อมูล Wi-Fi"</string> <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"คุณใช้อินเทอร์เน็ตเกินไป <xliff:g id="SIZE">%s</xliff:g> จากปริมาณที่กำหนดไว้"</string> - <string name="data_usage_restricted_title" msgid="126711424380051268">"จำกัดอินเทอร์เน็ตที่ใช้งานอยู่เบื้องหลัง"</string> + <string name="data_usage_restricted_title" msgid="126711424380051268">"จำกัดข้อมูลในเบื้องหลัง"</string> <string name="data_usage_restricted_body" msgid="5338694433686077733">"แตะเพื่อนำข้อจำกัดออก"</string> <string name="data_usage_rapid_title" msgid="2950192123248740375">"ปริมาณการใช้เน็ตมือถือสูง"</string> <string name="data_usage_rapid_body" msgid="3886676853263693432">"แอปของคุณใช้อินเทอร์เน็ตมากกว่าปกติ"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"เปิด Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"วิธีการทำงาน"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"รอดำเนินการ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 6eab396ca97c..4dd488681f5b 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -161,7 +161,7 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Nakakonekta sa naka-encrypt na network na <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Mas secure na ang koneksyon ng <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Nakakonekta sa hindi naka-encrypt na network"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kasalukuyang mas nanganganib ang mga tawag, mensahe, at data habang ginagamit ang iyong <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kasalukuyang walang proteksyon ang tawag, mensahe, at data habang gamit ang <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM mo"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Kasalukuyang mas nanganganib ang mga tawag, mensahe, at data habang ginagamit ang iyong <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nKapag na-encrypt ulit ang iyong koneksyon, makakatanggap ka ng isa pang notification."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mga setting ng seguridad ng mobile network"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Matuto pa"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buksan ang Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Paano ito gumagana"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Nakabinbin..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 4c8083a754a5..2b616defdaa6 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajlar\'ı aç"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"İşleyiş şekli"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Bekliyor..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 6f94a2cf4e51..850e21b15ef9 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -2146,10 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Сповіщення про програму"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Режим енергозбереження ввімкнено"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Заряд використовується економно, щоб подовжити час роботи акумулятора"</string> - <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) --> - <skip /> - <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) --> - <skip /> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Режим енергозбереження ввімкнено"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Увімкнено режим енергозбереження, щоб збільшити час роботи акумулятора"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Режим енергозбереження"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Режим енергозбереження вимкнено."</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Телефон має достатньо заряду акумулятора. Функції вже не обмежено."</string> @@ -2417,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Відкрийте Повідомлення"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як це працює"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Обробка…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index ed1a358d19a8..d3fde3c4f455 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -157,7 +157,7 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"غیر مرموز کردہ نیٹ ورکس کے لیے مرموز کاری، اطلاعات"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"آلہ ID تک رسائی کی گئی"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> میں، قریبی نیٹ ورک نے آپ کے <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM کا استعمال کرتے ہوئے آپ کے آلے کی منفرد ID (IMSI یا IMEI) ریکارڈ کی"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> میں، آپ کے <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM استعمال کرتے ہوئے قریبی نیٹ ورک نے آپ کے آلے کی منفرد ID (IMSI یا IMEI) کو ریکارڈ کیا۔\n\nاس کا مطلب ہے کہ آپ کا مقام، سرگرمی یا شناخت لاگ ان ہو چکی ہیں۔ یہ عام بات ہے لیکن رازداری کے بارے میں فکر مند لوگوں کے لیے ایک مسئلہ ہو سکتا ہے۔"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> میں، آپ کے <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM استعمال کرتے ہوئے قریبی نیٹ ورک نے آپ کے آلے کی منفرد ID (IMSI یا IMEI) کو ریکارڈ کیا۔\n\nاس کا مطلب ہے کہ آپ کا مقام، سرگرمی یا شناخت کو ریکارڈ کیا جا چکا ہے۔ یہ عام بات ہے لیکن رازداری کے بارے میں فکر مند لوگوں کے لیے ایک مسئلہ ہو سکتا ہے۔"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"مرموز کردہ نیٹ ورک <xliff:g id="NETWORK_NAME">%1$s</xliff:g> سے منسلک ہے"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM کا کنکشن اب بہت محفوظ ہے"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"غیر مرموز کردہ نیٹ ورک سے منسلک ہے"</string> @@ -644,7 +644,7 @@ <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"جاری رکھنے کے لیے اپنے بایو میٹرک اور اسکرین لاک کا استعمال کریں"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"بایومیٹرک ہارڈ ویئر دستیاب نہیں ہے"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"تصدیق کا عمل منسوخ ہو گیا"</string> - <string name="biometric_not_recognized" msgid="5106687642694635888">"تسلیم شدہ نہیں ہے"</string> + <string name="biometric_not_recognized" msgid="5106687642694635888">"شناخت نہیں ہو سکی"</string> <string name="biometric_face_not_recognized" msgid="5535599455744525200">"چہرے کی شناخت نہیں ہو سکی"</string> <string name="biometric_error_canceled" msgid="8266582404844179778">"تصدیق کا عمل منسوخ ہو گیا"</string> <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"کوئی پن، پیٹرن، یا پاس ورڈ سیٹ نہیں ہے"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"پیغامات ایپ کو کھولیں"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"اس کے کام کرنے کا طریقہ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"زیر التواء..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 4ddd368766c8..c143244db230 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Xabarlar ilovasini ochish"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ishlash tartibi"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Kutilmoqda..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index c633c2af0a15..a0775d400d5d 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -155,15 +155,15 @@ <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Không được chuyển tiếp"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Bảo mật mạng di động"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Mã hoá, thông báo cho mạng chưa được mã hoá"</string> - <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Mã thiết bị được truy cập"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Mã thiết bị đã bị truy cập"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Vào <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, một mạng lân cận đã ghi nhận mã nhận dạng duy nhất của thiết bị của bạn (IMSI hoặc IMEI) trong lúc sử dụng SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> của bạn"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Vào <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, một mạng lân cận đã ghi nhận mã nhận dạng duy nhất của thiết bị của bạn (IMSI hoặc IMEI) trong lúc sử dụng SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> của bạn.\n\nTức là vị trí, hoạt động hoặc danh tính của bạn đã được ghi lại. Đây là một phương thức thông dụng nhưng có thể trở thành vấn đề đối với những ai lo ngại về quyền riêng tư."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Vào <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, một mạng lân cận đã ghi lại mã nhận dạng duy nhất của thiết bị của bạn (IMSI hoặc IMEI) trong lúc sử dụng SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> của bạn.\n\nTức là vị trí, hoạt động hoặc danh tính của bạn đã được ghi lại. Đây là một phương thức thông dụng nhưng có thể trở thành vấn đề đối với những ai lo ngại về quyền riêng tư."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Đã kết nối với mạng <xliff:g id="NETWORK_NAME">%1$s</xliff:g> đã mã hoá"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Kết nối SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> nay an toàn hơn"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Đã kết nối với mạng chưa được mã hoá"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Cuộc gọi, tin nhắn và dữ liệu hiện dễ bị tấn công trong lúc sử dụng SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> của bạn."</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Cuộc gọi, tin nhắn và dữ liệu hiện dễ bị tấn công trong lúc sử dụng SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> của bạn.\n\nKhi kết nối của bạn được mã hoá lại, bạn sẽ nhận được một thông báo khác."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Chế độ bảo mật mạng di động"</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Cài đặt an ninh mạng di động"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Tìm hiểu thêm"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Tôi hiểu"</string> <string name="fcComplete" msgid="1080909484660507044">"Mã tính năng đã hoàn tất."</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mở ứng dụng Tin nhắn"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cách hoạt động"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Đang chờ xử lý..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-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 f16881a847c3..00dd3db2b35a 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -154,12 +154,12 @@ <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"移动网络安全"</string> - <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"加密,连接到未加密网络时通知"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"加密、通知(连接到未加密网络时)"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"有网络获取了设备 ID"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>,当您使用<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM 卡时,一个附近的网络记录了您设备的唯一 ID(IMSI 或 IMEI)"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>,当您使用<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM 卡时,一个附近的网络记录了您设备的唯一 ID(IMSI 或 IMEI)。\n\n这意味着您的位置信息、活动或身份信息都被记录了。这是常见做法,但对注重隐私的人来说可能是一个问题。"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"连接到了名为“<xliff:g id="NETWORK_NAME">%1$s</xliff:g>”的加密网络"</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡连接现在更安全了"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡的连接现在更安全了"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"连接到了一个未加密的网络"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"目前,当您使用<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡时,通话、消息和数据更易受到攻击"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"目前,当您使用<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡时,通话、消息和数据更易受到攻击。\n\n当您的连接再次加密时,您会另收到一条通知。"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"打开“信息”应用"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"运作方式"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"待归档…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index efa260f3f4e6..142940a86ebb 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"待處理…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 0c18193fc542..b7ee23c23f21 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -2144,10 +2144,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"日常安排模式資訊通知"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"已開啟省電模式"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"降低電池用量,以便延長電池續航力"</string> - <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) --> - <skip /> - <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) --> - <skip /> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"省電模式已開啟"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"已開啟省電模式,延長電池續航力"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"省電模式"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"省電模式已關閉"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"手機電力充足,各項功能不再受到限制。"</string> @@ -2415,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」應用程式"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"待處理…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 7dc3fd63719e..a967fb6f1ad1 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Vula Imilayezo"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Indlela esebenza ngayo"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ilindile..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 37d39a752c65..5fa13bab7b0c 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> <!-- **************************************************************** --> 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 cefc648e45a5..5bd20332f381 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4444,8 +4444,8 @@ <!-- True if camera app should be pinned via Pinner Service --> <bool name="config_pinnerCameraApp">false</bool> - <!-- True if home app should be pinned via Pinner Service --> - <bool name="config_pinnerHomeApp">false</bool> + <!-- Bytes that the PinnerService will pin for Home app --> + <integer name="config_pinnerHomePinBytes">0</integer> <!-- True if assistant app should be pinned via Pinner Service --> <bool name="config_pinnerAssistantApp">false</bool> @@ -4652,6 +4652,19 @@ --> <string-array name="config_companionDeviceCerts" translatable="false"></string-array> + <!-- A list of packages that auto-enable permissions sync feature. + Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts + are parallel arrays. + --> + <string-array name="config_companionPermSyncEnabledPackages" translatable="false"></string-array> + + <!-- A list of SHA256 Certificates corresponding to config_companionPermSyncEnabledPackages. + Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts + are parallel arrays. + Example: "1A:2B:3C:4D" + --> + <string-array name="config_companionPermSyncEnabledCerts" translatable="false"></string-array> + <!-- The package name for the default wellbeing app. This package must be trusted, as it has the permissions to control other applications on the device. @@ -4704,6 +4717,13 @@ <!-- The component name for the default system on-device sandboxed inference service. --> <string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string> + <!-- The broadcast intent name for notifying when the on-device model is loading --> + <string name="config_onDeviceIntelligenceModelLoadedBroadcastKey" translatable="false"></string> + + <!-- The broadcast intent name for notifying when the on-device model has been unloaded --> + <string name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" translatable="false"></string> + + <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for wearable sensing. --> <string translatable="false" name="config_defaultWearableSensingConsentComponent"></string> diff --git a/core/res/res/values/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/strings.xml b/core/res/res/values/strings.xml index 59e4161b2e0b..28678c1c5fb0 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> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d058fb188d16..acd3b372b3ae 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" /> @@ -2351,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" /> @@ -3437,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" /> @@ -3626,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" /> @@ -3941,6 +3945,8 @@ <java-symbol type="string" name="config_defaultWearableSensingService" /> <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" /> <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" /> + <java-symbol type="string" name="config_onDeviceIntelligenceModelLoadedBroadcastKey" /> + <java-symbol type="string" name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" /> <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> @@ -4518,6 +4524,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" /> @@ -5236,9 +5243,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" /> @@ -5288,6 +5293,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" /> 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/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/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/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 57bbb1cc9b57..5917cc181b47 100644 --- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java +++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java @@ -37,6 +37,7 @@ import static org.testng.Assert.assertEquals; import android.content.Context; import android.graphics.Insets; import android.platform.test.annotations.Presubmit; +import android.util.SparseArray; import android.view.animation.BackGestureInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputMethodManager; @@ -54,6 +55,8 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.lang.reflect.Field; + /** * Tests for {@link ImeBackAnimationController}. * @@ -104,6 +107,13 @@ public class ImeBackAnimationControllerTest { when(mInsetsController.getHost()).thenReturn(mViewRootInsetsControllerHost); when(mViewRootInsetsControllerHost.getInputMethodManager()).thenReturn( inputMethodManager); + try { + Field field = InsetsController.class.getDeclaredField("mSourceConsumers"); + field.setAccessible(true); + field.set(mInsetsController, new SparseArray<InsetsSourceConsumer>()); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Unable to set mSourceConsumers", e); + } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } 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/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/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/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 c86b744dbafa..88f0e8ef8a94 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -219,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 318aadda63fe..2893177aafc5 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -789,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/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/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java index 0c6d4bd5da48..d8cf21e72441 100644 --- a/graphics/java/android/graphics/text/LineBreaker.java +++ b/graphics/java/android/graphics/text/LineBreaker.java @@ -376,8 +376,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 diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java index f8328b13ca1f..671eb6e514c5 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -139,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) { 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..1fbaeeac8608 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -35,6 +35,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI import android.annotation.DimenRes; import android.annotation.Nullable; +import android.app.Activity; import android.app.ActivityThread; import android.content.Context; import android.content.pm.ActivityInfo; @@ -42,6 +43,7 @@ import android.content.res.Configuration; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.RotateDrawable; import android.hardware.display.DisplayManager; @@ -213,7 +215,11 @@ class DividerPresenter implements View.OnTouchListener { isVerticalSplit, isReversedLayout, parentInfo.getDisplayId(), - isDraggableExpandType + isDraggableExpandType, + getContainerBackgroundColor(topSplitContainer.getPrimaryContainer(), + DEFAULT_PRIMARY_VEIL_COLOR), + getContainerBackgroundColor(topSplitContainer.getSecondaryContainer(), + DEFAULT_SECONDARY_VEIL_COLOR) )); } } @@ -242,6 +248,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. * @@ -439,10 +468,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; @@ -492,8 +517,11 @@ class DividerPresenter implements View.OnTouchListener { private void onStartDragging() { 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. @@ -509,18 +537,18 @@ class DividerPresenter implements View.OnTouchListener { @GuardedBy("mLock") private void onDrag() { - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mRenderer.updateSurface(t); - t.apply(); + mRenderer.updateSurface(); } @GuardedBy("mLock") private void onFinishDragging() { mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition); mRenderer.setDividerPosition(mDividerPosition); + 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.hideVeils(t); // Callbacks must be executed on the executor to release mLock and prevent deadlocks. @@ -800,6 +828,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 +840,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 +851,8 @@ class DividerPresenter implements View.OnTouchListener { mIsReversedLayout = isReversedLayout; mDisplayId = displayId; mIsDraggableExpandType = isDraggableExpandType; + mPrimaryVeilColor = primaryVeilColor; + mSecondaryVeilColor = secondaryVeilColor; } /** @@ -840,7 +874,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 +913,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 +945,7 @@ class DividerPresenter implements View.OnTouchListener { context, displayManager.getDisplay(mProperties.mDisplayId), mWindowlessWindowManager, "DividerContainer"); mDividerLayout = new FrameLayout(context); + mDividerLine = new View(context); update(); } @@ -921,6 +962,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 +999,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, mDividerPosition); - t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerWidthPx); + t.setPosition(mDividerSurface, 0.0f, dividerSurfacePosition); + t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerSurfaceWidthPx); } + + // Update divider line position in the surface + if (!mProperties.mIsReversedLayout) { + final int offset = mDividerPosition - dividerSurfacePosition; + mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0); + mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset); + } else { + // For reversed layout, the divider line is always at the start of the divider + // surface. + mDividerLine.setX(0); + mDividerLine.setY(0); + } + if (mIsDragging) { updateVeils(t); } @@ -971,14 +1071,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 +1095,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 +1134,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 +1194,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 14388a6d42ff..13c2d1f73461 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -350,8 +350,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(); @@ -855,7 +854,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!parentInfo.isVisible()) { // Only making the TaskContainer invisible and drops the other info, and perform the // update when the next time the Task becomes visible. - taskContainer.setIsVisible(false); + if (taskContainer.isVisible()) { + taskContainer.setInvisible(); + } return; } @@ -1076,8 +1077,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; } @@ -1533,8 +1533,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)) { @@ -3228,10 +3227,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId()); final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo(); - if (parentInfo != null) { - dividerPresenter.updateDivider( - wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer()); - } + dividerPresenter.updateDivider( + wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer()); } @Override diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/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 a68373832a14..c708da97d908 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -78,16 +78,7 @@ class TaskContainer { private TaskFragmentContainer mAlwaysOnTopOverlayContainer; @NonNull - private final Configuration mConfiguration; - - private int mDisplayId; - - private boolean mIsVisible; - - private boolean mHasDirectActivity; - - @Nullable - private TaskFragmentParentInfo mTaskFragmentParentInfo; + private TaskFragmentParentInfo mInfo; /** * TaskFragments that the organizer has requested to be closed. They should be removed when @@ -131,12 +122,14 @@ class TaskContainer { mTaskId = taskId; final TaskProperties taskProperties = TaskProperties .getTaskPropertiesFromActivity(activityInTask); - mConfiguration = taskProperties.getConfiguration(); - mDisplayId = taskProperties.getDisplayId(); - // Note that it is always called when there's a new Activity is started, which implies - // the host task is visible and has an activity in the task. - mIsVisible = true; - mHasDirectActivity = true; + mInfo = new TaskFragmentParentInfo( + taskProperties.getConfiguration(), + taskProperties.getDisplayId(), + // Note that it is always called when there's a new Activity is started, which + // implies the host task is visible and has an activity in the task. + true /* visible */, + true /* hasDirectActivity */, + null /* decorSurface */); } int getTaskId() { @@ -144,43 +137,39 @@ class TaskContainer { } int getDisplayId() { - return mDisplayId; + return mInfo.getDisplayId(); } boolean isVisible() { - return mIsVisible; + return mInfo.isVisible(); } - void setIsVisible(boolean visible) { - mIsVisible = visible; + void setInvisible() { + mInfo = new TaskFragmentParentInfo(mInfo.getConfiguration(), mInfo.getDisplayId(), + false /* visible */, mInfo.hasDirectActivity(), mInfo.getDecorSurface()); } boolean hasDirectActivity() { - return mHasDirectActivity; + return mInfo.hasDirectActivity(); } @NonNull Rect getBounds() { - return mConfiguration.windowConfiguration.getBounds(); + return mInfo.getConfiguration().windowConfiguration.getBounds(); } @NonNull TaskProperties getTaskProperties() { - return new TaskProperties(mDisplayId, mConfiguration); + return new TaskProperties(mInfo.getDisplayId(), mInfo.getConfiguration()); } void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { - // TODO(b/293654166): cache the TaskFragmentParentInfo and remove these fields. - mConfiguration.setTo(info.getConfiguration()); - mDisplayId = info.getDisplayId(); - mIsVisible = info.isVisible(); - mHasDirectActivity = info.hasDirectActivity(); - mTaskFragmentParentInfo = info; + mInfo = info; } - @Nullable + @NonNull TaskFragmentParentInfo getTaskFragmentParentInfo() { - return mTaskFragmentParentInfo; + return mInfo; } /** @@ -196,8 +185,8 @@ class TaskContainer { // If the task properties equals regardless of starting position, don't // need to update the container. - return mConfiguration.diffPublicOnly(configuration) != 0 - || mDisplayId != info.getDisplayId(); + return mInfo.getConfiguration().diffPublicOnly(configuration) != 0 + || mInfo.getDisplayId() != info.getDisplayId(); } /** @@ -224,7 +213,7 @@ class TaskContainer { } boolean isInPictureInPicture() { - return isInPictureInPicture(mConfiguration); + return isInPictureInPicture(mInfo.getConfiguration()); } private static boolean isInPictureInPicture(@NonNull Configuration configuration) { @@ -237,7 +226,7 @@ class TaskContainer { @WindowingMode private int getWindowingMode() { - return mConfiguration.windowConfiguration.getWindowingMode(); + return mInfo.getConfiguration().windowConfiguration.getWindowingMode(); } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index c952dfea4de5..482554351b97 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -184,6 +184,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; @@ -893,6 +898,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; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index 56c3bce87d6e..339908a3a9a4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -16,16 +16,10 @@ package androidx.window.sidecar; -import static android.view.Display.DEFAULT_DISPLAY; - -import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; -import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; - +import android.annotation.Nullable; import android.app.Activity; -import android.app.ActivityThread; import android.app.Application; import android.content.Context; -import android.graphics.Rect; import android.hardware.devicestate.DeviceStateManager; import android.os.Bundle; import android.os.IBinder; @@ -38,7 +32,6 @@ import androidx.window.common.RawFoldingFeatureProducer; import androidx.window.util.BaseDataProducer; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -76,64 +69,13 @@ class SampleSidecarImpl extends StubSidecar { @NonNull @Override public SidecarDeviceState getDeviceState() { - SidecarDeviceState deviceState = new SidecarDeviceState(); - deviceState.posture = deviceStateFromFeature(); - return deviceState; - } - - private int deviceStateFromFeature() { - for (int i = 0; i < mStoredFeatures.size(); i++) { - CommonFoldingFeature feature = mStoredFeatures.get(i); - final int state = feature.getState(); - switch (state) { - case CommonFoldingFeature.COMMON_STATE_FLAT: - return SidecarDeviceState.POSTURE_OPENED; - case CommonFoldingFeature.COMMON_STATE_HALF_OPENED: - return SidecarDeviceState.POSTURE_HALF_OPENED; - case CommonFoldingFeature.COMMON_STATE_UNKNOWN: - return SidecarDeviceState.POSTURE_UNKNOWN; - } - } - return SidecarDeviceState.POSTURE_UNKNOWN; + return SidecarHelper.calculateDeviceState(mStoredFeatures); } @NonNull @Override public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) { - Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); - SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); - if (activity == null) { - return windowLayoutInfo; - } - windowLayoutInfo.displayFeatures = getDisplayFeatures(activity); - return windowLayoutInfo; - } - - private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { - int displayId = activity.getDisplay().getDisplayId(); - if (displayId != DEFAULT_DISPLAY) { - return Collections.emptyList(); - } - - if (activity.isInMultiWindowMode()) { - // It is recommended not to report any display features in multi-window mode, since it - // won't be possible to synchronize the display feature positions with window movement. - return Collections.emptyList(); - } - - List<SidecarDisplayFeature> features = new ArrayList<>(); - final int rotation = activity.getResources().getConfiguration().windowConfiguration - .getDisplayRotation(); - for (CommonFoldingFeature baseFeature : mStoredFeatures) { - SidecarDisplayFeature feature = new SidecarDisplayFeature(); - Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, rotation, featureRect); - transformToWindowSpaceRect(activity, featureRect); - feature.setRect(featureRect); - feature.setType(baseFeature.getType()); - features.add(feature); - } - return Collections.unmodifiableList(features); + return SidecarHelper.calculateWindowLayoutInfo(windowToken, mStoredFeatures); } @Override @@ -145,13 +87,14 @@ class SampleSidecarImpl extends StubSidecar { private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter { @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + public void onActivityCreated(@NonNull Activity activity, + @Nullable Bundle savedInstanceState) { super.onActivityCreated(activity, savedInstanceState); onDisplayFeaturesChangedForActivity(activity); } @Override - public void onActivityConfigurationChanged(Activity activity) { + public void onActivityConfigurationChanged(@NonNull Activity activity) { super.onActivityConfigurationChanged(activity); onDisplayFeaturesChangedForActivity(activity); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java new file mode 100644 index 000000000000..bb6ab47b144d --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.window.sidecar; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; +import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; + +import android.annotation.NonNull; +import android.app.Activity; +import android.app.ActivityThread; +import android.graphics.Rect; +import android.os.IBinder; + +import androidx.window.common.CommonFoldingFeature; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A utility class for transforming between Sidecar and Extensions features. + */ +class SidecarHelper { + + private SidecarHelper() {} + + /** + * Returns the {@link SidecarDeviceState} posture that is calculated for the first fold in + * the feature list. Sidecar devices only have one fold so we only pick the first one to + * determine the state. + * @param featureList the {@link CommonFoldingFeature} that are currently active. + * @return the {@link SidecarDeviceState} calculated from the {@link List} of + * {@link CommonFoldingFeature}. + */ + @SuppressWarnings("deprecation") + private static int deviceStateFromFeatureList(@NonNull List<CommonFoldingFeature> featureList) { + for (int i = 0; i < featureList.size(); i++) { + final CommonFoldingFeature feature = featureList.get(i); + final int state = feature.getState(); + switch (state) { + case CommonFoldingFeature.COMMON_STATE_FLAT: + return SidecarDeviceState.POSTURE_OPENED; + case CommonFoldingFeature.COMMON_STATE_HALF_OPENED: + return SidecarDeviceState.POSTURE_HALF_OPENED; + case CommonFoldingFeature.COMMON_STATE_UNKNOWN: + return SidecarDeviceState.POSTURE_UNKNOWN; + case CommonFoldingFeature.COMMON_STATE_NO_FOLDING_FEATURES: + return SidecarDeviceState.POSTURE_UNKNOWN; + case CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE: + return SidecarDeviceState.POSTURE_UNKNOWN; + } + } + return SidecarDeviceState.POSTURE_UNKNOWN; + } + + /** + * Returns a {@link SidecarDeviceState} calculated from a {@link List} of + * {@link CommonFoldingFeature}s. + */ + @SuppressWarnings("deprecation") + static SidecarDeviceState calculateDeviceState( + @NonNull List<CommonFoldingFeature> featureList) { + final SidecarDeviceState deviceState = new SidecarDeviceState(); + deviceState.posture = deviceStateFromFeatureList(featureList); + return deviceState; + } + + @SuppressWarnings("deprecation") + private static List<SidecarDisplayFeature> calculateDisplayFeatures( + @NonNull Activity activity, + @NonNull List<CommonFoldingFeature> featureList + ) { + final int displayId = activity.getDisplay().getDisplayId(); + if (displayId != DEFAULT_DISPLAY) { + return Collections.emptyList(); + } + + if (activity.isInMultiWindowMode()) { + // It is recommended not to report any display features in multi-window mode, since it + // won't be possible to synchronize the display feature positions with window movement. + return Collections.emptyList(); + } + + final List<SidecarDisplayFeature> features = new ArrayList<>(); + final int rotation = activity.getResources().getConfiguration().windowConfiguration + .getDisplayRotation(); + for (CommonFoldingFeature baseFeature : featureList) { + final SidecarDisplayFeature feature = new SidecarDisplayFeature(); + final Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, rotation, featureRect); + transformToWindowSpaceRect(activity, featureRect); + feature.setRect(featureRect); + feature.setType(baseFeature.getType()); + features.add(feature); + } + return Collections.unmodifiableList(features); + } + + /** + * Returns a {@link SidecarWindowLayoutInfo} calculated from the {@link List} of + * {@link CommonFoldingFeature}. + */ + @SuppressWarnings("deprecation") + static SidecarWindowLayoutInfo calculateWindowLayoutInfo(@NonNull IBinder windowToken, + @NonNull List<CommonFoldingFeature> featureList) { + final Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); + final SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); + if (activity == null) { + return windowLayoutInfo; + } + windowLayoutInfo.displayFeatures = calculateDisplayFeatures(activity, featureList); + return windowLayoutInfo; + } +} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index 8aca92e89e6b..b0a45e285896 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -35,8 +35,11 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Activity; import android.content.res.Configuration; +import android.graphics.Color; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.os.Binder; import android.os.IBinder; import android.platform.test.annotations.Presubmit; @@ -44,6 +47,8 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.view.Display; import android.view.MotionEvent; import android.view.SurfaceControl; +import android.view.View; +import android.view.Window; import android.window.TaskFragmentOperation; import android.window.TaskFragmentParentInfo; import android.window.WindowContainerTransaction; @@ -152,7 +157,10 @@ public class DividerPresenterTest { true /* isVerticalSplit */, false /* isReversedLayout */, Display.DEFAULT_DISPLAY, - false /* isDraggableExpandType */); + false /* isDraggableExpandType */, + Color.valueOf(Color.BLACK), /* primaryVeilColor */ + Color.valueOf(Color.GRAY) /* secondaryVeilColor */ + ); mDividerPresenter = new DividerPresenter( MOCK_TASK_ID, mDragEventCallback, mock(Executor.class)); @@ -604,6 +612,31 @@ 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)); + } + 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 049a9e2c2aca..9ebcb759115d 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(); @@ -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); } 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/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index fe68123513ca..8977d5cad780 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -71,3 +71,10 @@ flag { 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" +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt index 611013365e7d..076414132e27 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt @@ -31,7 +31,6 @@ import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.bubbles.DeviceConfig -import com.android.wm.shell.bubbles.bar.BubbleExpandedViewPinController.Companion.DROP_TARGET_SCALE import com.android.wm.shell.common.bubbles.BaseBubblePinController import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION @@ -248,15 +247,8 @@ class BubbleExpandedViewPinControllerTest { private val dropTargetView: View? get() = container.findViewById(R.id.bubble_bar_drop_target) - private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect { - val rect = Rect() - positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, rect) - // Scale the rect to expected size, but keep the center point the same - val centerX = rect.centerX() - val centerY = rect.centerY() - rect.scale(DROP_TARGET_SCALE) - rect.offset(centerX - rect.centerX(), centerY - rect.centerY()) - return rect + private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect = Rect().also { + positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, it) } private fun runOnMainSync(runnable: Runnable) { diff --git a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml index 9dcde3b54421..b928a0b20764 100644 --- a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml +++ b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml @@ -13,12 +13,14 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" +<inset xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <corners android:radius="@dimen/bubble_bar_expanded_view_corner_radius" /> - <solid android:color="@color/bubble_drop_target_background_color" /> - <stroke - android:width="1dp" - android:color="?androidprv:attr/materialColorPrimaryContainer" /> -</shape> + android:inset="@dimen/bubble_bar_expanded_view_drop_target_padding"> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/bubble_bar_expanded_view_drop_target_corner" /> + <solid android:color="@color/bubble_drop_target_background_color" /> + <stroke + android:width="1dp" + android:color="?androidprv:attr/materialColorPrimaryContainer" /> + </shape> +</inset> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 3aecf5f53cf0..3492f136c4f9 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -62,9 +62,9 @@ <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string> <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g> et <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> autres"</string> <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Déplacer dans coin sup. gauche"</string> - <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Déplacer dans coin sup. droit"</string> - <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Déplacer dans coin inf. gauche"</string> - <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer dans coin inf. droit"</string> + <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Déplacer en haut à droite"</string> + <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Déplacer en bas à gauche"</string> + <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer en bas à droite"</string> <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"développer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"réduire <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 7b7779d85d78..302c0071a73a 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -71,7 +71,7 @@ <string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string> <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Жазышууда калкып чыкма билдирмелер көрүнбөсүн"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Калкып чыкма билдирмелер аркылуу маектешүү"</string> - <string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн таптап коюңуз. Жылдыруу үчүн сүйрөңүз."</string> + <string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн тийип коюңуз. Жылдыруу үчүн сүйрөңүз."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Калкып чыкма билдирмелерди каалаган убакта көзөмөлдөңүз"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Бул колдонмодогу калкып чыкма билдирмелерди өчүрүү үчүн \"Башкарууну\" басыңыз"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түшүндүм"</string> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 4a9fab92f11f..5e43506ab621 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -53,7 +53,7 @@ <string name="accessibility_split_top" msgid="2789329702027147146">"Дээд талд хуваах"</string> <string name="accessibility_split_bottom" msgid="8694551025220868191">"Доод талд хуваах"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Нэг гарын горимыг ашиглаж байна"</string> - <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл апп дээр хүссэн газраа товшино уу"</string> + <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл аппын дээр хүссэн газраа товшино уу"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Нэг гарын горимыг эхлүүлэх"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Нэг гарын горимоос гарах"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g>-н бөмбөлгүүдийн тохиргоо"</string> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index f532f96ccfc9..8d24c161e3e4 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -274,6 +274,9 @@ <dimen name="bubble_bar_expanded_view_corner_radius">16dp</dimen> <!-- Corner radius for expanded view while it is being dragged --> <dimen name="bubble_bar_expanded_view_corner_radius_dragged">28dp</dimen> + <!-- Corner radius for expanded view drop target --> + <dimen name="bubble_bar_expanded_view_drop_target_corner">28dp</dimen> + <dimen name="bubble_bar_expanded_view_drop_target_padding">24dp</dimen> <!-- Width of the box around bottom center of the screen where drag only leads to dismiss --> <dimen name="bubble_bar_dismiss_zone_width">192dp</dimen> <!-- Height of the box around bottom center of the screen where drag only leads to dismiss --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java index 9bf9fa749373..bdd89c0e1ac9 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); @@ -109,14 +100,6 @@ public class DesktopModeStatus { } /** - * Return {@code true} if desktop task stashing is enabled when going home. - * Allows users to use home screen to add tasks to desktop. - */ - public static boolean isStashingEnabled() { - return IS_STASHING_ENABLED; - } - - /** * Return whether to use window shadows. * * @param isFocusedWindow whether the window to apply shadows to is focused @@ -144,7 +127,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; } 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 87aac0b55e35..40658709cdf9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -170,6 +170,8 @@ public class BubbleController implements ConfigurationChangeListener, * the pointer might need to be updated. */ void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer); + /** Called when the bubble overflow empty state changes, used to show/hide the overflow. */ + void bubbleOverflowChanged(boolean hasBubbles); } private final Context mContext; @@ -1168,27 +1170,44 @@ public class BubbleController implements ConfigurationChangeListener, * @param bubbleKey key of the bubble being dragged */ public void startBubbleDrag(String bubbleKey) { - onBubbleDrag(bubbleKey, true /* isBeingDragged */); + 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. As was released in given location. + * 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 bubbleKey key of the bubble being dragged * @param location location where bubble was released */ - public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) { + public void stopBubbleDrag(BubbleBarLocation location) { mBubblePositioner.setBubbleBarLocation(location); - onBubbleDrag(bubbleKey, false /* isBeingDragged */); + if (mBubbleData.getSelectedBubble() != null) { + mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true); + } } - private void onBubbleDrag(String bubbleKey, boolean isBeingDragged) { - // TODO(b/330585402): collapse stack if any bubble is dragged - if (mBubbleData.getSelectedBubble() != null - && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) { - // Should collapse/expand only if equals to selected bubble. - mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged); + /** + * 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); } } @@ -1401,7 +1420,7 @@ public class BubbleController implements ConfigurationChangeListener, Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey); if (b != null) { // It's in the overflow, so remove it & reinflate - mBubbleData.removeOverflowBubble(b); + mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL); } else { // App bubble does not exist, lets add and expand it b = Bubble.createAppBubble(intent, user, icon, mMainExecutor); @@ -1844,6 +1863,11 @@ public class BubbleController implements ConfigurationChangeListener, } } + + @Override + public void bubbleOverflowChanged(boolean hasBubbles) { + // TODO (b/334175587): tell stack view to hide / show the overflow + } }; /** When bubbles are in the bubble bar, this will be used to notify bubble bar views. */ @@ -1876,6 +1900,11 @@ public class BubbleController implements ConfigurationChangeListener, } @Override + public void bubbleOverflowChanged(boolean hasBubbles) { + // Nothing to do for our views, handled by launcher / in the bubble bar. + } + + @Override public void suppressionChanged(Bubble bubble, boolean isSuppressed) { if (mLayerView != null) { // TODO (b/273316505) handle suppression changes, although might not need to @@ -1914,7 +1943,7 @@ public class BubbleController implements ConfigurationChangeListener, ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:" + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b" + " expanded=%b selectionChanged=%b selected=%s" - + " suppressed=%s unsupressed=%s shouldShowEducation=%b", + + " suppressed=%s unsupressed=%s shouldShowEducation=%b showOverflowChanged=%b", update.addedBubble != null ? update.addedBubble.getKey() : "null", !update.removedBubbles.isEmpty(), update.updatedBubble != null ? update.updatedBubble.getKey() : "null", @@ -1923,13 +1952,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(); @@ -2333,12 +2366,6 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void removeBubble(String key) { - mMainExecutor.execute( - () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE)); - } - - @Override public void removeAllBubbles() { mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE)); } @@ -2354,8 +2381,13 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) { - mMainExecutor.execute(() -> mController.stopBubbleDrag(bubbleKey, location)); + public void stopBubbleDrag(BubbleBarLocation location) { + mMainExecutor.execute(() -> mController.stopBubbleDrag(location)); + } + + @Override + public void dragBubbleToDismiss(String key) { + mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key)); } @Override 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/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 3c788b18429c..be88b3497000 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -536,7 +536,8 @@ public class BubbleStackView extends FrameLayout private OnClickListener mBubbleClickListener = new OnClickListener() { @Override public void onClick(View view) { - mIsDraggingStack = false; // If the touch ended in a click, we're no longer dragging. + // If the touch ended in a click, we're no longer dragging. + onDraggingEnded(); // Bubble clicks either trigger expansion/collapse or a bubble switch, both of which we // shouldn't interrupt. These are quick transitions, so it's not worth trying to adjust @@ -719,8 +720,7 @@ public class BubbleStackView extends FrameLayout mDismissView.hide(); } - mIsDraggingStack = false; - mMagnetizedObject = null; + onDraggingEnded(); // Hide the stack after a delay, if needed. updateTemporarilyInvisibleAnimation(false /* hideImmediately */); @@ -1096,6 +1096,7 @@ public class BubbleStackView extends FrameLayout } else { maybeShowStackEdu(); } + onDraggingEnded(); }); animate() @@ -1153,6 +1154,14 @@ public class BubbleStackView extends FrameLayout } /** + * Reset state related to dragging. + */ + private void onDraggingEnded() { + mIsDraggingStack = false; + mMagnetizedObject = null; + } + + /** * Sets whether or not the stack should become temporarily invisible by moving off the side of * the screen. * @@ -2341,8 +2350,8 @@ public class BubbleStackView extends FrameLayout showScrim(true, null /* runnable */); updateBubbleShadows(mIsExpanded); - updateBadges(false /* setBadgeForCollapsedStack */); mBubbleContainer.setActiveController(mExpandedAnimationController); + updateBadges(false /* setBadgeForCollapsedStack */); updateOverflowVisibility(); updatePointerPosition(false /* forIme */); mExpandedAnimationController.expandFromStack(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 66f77fa6f76d..1eff149f2e91 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 @@ -33,7 +33,7 @@ interface IBubbles { oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3; - oneway void removeBubble(in String key) = 4; + oneway void dragBubbleToDismiss(in String key) = 4; oneway void removeAllBubbles() = 5; @@ -47,5 +47,5 @@ interface IBubbles { oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10; - oneway void stopBubbleDrag(in String key, in BubbleBarLocation location) = 11; + oneway void stopBubbleDrag(in BubbleBarLocation location) = 11; }
\ No newline at end of file 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/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 5c292f173e5b..bfac24b81d2f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -188,6 +188,11 @@ public class CompatUIController implements OnDisplaysChangedListener, */ private boolean mHasShownUserAspectRatioSettingsButton = false; + /** + * This is true when the rechability education is displayed for the first time. + */ + private boolean mIsFirstReachabilityEducationRunning; + public CompatUIController(@NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @@ -252,9 +257,35 @@ public class CompatUIController implements OnDisplaysChangedListener, removeLayouts(taskInfo.taskId); return; } - + // We're showing the first reachability education so we ignore incoming TaskInfo + // until the education flow has completed or we double tap. + if (mIsFirstReachabilityEducationRunning) { + return; + } + if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) { + if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) { + createOrUpdateLetterboxEduLayout(taskInfo, taskListener); + } else if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) { + // In this case the app is letterboxed and the letterbox education + // is disabled. In this case we need to understand if it's the first + // time we show the reachability education. When this is happening + // we need to ignore all the incoming TaskInfo until the education + // completes. If we come from a double tap we follow the normal flow. + final boolean topActivityPillarboxed = + taskInfo.appCompatTaskInfo.isTopActivityPillarboxed(); + final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed + && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo); + final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed + && !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo); + if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) { + mIsFirstReachabilityEducationRunning = true; + mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId); + createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + return; + } + } + } createOrUpdateCompatLayout(taskInfo, taskListener); - createOrUpdateLetterboxEduLayout(taskInfo, taskListener); createOrUpdateRestartDialogLayout(taskInfo, taskListener); if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) { createOrUpdateReachabilityEduLayout(taskInfo, taskListener); @@ -589,6 +620,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) { // We need to update the UI otherwise it will not be shown until the user relaunches the app + mIsFirstReachabilityEducationRunning = false; createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index f195f955692e..3ab1fad2b203 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -81,6 +81,10 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { super(context, taskInfo, syncQueue, taskListener, displayLayout); mCallback = callback; mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; + if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) { + // Don't show the SCM button for freeform tasks + mHasSizeCompat &= !taskInfo.isFreeform(); + } mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState; mCompatUIHintsState = compatUIHintsState; @@ -136,6 +140,10 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { final boolean prevHasSizeCompat = mHasSizeCompat; final int prevCameraCompatControlState = mCameraCompatControlState; mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; + if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) { + // Don't show the SCM button for freeform tasks + mHasSizeCompat &= !taskInfo.isFreeform(); + } mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index e729c7dd802b..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; 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 a1910c5eb3a3..fb0a1ab3062e 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; 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/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 6e61f22ca563..414a9d1151ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -132,6 +132,7 @@ public abstract class Pip2Module { PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, + @NonNull PipTransitionState pipTransitionState, @NonNull SizeSpecSource sizeSpecSource, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -139,8 +140,9 @@ public abstract class Pip2Module { @ShellMainThread ShellExecutor mainExecutor, Optional<PipPerfHintController> pipPerfHintControllerOptional) { return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, - pipBoundsState, sizeSpecSource, pipMotionHelper, floatingContentCoordinator, - pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional); + pipBoundsState, pipTransitionState, sizeSpecSource, pipMotionHelper, + floatingContentCoordinator, pipUiEventLogger, mainExecutor, + pipPerfHintControllerOptional); } @WMSingleton @@ -149,9 +151,13 @@ public abstract class Pip2Module { PipBoundsState pipBoundsState, PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, FloatingContentCoordinator floatingContentCoordinator, - Optional<PipPerfHintController> pipPerfHintControllerOptional) { + PipScheduler pipScheduler, + Optional<PipPerfHintController> pipPerfHintControllerOptional, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipTransitionState pipTransitionState) { return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm, - floatingContentCoordinator, pipPerfHintControllerOptional); + floatingContentCoordinator, pipScheduler, pipPerfHintControllerOptional, + pipBoundsAlgorithm, pipTransitionState); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index 9038aaad9178..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 @@ -39,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 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 f7bfb86a5158..2dc4573b4921 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,7 @@ 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.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.splitscreen.SplitScreenController @@ -85,7 +87,6 @@ import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener -import com.android.wm.shell.windowdecor.extension.isFreeform import com.android.wm.shell.windowdecor.extension.isFullscreen import java.io.PrintWriter import java.util.Optional @@ -203,6 +204,11 @@ class DesktopTasksController( dragAndDropController.addListener(this) } + @VisibleForTesting + fun getVisualIndicator(): DesktopModeVisualIndicator? { + return visualIndicator + } + fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) { toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) @@ -222,7 +228,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 +241,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 +583,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 +602,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 +844,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 +882,8 @@ class DesktopTasksController( .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) } } - private val TaskInfo.isStashed: Boolean - get() = desktopModeTaskRepository.isStashed(displayId) - - private fun shouldLaunchAsModal(task: TaskInfo): Boolean { - return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) - } + private fun shouldLaunchAsModal(task: TaskInfo) = + Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean { return Flags.enableDesktopWindowingWallpaperActivity() && @@ -966,24 +943,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 +970,7 @@ class DesktopTasksController( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo ) { + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { @@ -1019,6 +979,9 @@ class DesktopTasksController( } else { WINDOWING_MODE_FREEFORM } + if (Flags.enableWindowingDynamicInitialBounds()) { + wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo)) + } wct.setWindowingMode(taskInfo.token, targetWindowingMode) wct.reorder(taskInfo.token, true /* onTop */) if (isDesktopDensityOverrideSet()) { @@ -1239,13 +1202,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 +1375,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 +1406,25 @@ class DesktopTasksController( ) { c -> c.showDesktopApps(displayId, remoteTransition) } } - override fun stashDesktopApps(displayId: Int) { + override fun showDesktopApp(taskId: Int) { ExecutorUtils.executeRemoteCallWithTaskPermission( controller, - "stashDesktopApps" - ) { c -> c.stashDesktopApps(displayId) } + "showDesktopApp" + ) { c -> c.moveTaskToFront(taskId) } } - override fun hideStashedDesktopApps(displayId: Int) { - ExecutorUtils.executeRemoteCallWithTaskPermission( - controller, - "hideStashedDesktopApps" - ) { c -> c.hideStashedDesktopApps(displayId) } + override fun stashDesktopApps(displayId: Int) { + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: stashDesktopApps is deprecated" + ) } - override fun showDesktopApp(taskId: Int) { - ExecutorUtils.executeRemoteCallWithTaskPermission( - controller, - "showDesktopApp" - ) { c -> c.moveTaskToFront(taskId) } + override fun hideStashedDesktopApps(displayId: Int) { + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: hideStashedDesktopApps is deprecated" + ) } override fun getVisibleTaskCount(displayId: Int): Int { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/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 451e09c3cd9c..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 @@ -23,6 +23,7 @@ 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 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/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index ecb53dc17a48..59d696918448 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -16,11 +16,11 @@ package com.android.wm.shell.draganddrop; -import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE_NONE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; @@ -516,20 +516,18 @@ public class DragLayout extends LinearLayout } private void animateFullscreenContainer(boolean visible) { - int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE; - StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags, - DISABLE2_NONE); - mStatusBarManager.requestDisabledComponent(disableInfo, "animateFullscreenContainer"); + mStatusBarManager.disable(visible + ? HIDE_STATUS_BAR_FLAGS + : DISABLE_NONE); // We're only using the first drop zone if there is one fullscreen target mDropZoneView1.setShowingMargin(visible); mDropZoneView1.setShowingHighlight(visible); } private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) { - int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE; - StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags, - DISABLE2_NONE); - mStatusBarManager.requestDisabledComponent(disableInfo, "animateSplitContainers"); + mStatusBarManager.disable(visible + ? HIDE_STATUS_BAR_FLAGS + : DISABLE_NONE); mDropZoneView1.setShowingMargin(visible); mDropZoneView2.setShowingMargin(visible); Animator animator = mDropZoneView1.getAnimator(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index a414a55eb633..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 @@ -27,9 +27,9 @@ 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; 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/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index fdde3ee01264..2082756feda7 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 @@ -824,12 +824,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/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java index 03547a55fa27..b757b00f16dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java @@ -53,7 +53,7 @@ public class PipInputConsumer { * Listener interface for callers to learn when this class is registered or unregistered with * window manager */ - private interface RegistrationListener { + interface RegistrationListener { void onRegistrationChanged(boolean isRegistered); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 619bed4e19ca..a097a0ffa47d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -31,7 +31,9 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; +import android.os.Bundle; import android.os.Debug; +import android.view.SurfaceControl; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; @@ -39,6 +41,7 @@ import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipSnapAlgorithm; @@ -57,6 +60,7 @@ import java.util.function.Consumer; public class PipMotionHelper implements PipAppOpsListener.Callback, FloatingContentCoordinator.FloatingContent { private static final String TAG = "PipMotionHelper"; + private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change"; private static final boolean DEBUG = false; private static final int SHRINK_STACK_FROM_MENU_DURATION = 250; @@ -72,7 +76,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private final Context mContext; private @NonNull PipBoundsState mPipBoundsState; - + private @NonNull PipBoundsAlgorithm mPipBoundsAlgorithm; + private @NonNull PipScheduler mPipScheduler; + private @NonNull PipTransitionState mPipTransitionState; private PhonePipMenuController mMenuController; private PipSnapAlgorithm mSnapAlgorithm; @@ -145,6 +151,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private boolean mDismissalPending = false; /** + * Set to true if bounds change transition has been scheduled from PipMotionHelper. + */ + private boolean mWaitingForBoundsChangeTransition = false; + + /** * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is * used to show menu activity when the expand animation is completed. */ @@ -152,22 +163,25 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, - FloatingContentCoordinator floatingContentCoordinator, - Optional<PipPerfHintController> pipPerfHintControllerOptional) { + FloatingContentCoordinator floatingContentCoordinator, PipScheduler pipScheduler, + Optional<PipPerfHintController> pipPerfHintControllerOptional, + PipBoundsAlgorithm pipBoundsAlgorithm, PipTransitionState pipTransitionState) { mContext = context; mPipBoundsState = pipBoundsState; + mPipBoundsAlgorithm = pipBoundsAlgorithm; + mPipScheduler = pipScheduler; mMenuController = menuController; mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); mResizePipUpdateListener = (target, values) -> { if (mPipBoundsState.getMotionBoundsState().isInMotion()) { - /* - mPipTaskOrganizer.scheduleUserResizePip(getBounds(), - mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null); - */ + mPipScheduler.scheduleUserResizePip( + mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); } }; + mPipTransitionState = pipTransitionState; + mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged); } void init() { @@ -236,12 +250,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mPipBoundsState.setBounds(toBounds); } else { mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds); - /* - mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds, - (Rect newBounds) -> { - mMenuController.updateMenuLayout(newBounds); - }); - */ + mPipScheduler.scheduleUserResizePip(toBounds); } } else { // If PIP is 'catching up' after being stuck in the dismiss target, update the animation @@ -552,11 +561,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** Set new fling configs whose min/max values respect the given movement bounds. */ private void rebuildFlingConfigs() { mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION, - mPipBoundsState.getMovementBounds().left, - mPipBoundsState.getMovementBounds().right); + mPipBoundsAlgorithm.getMovementBounds(getBounds()).left, + mPipBoundsAlgorithm.getMovementBounds(getBounds()).right); mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION, - mPipBoundsState.getMovementBounds().top, - mPipBoundsState.getMovementBounds().bottom); + mPipBoundsAlgorithm.getMovementBounds(getBounds()).top, + mPipBoundsAlgorithm.getMovementBounds(getBounds()).bottom); final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets(); mStashConfigX = new PhysicsAnimator.FlingConfig( DEFAULT_FRICTION, @@ -623,22 +632,15 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void onBoundsPhysicsAnimationEnd() { // The physics animation ended, though we may not necessarily be done animating, such as // when we're still dragging after moving out of the magnetic target. - if (!mDismissalPending - && !mSpringingToTouch - && !mMagnetizedPip.getObjectStuckToTarget()) { - // All motion operations have actually finished. - mPipBoundsState.setBounds( - mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); - mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded(); - if (!mDismissalPending) { - // do not schedule resize if PiP is dismissing, which may cause app re-open to - // mBounds instead of its normal bounds. - // mPipTaskOrganizer.scheduleFinishResizePip(getBounds()); - } + if (!mDismissalPending && !mSpringingToTouch && !mMagnetizedPip.getObjectStuckToTarget()) { + // do not schedule resize if PiP is dismissing, which may cause app re-open to + // mBounds instead of its normal bounds. + Bundle extra = new Bundle(); + extra.putBoolean(FLING_BOUNDS_CHANGE, true); + mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra); + return; } - mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded(); - mSpringingToTouch = false; - mDismissalPending = false; + settlePipBoundsAfterPhysicsAnimation(true /* animatingAfter */); cleanUpHighPerfSessionMaybe(); } @@ -662,7 +664,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, + " callers=\n%s", TAG, toBounds, Debug.getCallers(5, " ")); } if (!toBounds.equals(getBounds())) { - // mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback); + mPipScheduler.scheduleAnimateResizePip(toBounds); } } @@ -685,6 +687,60 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, // setAnimatingToBounds(toBounds); } + private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @PipTransitionState.TransitionState int newState, + @Nullable Bundle extra) { + switch (newState) { + case PipTransitionState.SCHEDULED_BOUNDS_CHANGE: + if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break; + + // If touch is turned off and we are in a fling animation, schedule a transition. + mWaitingForBoundsChangeTransition = true; + mPipScheduler.scheduleAnimateResizePip( + mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); + break; + case PipTransitionState.CHANGING_PIP_BOUNDS: + if (!mWaitingForBoundsChangeTransition) break; + + // If bounds change transition was scheduled from this class, handle leash updates. + mWaitingForBoundsChangeTransition = false; + SurfaceControl.Transaction startTx = extra.getParcelable( + PipTransition.PIP_START_TX, SurfaceControl.Transaction.class); + Rect destinationBounds = extra.getParcelable( + PipTransition.PIP_DESTINATION_BOUNDS, Rect.class); + startTx.setPosition(mPipTransitionState.mPinnedTaskLeash, + destinationBounds.left, destinationBounds.top); + startTx.apply(); + + // All motion operations have actually finished, so make bounds cache updates. + settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */); + cleanUpHighPerfSessionMaybe(); + + // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core. + mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS); + break; + case PipTransitionState.EXITING_PIP: + // We need to force finish any local animators if about to leave PiP, to avoid + // breaking the state (e.g. leashes are cleaned up upon exit). + if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break; + cancelPhysicsAnimation(); + settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */); + } + } + + private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) { + if (!animatingAfter) { + // The physics animation ended, though we may not necessarily be done animating, such as + // when we're still dragging after moving out of the magnetic target. Only set the final + // bounds state and clear motion bounds completely if the whole animation is over. + mPipBoundsState.setBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); + mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded(); + } + mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded(); + mSpringingToTouch = false; + mDismissalPending = false; + } + /** * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the * magnetic dismiss target so it can calculate PIP's size and position. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 72fa3badeb93..c5b0de31f104 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -25,16 +25,19 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Rect; +import android.view.SurfaceControl; import android.window.WindowContainerTransaction; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -156,4 +159,20 @@ public class PipScheduler { wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds); mPipTransitionController.startResizeTransition(wct); } + + /** + * Directly perform a scaled matrix transformation on the leash. This will not perform any + * {@link WindowContainerTransaction}. + */ + public void scheduleUserResizePip(Rect toBounds) { + if (toBounds.isEmpty()) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG); + return; + } + SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash; + final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + tx.setPosition(leash, toBounds.left, toBounds.top); + tx.apply(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 472003cb435f..9c6e3ea494fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -16,6 +16,8 @@ package com.android.wm.shell.pip2.phone; +import static android.view.WindowManager.INPUT_CONSUMER_PIP; + import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD; import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; @@ -30,18 +32,19 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; -import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.os.Bundle; import android.provider.DeviceConfig; import android.util.Size; import android.view.DisplayCutout; import android.view.InputEvent; import android.view.MotionEvent; import android.view.ViewConfiguration; +import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -80,6 +83,7 @@ public class PipTouchHandler { private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; @NonNull private final PipBoundsState mPipBoundsState; + @NonNull private final PipTransitionState mPipTransitionState; @NonNull private final SizeSpecSource mSizeSpecSource; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; @@ -125,6 +129,7 @@ public class PipTouchHandler { private final FloatingContentCoordinator mFloatingContentCoordinator; private PipMotionHelper mMotionHelper; private PipTouchGesture mGesture; + private PipInputConsumer mPipInputConsumer; // Temp vars private final Rect mTmpBounds = new Rect(); @@ -167,6 +172,7 @@ public class PipTouchHandler { PhonePipMenuController menuController, PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, + @NonNull PipTransitionState pipTransitionState, @NonNull SizeSpecSource sizeSpecSource, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -179,6 +185,9 @@ public class PipTouchHandler { mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; + + mPipTransitionState = pipTransitionState; + mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged); mSizeSpecSource = sizeSpecSource; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; @@ -227,6 +236,11 @@ public class PipTouchHandler { mPipResizeGestureHandler.init(); mPipDismissTargetHandler.init(); + mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), + INPUT_CONSUMER_PIP, mMainExecutor); + mPipInputConsumer.setInputListener(this::handleTouchEvent); + mPipInputConsumer.setRegistrationListener(this::onRegistrationChanged); + mEnableStash = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, PIP_STASHING, @@ -294,19 +308,17 @@ public class PipTouchHandler { void onActivityPinned() { mPipDismissTargetHandler.createOrUpdateDismissTarget(); - mPipResizeGestureHandler.onActivityPinned(); mFloatingContentCoordinator.onContentAdded(mMotionHelper); + mPipInputConsumer.registerInputConsumer(); } - void onActivityUnpinned(ComponentName topPipActivity) { - if (topPipActivity == null) { - // Clean up state after the last PiP activity is removed - mPipDismissTargetHandler.cleanUpDismissTarget(); - - mFloatingContentCoordinator.onContentRemoved(mMotionHelper); - } + void onActivityUnpinned() { + // Clean up state after the last PiP activity is removed + mPipDismissTargetHandler.cleanUpDismissTarget(); + mFloatingContentCoordinator.onContentRemoved(mMotionHelper); mPipResizeGestureHandler.onActivityUnpinned(); + mPipInputConsumer.unregisterInputConsumer(); } void onPinnedStackAnimationEnded( @@ -512,6 +524,7 @@ public class PipTouchHandler { return true; } + /* if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting()) && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) { // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event @@ -528,11 +541,13 @@ public class PipTouchHandler { return true; } - if (!mTouchState.isUserInteracting()) { + // Ignore the motion event When the entry animation is waiting to be started + if (!mTouchState.isUserInteracting() && mPipTaskOrganizer.isEntryScheduled()) { ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE, "%s: Waiting to start the entry animation, skip the motion event.", TAG); return true; } + */ // Update the touch state mTouchState.onTouchEvent(ev); @@ -808,7 +823,7 @@ public class PipTouchHandler { mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mPipBoundsState.getMovementBounds().bottom; mMotionHelper.setSpringingToTouch(false); - // mPipDismissTargetHandler.setTaskLeash(mPipTaskOrganizer.getSurfaceControl()); + mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it @@ -880,7 +895,8 @@ public class PipTouchHandler { // Reset the touch state on up before the fling settles mTouchState.reset(); if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) { - mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */); + // mMotionHelper.stashToEdge(vel.x, vel.y, + // this::stashEndAction /* endAction */); } else { if (mPipBoundsState.isStashed()) { // Reset stashed state if previously stashed @@ -1059,6 +1075,27 @@ public class PipTouchHandler { mPipResizeGestureHandler.setOhmOffset(offset); } + private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @PipTransitionState.TransitionState int newState, + @Nullable Bundle extra) { + switch (newState) { + case PipTransitionState.ENTERED_PIP: + onActivityPinned(); + mTouchState.setAllowInputEvents(true); + break; + case PipTransitionState.EXITED_PIP: + mTouchState.setAllowInputEvents(false); + onActivityUnpinned(); + break; + case PipTransitionState.SCHEDULED_BOUNDS_CHANGE: + mTouchState.setAllowInputEvents(false); + break; + case PipTransitionState.CHANGED_PIP_BOUNDS: + mTouchState.setAllowInputEvents(true); + break; + } + } + /** * Dumps the {@link PipTouchHandler} state. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 8b2d0ddf2510..7dddd2748f83 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -61,11 +61,15 @@ import com.android.wm.shell.transition.Transitions; public class PipTransition extends PipTransitionController implements PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = PipTransition.class.getSimpleName(); + + // Used when for ENTERING_PIP state update. private static final String PIP_TASK_TOKEN = "pip_task_token"; private static final String PIP_TASK_LEASH = "pip_task_leash"; - private static final String PIP_START_TX = "pip_start_tx"; - private static final String PIP_FINISH_TX = "pip_finish_tx"; - private static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds"; + + // Used for PiP CHANGING_BOUNDS state update. + static final String PIP_START_TX = "pip_start_tx"; + static final String PIP_FINISH_TX = "pip_finish_tx"; + static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds"; /** * The fixed start delay in ms when fading out the content overlay from bounds animation. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java index 9a9c59e2fa8e..8204d41a9833 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java @@ -71,17 +71,21 @@ public class PipTransitionState { // State for app finishing drawing in PiP mode as a final step in enter PiP flow. public static final int ENTERED_PIP = 3; - // State for scheduling a transition to change PiP bounds. - public static final int CHANGING_PIP_BOUNDS = 4; + // State to indicate we have scheduled a PiP bounds change transition. + public static final int SCHEDULED_BOUNDS_CHANGE = 4; - // State for app potentially finishing drawing in new PiP bounds after resize is complete. - public static final int CHANGED_PIP_BOUNDS = 5; + // State for the start of playing a transition to change PiP bounds. At this point, WM Core + // is aware of the new PiP bounds, but Shell might still be continuing animating. + public static final int CHANGING_PIP_BOUNDS = 5; + + // State for finishing animating into new PiP bounds after resize is complete. + public static final int CHANGED_PIP_BOUNDS = 6; // State for starting exiting PiP. - public static final int EXITING_PIP = 6; + public static final int EXITING_PIP = 7; // State for finishing exit PiP flow. - public static final int EXITED_PIP = 7; + public static final int EXITED_PIP = 8; private static final int FIRST_CUSTOM_STATE = 1000; @@ -92,6 +96,7 @@ public class PipTransitionState { SWIPING_TO_PIP, ENTERING_PIP, ENTERED_PIP, + SCHEDULED_BOUNDS_CHANGE, CHANGING_PIP_BOUNDS, CHANGED_PIP_BOUNDS, EXITING_PIP, @@ -165,10 +170,11 @@ public class PipTransitionState { * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info. */ public void setState(@TransitionState int state, @Nullable Bundle extra) { - if (state == ENTERING_PIP || state == SWIPING_TO_PIP) { - // Whenever we are entering PiP caller must provide extra state to set as well. + if (state == ENTERING_PIP || state == SWIPING_TO_PIP + || state == SCHEDULED_BOUNDS_CHANGE || state == CHANGING_PIP_BOUNDS) { + // States listed above require extra bundles to be provided. Preconditions.checkArgument(extra != null && !extra.isEmpty(), - "No extra bundle for either ENTERING_PIP or SWIPING_TO_PIP state."); + "No extra bundle for " + stateToString(state) + " state."); } if (mState != state) { dispatchPipTransitionStateChanged(mState, state, extra); @@ -254,23 +260,24 @@ public class PipTransitionState { return ++mPrevCustomState; } - private String stateToString() { - switch (mState) { + private static String stateToString(int state) { + switch (state) { case UNDEFINED: return "undefined"; case SWIPING_TO_PIP: return "swiping_to_pip"; case ENTERING_PIP: return "entering-pip"; case ENTERED_PIP: return "entered-pip"; + case SCHEDULED_BOUNDS_CHANGE: return "scheduled_bounds_change"; case CHANGING_PIP_BOUNDS: return "changing-bounds"; case CHANGED_PIP_BOUNDS: return "changed-bounds"; case EXITING_PIP: return "exiting-pip"; case EXITED_PIP: return "exited-pip"; } - throw new IllegalStateException("Unknown state: " + mState); + throw new IllegalStateException("Unknown state: " + state); } @Override public String toString() { return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)", - stateToString(), mInSwipePipToHomeTransition); + stateToString(mState), mInSwipePipToHomeTransition); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index ad29d15019c5..19af3d544b36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -52,7 +52,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, - Consts.TAG_WM_SHELL), + Consts.TAG_WM_DESKTOP_MODE), WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, @@ -120,6 +120,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { private static final String TAG_WM_SHELL = "WindowManagerShell"; private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow"; private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen"; + private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode"; private static final boolean ENABLE_DEBUG = true; private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index a8611d966a29..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; 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..968b27b62490 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -426,7 +426,8 @@ public class DefaultMixedHandler implements MixedTransitionHandler, ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Converting mixed transition into a keyguard transition"); // Consume the original mixed transition - onTransitionConsumed(transition, false, null); + mActiveTransitions.remove(mixed); + mixed.onTransitionConsumed(transition, false, null); return true; } else { // Keyguard handler cannot handle it, process through original mixed diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/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/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index f3ef7c17fac3..9afb057ffbe5 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 @@ -84,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; @@ -278,9 +278,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) { if (visible && stage != STAGE_TYPE_UNDEFINED) { DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId); - if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopTasksController.moveToSplit(decor.mTaskInfo); + if (decor == null || !DesktopModeStatus.canEnterDesktopMode(mContext) + || decor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) { + return; } + mDesktopTasksController.moveToSplit(decor.mTaskInfo); } } }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index f7516dabe1ac..4d4dc3c72420 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 @@ -66,8 +66,8 @@ 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; @@ -110,6 +110,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private ResizeVeil mResizeVeil; private Bitmap mAppIconBitmap; + private Bitmap mResizeVeilBitmap; + private CharSequence mAppName; private ExclusionRegionListener mExclusionRegionListener; @@ -468,11 +470,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin PackageManager pm = mContext.getApplicationContext().getPackageManager(); final IconProvider provider = new IconProvider(mContext); final Drawable appIconDrawable = provider.getIcon(activityInfo); - final Resources resources = mContext.getResources(); - final BaseIconFactory factory = new BaseIconFactory(mContext, - resources.getDisplayMetrics().densityDpi, - resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius)); - mAppIconBitmap = factory.createScaledBitmap(appIconDrawable, MODE_DEFAULT); + final BaseIconFactory headerIconFactory = createIconFactory(mContext, + R.dimen.desktop_mode_caption_icon_radius); + mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT); + + final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext, + R.dimen.desktop_mode_resize_veil_icon_size); + mResizeVeilBitmap = resizeVeilIconFactory + .createScaledBitmap(appIconDrawable, MODE_DEFAULT); + final ApplicationInfo applicationInfo = activityInfo.applicationInfo; mAppName = pm.getApplicationLabel(applicationInfo); } finally { @@ -480,6 +486,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } } + private BaseIconFactory createIconFactory(Context context, int dimensions) { + final Resources resources = context.getResources(); + final int densityDpi = resources.getDisplayMetrics().densityDpi; + final int iconSize = resources.getDimensionPixelSize(dimensions); + return new BaseIconFactory(context, densityDpi, iconSize); + } + private void closeDragResizeListener() { if (mDragResizeListener == null) { return; @@ -495,7 +508,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private void createResizeVeilIfNeeded() { if (mResizeVeil != null) return; loadAppInfoIfNeeded(); - mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconBitmap, mTaskInfo, + mResizeVeil = new ResizeVeil(mContext, mDisplayController, mResizeVeilBitmap, mTaskInfo, mTaskSurface, mSurfaceControlTransactionSupplier); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 9624d46678bf..5379ca6cd51d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -24,7 +24,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERL import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; -import static com.android.input.flags.Flags.enablePointerChoreographer; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT; @@ -54,6 +54,7 @@ import android.view.ViewConfiguration; import android.view.WindowManagerGlobal; import android.window.InputTransferToken; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; @@ -399,12 +400,17 @@ class DragResizeInputListener implements AutoCloseable { float rawX = e.getRawX(0); float rawY = e.getRawY(0); int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y); + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "%s: Handling action down, update ctrlType to %d", TAG, ctrlType); mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType, rawX, rawY); // Increase the input sink region to cover the whole screen; this is to // prevent input and focus from going to other tasks during a drag resize. updateInputSinkRegionForDrag(mDragStartTaskBounds); result = true; + } else { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "%s: Handling action down, but ignore event", TAG); } break; } @@ -499,12 +505,10 @@ class DragResizeInputListener implements AutoCloseable { // where views in the task can receive input events because we can't set touch regions // of input sinks to have rounded corners. if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) { - if (enablePointerChoreographer()) { - mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType), - displayId, deviceId, pointerId, mInputChannel.getToken()); - } else { - mInputManager.setPointerIconType(cursorType); - } + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: update pointer icon from %d to %d", + TAG, mLastCursorType, cursorType); + mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType), + displayId, deviceId, pointerId, mInputChannel.getToken()); mLastCursorType = cursorType; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java index eafb56995db7..4f513f0a0fd8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java @@ -33,6 +33,9 @@ import android.graphics.Region; import android.util.Size; import android.view.MotionEvent; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + import com.android.wm.shell.R; import java.util.Objects; @@ -41,6 +44,11 @@ import java.util.Objects; * Geometry for a drag resize region for a particular window. */ final class DragResizeWindowGeometry { + // TODO(b/337264971) clean up when no longer needed + @VisibleForTesting static final boolean DEBUG = true; + // The additional width to apply to edge resize bounds just for logging when a touch is + // close. + @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15; private final int mTaskCornerRadius; private final Size mTaskSize; // The size of the handle applied to the edges of the window, for the user to drag resize. @@ -51,10 +59,9 @@ final class DragResizeWindowGeometry { // The task corners to permit drag resizing with a fine input, such as stylus or cursor. private final @NonNull TaskCorners mFineTaskCorners; // The bounds for each edge drag region, which can resize the task in one direction. - private final @NonNull Rect mTopEdgeBounds; - private final @NonNull Rect mLeftEdgeBounds; - private final @NonNull Rect mRightEdgeBounds; - private final @NonNull Rect mBottomEdgeBounds; + private final @NonNull TaskEdges mTaskEdges; + // Extra-large edge bounds for logging to help debug when an edge resize is ignored. + private final @Nullable TaskEdges mDebugTaskEdges; DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize, int resizeHandleThickness, int fineCornerSize, int largeCornerSize) { @@ -66,26 +73,12 @@ final class DragResizeWindowGeometry { mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize); // Save touch areas for each edge. - mTopEdgeBounds = new Rect( - -mResizeHandleThickness, - -mResizeHandleThickness, - mTaskSize.getWidth() + mResizeHandleThickness, - 0); - mLeftEdgeBounds = new Rect( - -mResizeHandleThickness, - 0, - 0, - mTaskSize.getHeight()); - mRightEdgeBounds = new Rect( - mTaskSize.getWidth(), - 0, - mTaskSize.getWidth() + mResizeHandleThickness, - mTaskSize.getHeight()); - mBottomEdgeBounds = new Rect( - -mResizeHandleThickness, - mTaskSize.getHeight(), - mTaskSize.getWidth() + mResizeHandleThickness, - mTaskSize.getHeight() + mResizeHandleThickness); + mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness); + if (DEBUG) { + mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER); + } else { + mDebugTaskEdges = null; + } } /** @@ -127,10 +120,13 @@ final class DragResizeWindowGeometry { */ void union(@NonNull Region region) { // Apply the edge resize regions. - region.union(mTopEdgeBounds); - region.union(mLeftEdgeBounds); - region.union(mRightEdgeBounds); - region.union(mBottomEdgeBounds); + if (inDebugMode()) { + // Use the larger edge sizes if we are debugging, to be able to log if we ignored a + // touch due to the size of the edge region. + mDebugTaskEdges.union(region); + } else { + mTaskEdges.union(region); + } if (enableWindowingEdgeDragResize()) { // Apply the corners as well for the larger corners, to ensure we capture all possible @@ -216,6 +212,10 @@ final class DragResizeWindowGeometry { @DragPositioningCallback.CtrlType private int calculateEdgeResizeCtrlType(float x, float y) { + if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y) + && !mTaskEdges.contains((int) x, (int) y))) { + return CTRL_TYPE_UNDEFINED; + } int ctrlType = CTRL_TYPE_UNDEFINED; // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with // sides will use the bounds specified in setGeometry and not go into task bounds. @@ -306,10 +306,9 @@ final class DragResizeWindowGeometry { && this.mResizeHandleThickness == other.mResizeHandleThickness && this.mFineTaskCorners.equals(other.mFineTaskCorners) && this.mLargeTaskCorners.equals(other.mLargeTaskCorners) - && this.mTopEdgeBounds.equals(other.mTopEdgeBounds) - && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds) - && this.mRightEdgeBounds.equals(other.mRightEdgeBounds) - && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds); + && (inDebugMode() + ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges) + : this.mTaskEdges.equals(other.mTaskEdges)); } @Override @@ -320,10 +319,11 @@ final class DragResizeWindowGeometry { mResizeHandleThickness, mFineTaskCorners, mLargeTaskCorners, - mTopEdgeBounds, - mLeftEdgeBounds, - mRightEdgeBounds, - mBottomEdgeBounds); + (inDebugMode() ? mDebugTaskEdges : mTaskEdges)); + } + + private boolean inDebugMode() { + return DEBUG && mDebugTaskEdges != null; } /** @@ -431,4 +431,92 @@ final class DragResizeWindowGeometry { mRightBottomCornerBounds); } } + + /** + * Representation of the drag resize regions at the edges of the window. + */ + private static class TaskEdges { + private final @NonNull Rect mTopEdgeBounds; + private final @NonNull Rect mLeftEdgeBounds; + private final @NonNull Rect mRightEdgeBounds; + private final @NonNull Rect mBottomEdgeBounds; + private final @NonNull Region mRegion; + + private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness) { + // Save touch areas for each edge. + mTopEdgeBounds = new Rect( + -resizeHandleThickness, + -resizeHandleThickness, + taskSize.getWidth() + resizeHandleThickness, + 0); + mLeftEdgeBounds = new Rect( + -resizeHandleThickness, + 0, + 0, + taskSize.getHeight()); + mRightEdgeBounds = new Rect( + taskSize.getWidth(), + 0, + taskSize.getWidth() + resizeHandleThickness, + taskSize.getHeight()); + mBottomEdgeBounds = new Rect( + -resizeHandleThickness, + taskSize.getHeight(), + taskSize.getWidth() + resizeHandleThickness, + taskSize.getHeight() + resizeHandleThickness); + + mRegion = new Region(); + mRegion.union(mTopEdgeBounds); + mRegion.union(mLeftEdgeBounds); + mRegion.union(mRightEdgeBounds); + mRegion.union(mBottomEdgeBounds); + } + + /** + * Returns {@code true} if the edges contain the given point. + */ + private boolean contains(int x, int y) { + return mRegion.contains(x, y); + } + + /** + * Updates the region to include all four corners. + */ + private void union(Region region) { + region.union(mTopEdgeBounds); + region.union(mLeftEdgeBounds); + region.union(mRightEdgeBounds); + region.union(mBottomEdgeBounds); + } + + @Override + public String toString() { + return "TaskEdges for the" + + " top " + mTopEdgeBounds + + " left " + mLeftEdgeBounds + + " right " + mRightEdgeBounds + + " bottom " + mBottomEdgeBounds; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof TaskEdges other)) return false; + + return this.mTopEdgeBounds.equals(other.mTopEdgeBounds) + && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds) + && this.mRightEdgeBounds.equals(other.mRightEdgeBounds) + && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds); + } + + @Override + public int hashCode() { + return Objects.hash( + mTopEdgeBounds, + mLeftEdgeBounds, + mRightEdgeBounds, + mBottomEdgeBounds); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index de6c03549f0e..541825437c86 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 @@ -52,7 +52,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; 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/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/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/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/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index afae653f0682..9c008647104a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -668,6 +668,18 @@ public class CompatUIControllerTest extends ShellTestCase { Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); } + @Test + public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() { + TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, + CAMERA_COMPAT_CONTROL_HIDDEN); + taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false; + + mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + + verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); + } + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, @CameraCompatControlState int cameraCompatControlState) { return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState, @@ -694,6 +706,8 @@ public class CompatUIControllerTest extends ShellTestCase { taskInfo.isVisible = isVisible; taskInfo.isFocused = isFocused; taskInfo.isTopActivityTransparent = isTopActivityTransparent; + taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = true; + taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = true; return taskInfo; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index 60a7dcda5351..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 @@ -41,6 +41,7 @@ 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 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 df8a2223dadc..f67da5573b7d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -24,6 +24,12 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE +import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT +import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.graphics.Point import android.graphics.PointF import android.graphics.Rect @@ -73,6 +79,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,7 +108,9 @@ import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock +import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.capture import org.mockito.quality.Strictness @@ -141,6 +150,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var dragAndDropController: DragAndDropController @Mock lateinit var multiInstanceHelper: MultiInstanceHelper @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver + @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -154,6 +164,15 @@ class DesktopTasksControllerTest : ShellTestCase() { // Mock running tasks are registered here so we can get the list from mock shell task organizer private val runningTasks = mutableListOf<RunningTaskInfo>() + private val DISPLAY_DIMENSION_SHORT = 1600 + private val DISPLAY_DIMENSION_LONG = 2560 + private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400) + private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240) + private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880) + private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400) + private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861) + private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400) + @Before fun setUp() { mockitoSession = mockitoSession().strictness(Strictness.LENIENT) @@ -161,7 +180,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(DesktopModeStatus.isEnabled()).thenReturn(true) doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } - shellInit = Mockito.spy(ShellInit(testExecutor)) + shellInit = spy(ShellInit(testExecutor)) desktopModeTaskRepository = DesktopModeTaskRepository() desktopTasksLimiter = DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer) @@ -464,6 +483,135 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask() + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, + shouldLetterbox = true) + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(isResizable = false, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(isResizable = false, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true) + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! @@ -897,29 +1045,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { - assumeTrue(ENABLE_SHELL_TRANSITIONS) - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) - markTaskHidden(stashedFreeformTask) - - val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY) - - controller.stashDesktopApps(DEFAULT_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - assertThat(result).isNotNull() - result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask) - assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - - // Stashed state should be cleared - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() - } - - @Test fun handleRequest_freeformTask_freeformVisible_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -986,27 +1111,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { - assumeTrue(ENABLE_SHELL_TRANSITIONS) - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) - markTaskHidden(stashedFreeformTask) - - val freeformTask = createFreeformTask(DEFAULT_DISPLAY) - - controller.stashDesktopApps(DEFAULT_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(freeformTask)) - assertThat(result).isNotNull() - result?.assertReorderSequence(stashedFreeformTask, freeformTask) - - // Stashed state should be cleared - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() - } - - @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -1122,29 +1226,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun stashDesktopApps_stateUpdates() { - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - controller.stashDesktopApps(DEFAULT_DISPLAY) - - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue() - assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse() - } - - @Test - fun hideStashedDesktopApps_stateUpdates() { - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true) - desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true) - controller.hideStashedDesktopApps(DEFAULT_DISPLAY) - - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() - // Check that second display is not affected - assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue() - } - - @Test fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() { val task = setUpFreeformTask() clearInvocations(launchAdjacentController) @@ -1225,6 +1306,185 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask() + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, + shouldLetterbox = true) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(isResizable = false, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(isResizable = false, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() { val task = setUpFreeformTask() val mockSurface = mock(SurfaceControl::class.java) @@ -1276,8 +1536,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.toggleDesktopTaskSize(task) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct() - assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds) - .isEqualTo(STABLE_BOUNDS) + assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) } @Test @@ -1304,8 +1563,7 @@ class DesktopTasksControllerTest : ShellTestCase() { // Assert bounds set to last bounds before maximize val wct = getLatestToggleResizeDesktopTaskWct() - assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds) - .isEqualTo(boundsBeforeMaximize) + assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) } @Test @@ -1346,14 +1604,65 @@ class DesktopTasksControllerTest : ShellTestCase() { return task } - private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + private fun setUpFullscreenTask( + displayId: Int = DEFAULT_DISPLAY, + isResizable: Boolean = true, + windowingMode: Int = WINDOWING_MODE_FULLSCREEN, + deviceOrientation: Int = ORIENTATION_LANDSCAPE, + screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED, + shouldLetterbox: Boolean = false + ): RunningTaskInfo { val task = createFullscreenTask(displayId) + val activityInfo = ActivityInfo() + activityInfo.screenOrientation = screenOrientation + with(task) { + topActivityInfo = activityInfo + isResizeable = isResizable + configuration.orientation = deviceOrientation + configuration.windowConfiguration.windowingMode = windowingMode + + if (shouldLetterbox) { + if (deviceOrientation == ORIENTATION_LANDSCAPE && + screenOrientation == SCREEN_ORIENTATION_PORTRAIT) { + // Letterbox to portrait size + appCompatTaskInfo.topActivityBoundsLetterboxed = true + appCompatTaskInfo.topActivityLetterboxWidth = 1200 + appCompatTaskInfo.topActivityLetterboxHeight = 1600 + } else if (deviceOrientation == ORIENTATION_PORTRAIT && + screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) { + // Letterbox to landscape size + appCompatTaskInfo.topActivityBoundsLetterboxed = true + appCompatTaskInfo.topActivityLetterboxWidth = 1600 + appCompatTaskInfo.topActivityLetterboxHeight = 1200 + } + } else { + appCompatTaskInfo.topActivityBoundsLetterboxed = false + } + + if (deviceOrientation == ORIENTATION_LANDSCAPE) { + configuration.windowConfiguration.appBounds = Rect(0, 0, + DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT) + } else { + configuration.windowConfiguration.appBounds = Rect(0, 0, + DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG) + } + } whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task } + private fun setUpLandscapeDisplay() { + whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG) + whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT) + } + + private fun setUpPortraitDisplay() { + whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT) + whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG) + } + private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createSplitScreenTask(displayId) whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) @@ -1418,6 +1727,17 @@ class DesktopTasksControllerTest : ShellTestCase() { return arg.value } + private fun getLatestDragToDesktopWct(): WindowContainerTransaction { + val arg: ArgumentCaptor<WindowContainerTransaction> = + ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + if (ENABLE_SHELL_TRANSITIONS) { + verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg)) + } else { + verify(shellTaskOrganizer).applyTransaction(capture(arg)) + } + return arg.value + } + private fun getLatestExitDesktopWct(): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) if (ENABLE_SHELL_TRANSITIONS) { @@ -1429,6 +1749,10 @@ class DesktopTasksControllerTest : ShellTestCase() { return arg.value } + private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? = + wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds + + private fun verifyWCTNotExecuted() { if (ENABLE_SHELL_TRANSITIONS) { verify(transitions, never()).startTransition(anyInt(), any(), isNull()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 539d5b86453f..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 @@ -32,6 +32,7 @@ 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 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 665077be3af7..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 @@ -35,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; 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 240324ba4420..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 @@ -67,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; 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..aa2cee79fcfc 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 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java index 82e5a1cd25ce..54645083eca8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java @@ -56,6 +56,8 @@ public class DragResizeWindowGeometryTests { private static final Size TASK_SIZE = new Size(500, 1000); private static final int TASK_CORNER_RADIUS = 10; private static final int EDGE_RESIZE_THICKNESS = 15; + private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS + + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0); private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10; private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10; private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry( @@ -90,13 +92,14 @@ public class DragResizeWindowGeometryTests { EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE)) - .addEqualityGroup( + .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, + EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, - LARGE_CORNER_SIZE + 5), + EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5)) + .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, + EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, - LARGE_CORNER_SIZE + 5)) + EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE)) .testEquals(); } @@ -122,21 +125,21 @@ public class DragResizeWindowGeometryTests { private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) { assertThat(region.contains(point.x, point.y)).isTrue(); // Horizontally along the edge is still contained. - assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue(); - assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue(); + assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue(); + assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue(); // Vertically along the edge is not contained. - assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse(); - assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse(); + assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse(); + assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse(); } private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) { assertThat(region.contains(point.x, point.y)).isTrue(); // Horizontally along the edge is not contained. - assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse(); - assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse(); + assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse(); + assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse(); // Vertically along the edge is contained. - assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue(); - assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue(); + assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue(); + assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue(); } /** @@ -148,7 +151,10 @@ public class DragResizeWindowGeometryTests { public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() { Region region = new Region(); GEOMETRY.union(region); - final int cornerRadius = LARGE_CORNER_SIZE / 2; + // Make sure we're choosing a point outside of any debug region buffer. + final int cornerRadius = DragResizeWindowGeometry.DEBUG + ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS) + : LARGE_CORNER_SIZE / 2; new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region); } @@ -162,7 +168,9 @@ public class DragResizeWindowGeometryTests { public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() { Region region = new Region(); GEOMETRY.union(region); - final int cornerRadius = FINE_CORNER_SIZE / 2; + final int cornerRadius = DragResizeWindowGeometry.DEBUG + ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS) + : LARGE_CORNER_SIZE / 2; new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region); } diff --git a/libs/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..8b8cd119effd 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 @@ -75,7 +75,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; 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/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 f97992f7c9d1..cca1b07c3118 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -142,7 +142,7 @@ std::optional<FloatRect> PointerController::getBounds() const { } void PointerController::move(float deltaX, float deltaY) { - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; { std::scoped_lock lock(getLock()); @@ -153,7 +153,7 @@ void PointerController::move(float deltaX, float deltaY) { } void PointerController::setPosition(float x, float y) { - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; { std::scoped_lock lock(getLock()); @@ -164,7 +164,7 @@ void PointerController::setPosition(float x, float y) { } FloatPoint PointerController::getPosition() const { - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); const auto p = mCursorController.getPosition(); { std::scoped_lock lock(getLock()); @@ -173,7 +173,7 @@ FloatPoint PointerController::getPosition() const { } } -int32_t PointerController::getDisplayId() const { +ui::LogicalDisplayId PointerController::getDisplayId() const { return mCursorController.getDisplayId(); } @@ -202,7 +202,7 @@ void PointerController::setPresentation(Presentation presentation) { } void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) { + BitSet32 spotIdBits, ui::LogicalDisplayId displayId) { std::scoped_lock lock(getLock()); std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; const ui::Transform& transform = getTransformForDisplayLocked(displayId); @@ -286,7 +286,7 @@ void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { mCursorController.setCustomPointerIcon(icon); } -void PointerController::setSkipScreenshot(int32_t displayId, bool skip) { +void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) { std::scoped_lock lock(getLock()); if (skip) { mLocked.displaysToSkipScreenshot.insert(displayId); @@ -300,14 +300,14 @@ void PointerController::doInactivityTimeout() { } void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) { - std::unordered_set<int32_t> displayIdSet; + std::unordered_set<ui::LogicalDisplayId> displayIdSet; for (const DisplayViewport& viewport : viewports) { displayIdSet.insert(viewport.displayId); } std::scoped_lock lock(getLock()); for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) { - int32_t displayId = it->first; + ui::LogicalDisplayId displayId = it->first; if (!displayIdSet.count(displayId)) { /* * Ensures that an in-progress animation won't dereference @@ -326,7 +326,8 @@ void PointerController::onDisplayInfosChangedLocked( mLocked.mDisplayInfos = displayInfo; } -const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const { +const ui::Transform& PointerController::getTransformForDisplayLocked( + ui::LogicalDisplayId displayId) const { const auto& di = mLocked.mDisplayInfos; auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) { return info.displayId == displayId; @@ -339,7 +340,8 @@ std::string PointerController::dump() { std::scoped_lock lock(getLock()); dump += StringPrintf(INDENT2 "Presentation: %s\n", ftl::enum_string(mLocked.presentation).c_str()); - dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId); + dump += StringPrintf(INDENT2 "Pointer Display ID: %s\n", + mLocked.pointerDisplayId.toString().c_str()); dump += StringPrintf(INDENT2 "Viewports:\n"); for (const auto& info : mLocked.mDisplayInfos) { info.dump(dump, INDENT3); diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index eaf34d527396..c6430f7f36ff 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -55,18 +55,18 @@ public: void move(float deltaX, float deltaY) override; void setPosition(float x, float y) override; FloatPoint getPosition() const override; - int32_t getDisplayId() const override; + ui::LogicalDisplayId getDisplayId() const override; void fade(Transition transition) override; void unfade(Transition transition) override; void setDisplayViewport(const DisplayViewport& viewport) override; void setPresentation(Presentation presentation) override; void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) override; + BitSet32 spotIdBits, ui::LogicalDisplayId displayId) override; void clearSpots() override; void updatePointerIcon(PointerIconStyle iconId) override; void setCustomPointerIcon(const SpriteIcon& icon) override; - void setSkipScreenshot(int32_t displayId, bool skip) override; + void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override; virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout); void doInactivityTimeout(); @@ -109,11 +109,11 @@ private: struct Locked { Presentation presentation; - int32_t pointerDisplayId = ADISPLAY_ID_NONE; + ui::LogicalDisplayId pointerDisplayId = ui::LogicalDisplayId::INVALID; std::vector<gui::DisplayInfo> mDisplayInfos; - std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers; - std::unordered_set<int32_t /* displayId */> displaysToSkipScreenshot; + std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers; + std::unordered_set<ui::LogicalDisplayId> displaysToSkipScreenshot; } mLocked GUARDED_BY(getLock()); class DisplayInfoListener : public gui::WindowInfosListener { @@ -132,7 +132,8 @@ private: sp<DisplayInfoListener> mDisplayInfoListener; const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener; - const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock()); + const ui::Transform& getTransformForDisplayLocked(ui::LogicalDisplayId displayId) const + REQUIRES(getLock()); void clearSpotsLocked() REQUIRES(getLock()); }; @@ -148,7 +149,7 @@ public: void setPresentation(Presentation) override { LOG_ALWAYS_FATAL("Should not be called"); } - void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override { + void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override { LOG_ALWAYS_FATAL("Should not be called"); } void clearSpots() override { @@ -176,7 +177,7 @@ public: FloatPoint getPosition() const override { LOG_ALWAYS_FATAL("Should not be called"); } - int32_t getDisplayId() const override { + ui::LogicalDisplayId getDisplayId() const override { LOG_ALWAYS_FATAL("Should not be called"); } void fade(Transition) override { @@ -212,7 +213,7 @@ public: void setPresentation(Presentation) override { LOG_ALWAYS_FATAL("Should not be called"); } - void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override { + void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override { LOG_ALWAYS_FATAL("Should not be called"); } void clearSpots() override { diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp index 15c35176afce..747eb8e5ad1b 100644 --- a/libs/input/PointerControllerContext.cpp +++ b/libs/input/PointerControllerContext.cpp @@ -138,12 +138,12 @@ int PointerControllerContext::LooperCallback::handleEvent(int /* fd */, int even return 1; // keep the callback } -void PointerControllerContext::addAnimationCallback(int32_t displayId, +void PointerControllerContext::addAnimationCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback) { mAnimator.addCallback(displayId, callback); } -void PointerControllerContext::removeAnimationCallback(int32_t displayId) { +void PointerControllerContext::removeAnimationCallback(ui::LogicalDisplayId displayId) { mAnimator.removeCallback(displayId); } @@ -161,14 +161,14 @@ void PointerControllerContext::PointerAnimator::initializeDisplayEventReceiver() } } -void PointerControllerContext::PointerAnimator::addCallback(int32_t displayId, +void PointerControllerContext::PointerAnimator::addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback) { std::scoped_lock lock(mLock); mLocked.callbacks[displayId] = callback; startAnimationLocked(); } -void PointerControllerContext::PointerAnimator::removeCallback(int32_t displayId) { +void PointerControllerContext::PointerAnimator::removeCallback(ui::LogicalDisplayId displayId) { std::scoped_lock lock(mLock); auto it = mLocked.callbacks.find(displayId); if (it == mLocked.callbacks.end()) { diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h index e893c49d80e5..d42214883d3a 100644 --- a/libs/input/PointerControllerContext.h +++ b/libs/input/PointerControllerContext.h @@ -72,12 +72,13 @@ protected: virtual ~PointerControllerPolicyInterface() {} public: - virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0; - virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0; + virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) = 0; + virtual void loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId) = 0; virtual void loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, - int32_t displayId) = 0; + ui::LogicalDisplayId displayId) = 0; virtual PointerIconStyle getDefaultPointerIconId() = 0; virtual PointerIconStyle getDefaultStylusIconId() = 0; virtual PointerIconStyle getCustomPointerIconId() = 0; @@ -102,7 +103,7 @@ public: nsecs_t getAnimationTime(); - void clearSpotsByDisplay(int32_t displayId); + void clearSpotsByDisplay(ui::LogicalDisplayId displayId); void setHandlerController(std::shared_ptr<PointerController> controller); void setCallbackController(std::shared_ptr<PointerController> controller); @@ -112,8 +113,9 @@ public: void handleDisplayEvents(); - void addAnimationCallback(int32_t displayId, std::function<bool(nsecs_t)> callback); - void removeAnimationCallback(int32_t displayId); + void addAnimationCallback(ui::LogicalDisplayId displayId, + std::function<bool(nsecs_t)> callback); + void removeAnimationCallback(ui::LogicalDisplayId displayId); class MessageHandler : public virtual android::MessageHandler { public: @@ -136,8 +138,8 @@ private: public: PointerAnimator(PointerControllerContext& context); - void addCallback(int32_t displayId, std::function<bool(nsecs_t)> callback); - void removeCallback(int32_t displayId); + void addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback); + void removeCallback(ui::LogicalDisplayId displayId); void handleVsyncEvents(); nsecs_t getAnimationTimeLocked(); @@ -148,7 +150,7 @@ private: bool animationPending{false}; nsecs_t animationTime{systemTime(SYSTEM_TIME_MONOTONIC)}; - std::unordered_map<int32_t, std::function<bool(nsecs_t)>> callbacks; + std::unordered_map<ui::LogicalDisplayId, std::function<bool(nsecs_t)>> callbacks; } mLocked GUARDED_BY(mLock); DisplayEventReceiver mDisplayEventReceiver; diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index 0baa9291f54d..af499390d390 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -19,9 +19,9 @@ #include "SpriteController.h" -#include <log/log.h> -#include <utils/String8.h> +#include <android-base/logging.h> #include <gui/Surface.h> +#include <utils/String8.h> namespace android { @@ -340,13 +340,14 @@ void SpriteController::ensureSurfaceComposerClient() { } } -sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, int32_t displayId, +sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, + ui::LogicalDisplayId displayId, bool hideOnMirrored) { ensureSurfaceComposerClient(); const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId); if (parent == nullptr) { - ALOGE("Failed to get the parent surface for pointers on display %d", displayId); + LOG(ERROR) << "Failed to get the parent surface for pointers on display " << displayId; } int32_t createFlags = ISurfaceComposerClient::eHidden | ISurfaceComposerClient::eCursorWindow; @@ -475,7 +476,7 @@ void SpriteController::SpriteImpl::setTransformationMatrix( } } -void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) { +void SpriteController::SpriteImpl::setDisplayId(ui::LogicalDisplayId displayId) { AutoMutex _l(mController.mLock); if (mLocked.state.displayId != displayId) { diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index fdb15506fd0c..e147c567ae2d 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -95,7 +95,7 @@ public: virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0; /* Sets the id of the display where the sprite should be shown. */ - virtual void setDisplayId(int32_t displayId) = 0; + virtual void setDisplayId(ui::LogicalDisplayId displayId) = 0; /* Sets the flag to hide sprite on mirrored displays. * This will add ISurfaceComposerClient::eSkipScreenshot flag to the sprite. */ @@ -115,7 +115,7 @@ public: */ class SpriteController { public: - using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>; + using ParentSurfaceProvider = std::function<sp<SurfaceControl>(ui::LogicalDisplayId)>; SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent); SpriteController(const SpriteController&) = delete; SpriteController& operator=(const SpriteController&) = delete; @@ -174,7 +174,7 @@ private: int32_t layer{0}; float alpha{1.0f}; SpriteTransformationMatrix transformationMatrix; - int32_t displayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId displayId{ui::LogicalDisplayId::DEFAULT}; sp<SurfaceControl> surfaceControl; int32_t surfaceWidth{0}; @@ -208,7 +208,7 @@ private: virtual void setLayer(int32_t layer); virtual void setAlpha(float alpha); virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix); - virtual void setDisplayId(int32_t displayId); + virtual void setDisplayId(ui::LogicalDisplayId displayId); virtual void setSkipScreenshot(bool skip); inline const SpriteState& getStateLocked() const { @@ -273,7 +273,7 @@ private: void doDisposeSurfaces(); void ensureSurfaceComposerClient(); - sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId, + sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, ui::LogicalDisplayId displayId, bool hideOnMirrored); }; diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp index 530d54129791..7462481f8779 100644 --- a/libs/input/TouchSpotController.cpp +++ b/libs/input/TouchSpotController.cpp @@ -40,7 +40,7 @@ namespace android { // --- Spot --- void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY, - int32_t displayId, bool skipScreenshot) { + ui::LogicalDisplayId displayId, bool skipScreenshot) { sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); sprite->setAlpha(alpha); sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); @@ -69,7 +69,8 @@ void TouchSpotController::Spot::dump(std::string& out, const char* prefix) const // --- TouchSpotController --- -TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context) +TouchSpotController::TouchSpotController(ui::LogicalDisplayId displayId, + PointerControllerContext& context) : mDisplayId(displayId), mContext(context) { mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId); } @@ -94,7 +95,7 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32 const PointerCoords& c = spotCoords[spotIdToIndex[id]]; ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id, c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y), - c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId); + c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId.id); } #endif @@ -274,7 +275,7 @@ void TouchSpotController::dump(std::string& out, const char* prefix) const { out += prefix; out += "SpotController:\n"; out += prefix; - StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId); + StringAppendF(&out, INDENT "DisplayId: %s\n", mDisplayId.toString().c_str()); std::scoped_lock lock(mLock); out += prefix; StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating)); diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h index 608653c6a2e7..ac37fa430249 100644 --- a/libs/input/TouchSpotController.h +++ b/libs/input/TouchSpotController.h @@ -29,7 +29,7 @@ namespace android { */ class TouchSpotController { public: - TouchSpotController(int32_t displayId, PointerControllerContext& context); + TouchSpotController(ui::LogicalDisplayId displayId, PointerControllerContext& context); ~TouchSpotController(); void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, bool skipScreenshot); @@ -59,7 +59,7 @@ private: y(0.0f), mLastIcon(nullptr) {} - void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId, + void updateSprite(const SpriteIcon* icon, float x, float y, ui::LogicalDisplayId displayId, bool skipScreenshot); void dump(std::string& out, const char* prefix = "") const; @@ -67,7 +67,7 @@ private: const SpriteIcon* mLastIcon; }; - int32_t mDisplayId; + ui::LogicalDisplayId mDisplayId; mutable std::mutex mLock; diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 3bc0e24b6e2e..2dcb1f1d1650 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -52,12 +52,13 @@ std::pair<float, float> getHotSpotCoordinatesForType(int32_t type) { class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface { public: - virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override; - virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override; + virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) override; + virtual void loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId) override; virtual void loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, - int32_t displayId) override; + ui::LogicalDisplayId displayId) override; virtual PointerIconStyle getDefaultPointerIconId() override; virtual PointerIconStyle getDefaultStylusIconId() override; virtual PointerIconStyle getCustomPointerIconId() override; @@ -73,13 +74,13 @@ private: bool additionalMouseResourcesLoaded{false}; }; -void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) { +void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId) { loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT); pointerIconLoaded = true; } void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources, - int32_t) { + ui::LogicalDisplayId) { loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER); loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH); loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR); @@ -88,7 +89,7 @@ void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, - std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) { + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, ui::LogicalDisplayId) { SpriteIcon icon; PointerAnimation anim; @@ -165,7 +166,7 @@ protected: PointerControllerTest(); ~PointerControllerTest(); - void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT); + void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT); sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; @@ -204,7 +205,7 @@ PointerControllerTest::~PointerControllerTest() { mThread.join(); } -void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) { +void PointerControllerTest::ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId) { DisplayViewport viewport; viewport.displayId = displayId; viewport.logicalRight = 1600; @@ -334,23 +335,23 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { // Update spots to sync state with sprite mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Marking the display to skip screenshot should update sprite as well - mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, true); + mPointerController->setSkipScreenshot(ui::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, - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Reset flag and verify again - mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, false); + mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, false); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false)); mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); } diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h index 0867221d9eed..21628fb9f72c 100644 --- a/libs/input/tests/mocks/MockSprite.h +++ b/libs/input/tests/mocks/MockSprite.h @@ -33,7 +33,7 @@ public: MOCK_METHOD(void, setLayer, (int32_t), (override)); MOCK_METHOD(void, setAlpha, (float), (override)); MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override)); - MOCK_METHOD(void, setDisplayId, (int32_t), (override)); + MOCK_METHOD(void, setDisplayId, (ui::LogicalDisplayId), (override)); MOCK_METHOD(void, setSkipScreenshot, (bool), (override)); }; diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h index 62f1d65e77a5..9ef6b7c3b480 100644 --- a/libs/input/tests/mocks/MockSpriteController.h +++ b/libs/input/tests/mocks/MockSpriteController.h @@ -27,7 +27,7 @@ class MockSpriteController : public SpriteController { public: MockSpriteController(sp<Looper> looper) - : SpriteController(looper, 0, [](int) { return nullptr; }) {} + : SpriteController(looper, 0, [](ui::LogicalDisplayId) { return nullptr; }) {} ~MockSpriteController() {} MOCK_METHOD(sp<Sprite>, createSprite, (), (override)); diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 6d4cc3a9ca44..cf7aea405756 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -64,8 +64,10 @@ package android.nfc { } public final class NfcAdapter { + method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(); method public void disableForegroundDispatch(android.app.Activity); method public void disableReaderMode(android.app.Activity); + method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]); method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 310130e59bfe..a33e2252019b 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -3,9 +3,7 @@ package android.nfc { public final class NfcAdapter { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]); - method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean); - method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean); method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState(); diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index e43d10422729..06098deb8aff 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -1106,6 +1106,9 @@ public final class NfcAdapter { * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the * operation is complete. * + * <p>This API is only allowed to be called by system apps + * or apps which are Device Owner or Profile Owner. + * * <p>If this returns true, then either NFC is already on, or * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent * to indicate a state transition. If this returns false, then @@ -1113,9 +1116,8 @@ public final class NfcAdapter { * NFC on (for example we are in airplane mode and NFC is not * toggleable in airplane mode on this platform). * - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable() { try { @@ -1146,15 +1148,17 @@ public final class NfcAdapter { * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the * operation is complete. * + * <p>This API is only allowed to be called by system apps + * or apps which are Device Owner or Profile Owner. + * * <p>If this returns true, then either NFC is already off, or * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent * to indicate a state transition. If this returns false, then * there is some problem that prevents an attempt to turn * NFC off. * - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable() { try { diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index 73b29db0f317..cb2a48c2913f 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -93,3 +93,11 @@ flag { description: "Enable NFC OEM extension support" bug: "331206243" } + +flag { + name: "nfc_state_change" + is_exported: true + namespace: "nfc" + description: "Enable nfc state change API" + bug: "319934052" +} diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml index a33e0dc25d8a..7015c50071a9 100644 --- a/packages/CompanionDeviceManager/res/values-fr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml @@ -38,7 +38,7 @@ <string name="helper_title_computer" msgid="4671071173916176037">"Services Google Play"</string> <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> pour accéder aux photos, contenus multimédias et notifications de votre téléphone"</string> <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Autoriser <strong><xliff:g id="DEVICE_NAME">%1$s</xliff:g></strong> à effectuer cette action ?"</string> - <string name="title_nearby_device_streaming_with_mirroring" msgid="242855799919611657">"Autoriser <strong><xliff:g id="DEVICE_NAME">%1$s</xliff:g></strong> à caster les applications et les fonctionnalités système de votre téléphone ?"</string> + <string name="title_nearby_device_streaming_with_mirroring" msgid="242855799919611657">"Autoriser <strong><xliff:g id="DEVICE_NAME">%1$s</xliff:g></strong> à streamer les applications et les fonctionnalités système de votre téléphone ?"</string> <string name="summary_nearby_device_streaming" msgid="4039565463149145573">"%1$s aura accès à tout ce qui est visible ou lu sur le téléphone, y compris les contenus audio, les photos, les informations de paiement, les mots de passe et les messages.<br/><br/>%1$s pourra caster des applications et des fonctionnalités système jusqu\'à ce que vous supprimiez l\'accès à cette autorisation."</string> <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_NAME">%2$s</xliff:g> de diffuser des applis et d\'autres fonctionnalités système en streaming sur des appareils à proximité"</string> <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string> diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml index 4985ae3cde34..c929ee4dc991 100644 --- a/packages/CompanionDeviceManager/res/values-hu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml @@ -54,7 +54,7 @@ <string name="vendor_header_button_description" msgid="7994879208461111473">"További információ"</string> <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string> <string name="permission_sms" msgid="6337141296535774786">"SMS"</string> - <string name="permission_contacts" msgid="3858319347208004438">"Címtár"</string> + <string name="permission_contacts" msgid="3858319347208004438">"Névjegyek"</string> <string name="permission_calendar" msgid="6805668388691290395">"Naptár"</string> <string name="permission_microphone" msgid="2152206421428732949">"Mikrofon"</string> <string name="permission_call_logs" msgid="5546761417694586041">"Hívásnaplók"</string> diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml index ef27359946b3..f74990925332 100644 --- a/packages/CredentialManager/res/values-es-rUS/strings.xml +++ b/packages/CredentialManager/res/values-es-rUS/strings.xml @@ -91,7 +91,7 @@ <string name="no_sign_in_info_in" msgid="2641118151920288356">"No hay información de acceso en <xliff:g id="SOURCE">%1$s</xliff:g>"</string> <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrar accesos"</string> <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Desde otro dispositivo"</string> - <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otra voz"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otro dispositivo"</string> <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> canceló la solicitud"</string> <string name="dropdown_presentation_more_sign_in_options_text" msgid="1693727354272417902">"Opciones de acceso"</string> <string name="more_options_content_description" msgid="1323427365788198808">"Más"</string> diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml index e3a9191ab225..9885a1f8fa62 100644 --- a/packages/CredentialManager/res/values-or/strings.xml +++ b/packages/CredentialManager/res/values-or/strings.xml @@ -57,9 +57,9 @@ <string name="set_as_default" msgid="4415328591568654603">"ଡିଫଲ୍ଟ ଭାବେ ସେଟ କରନ୍ତୁ"</string> <string name="settings" msgid="6536394145760913145">"ସେଟିଂସ"</string> <string name="use_once" msgid="9027366575315399714">"ଥରେ ବ୍ୟବହାର କରନ୍ତୁ"</string> - <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ଟି ପାସକୀ"</string> - <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ"</string> - <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>ଟି ପାସକୀ"</string> + <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ପାସୱାର୍ଡ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ପାସକୀ"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ପାସୱାର୍ଡ"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ପାସକୀ"</string> <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>ଟି କ୍ରେଡେନସିଆଲ"</string> <string name="passkey_before_subtitle" msgid="2448119456208647444">"ପାସକୀ"</string> <string name="another_device" msgid="5147276802037801217">"ଅନ୍ୟ ଏକ ଡିଭାଇସ"</string> diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml index 44f5eaa543b3..c6ac74358be4 100644 --- a/packages/CredentialManager/res/values-zh-rHK/strings.xml +++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml @@ -74,7 +74,7 @@ <string name="get_dialog_description_single_tap" msgid="2797059565126030879">"使用螢幕鎖定方式以 <xliff:g id="USERNAME">%2$s</xliff:g> 登入 <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="get_dialog_title_unlock_options_for" msgid="7096423827682163270">"解鎖 <xliff:g id="APP_NAME">%1$s</xliff:g> 的登入選項"</string> <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密鑰"</string> - <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼"</string> + <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"選擇「<xliff:g id="APP_NAME">%1$s</xliff:g>」的儲存密碼"</string> <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料"</string> <string name="get_dialog_title_choose_sign_in_for" msgid="645728947702442421">"選擇使用 <xliff:g id="APP_NAME">%1$s</xliff:g> 的帳戶"</string> <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"要選擇適用於「<xliff:g id="APP_NAME">%1$s</xliff:g>」的項目嗎?"</string> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/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/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 25ac3c9d9074..635dc420f18c 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -172,7 +172,7 @@ public class DynamicSystemInstallationService extends Service // This is for testing only now private boolean mEnableWhenCompleted; - private boolean mOneShot; + private boolean mOneShot = true; private boolean mHideNotification; private InstallationAsyncTask.Progress mInstallTaskProgress; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt index d969d1cf0a80..2e9b7b421a7b 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt @@ -446,6 +446,9 @@ class InstallRepository(private val context: Context) { // Returns InstallUserActionRequired stage if install details could be successfully // computed, else it returns InstallAborted. val confirmationSnippet: InstallStage = generateConfirmationSnippet() + if (confirmationSnippet.stageCode == InstallStage.STAGE_ABORTED) { + return confirmationSnippet + } val existingUpdateOwner: CharSequence? = getExistingUpdateOwner(newPackageInfo!!) return if (sessionId == SessionInfo.INVALID_ID && diff --git a/packages/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/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/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/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/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/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/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index 36cd136602f3..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 @@ -79,7 +80,12 @@ 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, ) @@ -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/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt index a49b358ca782..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 @@ -92,6 +94,7 @@ fun SearchScaffold( ) }, 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 af7a14647570..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,7 +23,9 @@ 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 @@ -57,6 +59,7 @@ fun SettingsScaffold( 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/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt index fc409302a2eb..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 @@ -55,7 +57,10 @@ fun SuwScaffold( content: @Composable () -> Unit, ) { ActivityTitle(title) - Scaffold(containerColor = MaterialTheme.colorScheme.settingsBackground) { 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/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/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 89f54d9b3b3b..9c0d29df420f 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -62,3 +62,10 @@ 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" +} diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 1594e8e75cb6..ad5337c3982f 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -105,9 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Regs: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktief"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gestoor"</string> - <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktief (net linkerkant)"</string> - <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktief (net regterkant)"</string> - <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktief (linkerkant en regterkant)"</string> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktief (net links)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktief (net regs)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktief (links en regs)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktief (net media). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiewe (net media). L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> battery"</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Gekoppel (steun oudiodeling). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 0cfae920c039..1a0fec5d431a 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -125,7 +125,7 @@ <string name="bluetooth_profile_opp" msgid="6692618568149493430">"Transferencia de archivos"</string> <string name="bluetooth_profile_hid" msgid="2969922922664315866">"Dispositivo de entrada"</string> <string name="bluetooth_profile_pan" msgid="1006235139308318188">"Acceso a Internet"</string> - <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"Acceso a contactos e historial de llamadas"</string> + <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"Acceso a historial de llamadas y contactos"</string> <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"Se usará la información para anuncios de llamadas y más"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Compartir conexión a Internet"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"Mensajes de texto"</string> @@ -281,7 +281,7 @@ <string name="keep_screen_on_summary" msgid="1510731514101925829">"La pantalla nunca quedará inactiva mientras el dispositivo se esté cargando"</string> <string name="bt_hci_snoop_log" msgid="7291287955649081448">"Registro de Bluetooth HCI"</string> <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Capturar paquetes de Bluetooth (activa/desactiva el Bluetooth después de cambiar esta configuración)"</string> - <string name="oem_unlock_enable" msgid="5334869171871566731">"Desbloqueo de OEM"</string> + <string name="oem_unlock_enable" msgid="5334869171871566731">"Desbloqueo para OEM"</string> <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Permitir que el cargador de inicio se desbloquee"</string> <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"¿Permitir desbloqueo de OEM?"</string> <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ADVERTENCIA: Las funciones de protección de dispositivos no funcionarán en este dispositivo mientras esta configuración esté activada."</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 497fbbb13a67..c330b7684fe5 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -294,7 +294,7 @@ <string name="wifi_scan_throttling" msgid="2985624788509913617">"Wifi-sareen bilaketaren moteltzea"</string> <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wifi-konexioetan iraunkorrak ez diren MAC helbideak ausaz antolatzea"</string> <string name="mobile_data_always_on" msgid="8275958101875563572">"Datu-konexioa beti aktibo"</string> - <string name="tethering_hardware_offload" msgid="4116053719006939161">"Konexioa partekatzeko hardwarearen azelerazioa"</string> + <string name="tethering_hardware_offload" msgid="4116053719006939161">"Konexioa partekatzeko hardwarearen bizkortzea"</string> <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Erakutsi Bluetooth bidezko gailuak izenik gabe"</string> <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Desgaitu bolumen absolutua"</string> <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gaitu Gabeldorsche"</string> @@ -339,7 +339,7 @@ <string name="allow_mock_location_summary" msgid="179780881081354579">"Onartu kokapen faltsuak"</string> <string name="debug_view_attributes" msgid="3539609843984208216">"Gaitu ikuspegiaren atributuak ikuskatzeko aukera"</string> <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Mantendu datu-konexioa beti aktibo, baita wifi-konexioa aktibo dagoenean ere (sare batetik bestera bizkor aldatu ahal izateko)."</string> - <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Erabilgarri badago, erabili konexioa partekatzeko hardwarearen azelerazioa"</string> + <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Erabilgarri badago, erabili konexioa partekatzeko hardwarearen bizkortzea"</string> <string name="adb_warning_title" msgid="7708653449506485728">"USB bidezko arazketa onartu?"</string> <string name="adb_warning_message" msgid="8145270656419669221">"USB bidezko arazketa garapen-xedeetarako soilik dago diseinatuta. Erabil ezazu ordenagailuaren eta gailuaren artean datuak kopiatzeko, aplikazioak gailuan jakinarazi gabe instalatzeko eta erregistro-datuak irakurtzeko."</string> <string name="adbwifi_warning_title" msgid="727104571653031865">"Hari gabeko arazketa baimendu nahi duzu?"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 5e7f998a7e41..1d331fbf390c 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -96,8 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"متصل (بدون تلفن یا رسانه)، باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"فعال. باتری: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"فعال. باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>، باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> - <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"فعال. باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> - <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"فعال. باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"فعال. چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> باتری."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"فعال. راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> باتری."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> شارژ باتری"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>، باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index e434d1efaabf..cfb6546057ca 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -126,7 +126,7 @@ <string name="bluetooth_profile_hid" msgid="2969922922664315866">"इनपुट डिवाइस"</string> <string name="bluetooth_profile_pan" msgid="1006235139308318188">"इंटरनेट का ऐक्सेस"</string> <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"संपर्क और कॉल इतिहास का ऐक्सेस दें"</string> - <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"इस जानकारी का इस्तेमाल, कॉल की सूचना देने और दूसरी चीज़ों के लिए किया जाएगा"</string> + <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"इस जानकारी का इस्तेमाल, कॉल की सूचना देने वगैरह के लिए किया जाएगा"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"इंटरनेट कनेक्शन साझाकरण"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"लेख संदेश"</string> <string name="bluetooth_profile_sap" msgid="8304170950447934386">"सिम का ऐक्सेस"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 4c0712c74ad1..5e3d779c34a2 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -159,7 +159,7 @@ <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Չեղարկել"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Զուգակցում է մուտքի թույլտվությունը դեպի ձեր կոնտակտները և զանգերի պատմությունը, երբ միացված է:"</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ:"</string> - <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ սխալ PIN-ի կամ անցաբառի պատճառով:"</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ՝ սխալ PIN-ի կամ անցաբառի պատճառով:"</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Հնարավոր չէ կապ հաստատել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ:"</string> <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Զուգավորումը մերժվեց <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի կողմից:"</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Համակարգիչ"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 4cd076b77fb9..be8d039ec07d 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -94,7 +94,7 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (без телефон), ниво на батеријата <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (без аудиовизуелни содржини), ниво на батеријата <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (без телефон и аудиовизуелни содржини), ниво на батеријата <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Активно. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Активно. Батерија: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Активно. Л: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> батерија, Д: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> батерија."</string> <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Активно. Л: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Активно. Д: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> @@ -159,7 +159,7 @@ <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Откажи"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Кога е поврзано, спарувањето одобрува пристап до контактите и историјата на повиците."</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Не може да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> - <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Не може да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g> поради погрешен PIN или лозинка."</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Не може да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g> поради погрешен PIN или криптографски клуч."</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Не може да комуницира со <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Спарувањето е одбиено од <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Компјутер"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index 42797d40ebaf..66efe8b3cf9a 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -94,7 +94,7 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Холбогдсон (утас байхгүй), батарей <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Холбогдсон (медиа байхгүй), батарей <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Холбогдсон (утас эсвэл медиа байхгүй), батарей <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Идэвхтэй байна. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Идэвхтэй байна. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> цэнэгтэй."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Идэвхтэй байна. З: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Б: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> батарей."</string> <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Идэвхтэй байна. З: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Идэвхтэй байна. Б: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index 40d604e39954..8bafef3caf4f 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -157,7 +157,7 @@ <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"कनेक्ट गर्नुहोस्"</string> <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"जोडी"</string> <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"रद्द गर्नुहोस्"</string> - <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"जब जडान हुन्छ जोडी अनुदानले तपाईँको सम्पर्कहरू पहुँच गर्छ र इतिहास सम्झाउँछ।"</string> + <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"जब जडान हुन्छ जोडी अनुदानले तपाईँको कन्ट्याक्टहरू पहुँच गर्छ र इतिहास सम्झाउँछ।"</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>सँग जोडा मिलाउन सकेन"</string> <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"गलत PIN वा पासकीका कारणले <xliff:g id="DEVICE_NAME">%1$s</xliff:g> मा कनेक्ट गर्न सकिएन।"</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> सँग कुराकानी हुन सक्दैन।"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 422d0f2c6256..d054d9b08d68 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -110,11 +110,11 @@ <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ativo (esquerdo e direito)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Ativo (apenas mídia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Ativo (apenas mídia). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (com suporte ao compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (com suporte ao compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (com suporte ao compartilhamento de áudio)"</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (aceita compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (aceita compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (aceita compartilhamento de áudio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Ativo (apenas mídia)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Com suporte ao compartilhamento de áudio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Ativo (apenas mídia), apenas esquerdo"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 422d0f2c6256..d054d9b08d68 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -110,11 +110,11 @@ <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ativo (esquerdo e direito)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Ativo (apenas mídia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Ativo (apenas mídia). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (com suporte ao compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (com suporte ao compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (com suporte ao compartilhamento de áudio)"</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (aceita compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (aceita compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (aceita compartilhamento de áudio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Ativo (apenas mídia)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Com suporte ao compartilhamento de áudio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Ativo (apenas mídia), apenas esquerdo"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 54c9b0a71980..aaf648ea98a5 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -94,7 +94,7 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Подключено (кроме звонков), уровень заряда батареи: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Подключено (кроме аудио), уровень заряда батареи: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Подключено (кроме звонков и аудио), уровень заряда батареи: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (Л), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (П)."</string> <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (Л)"</string> <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (П)"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index 4286c8144fb0..ed0b1dffd086 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -159,7 +159,7 @@ <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"منسوخ کریں"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"منسلک ہونے پر جوڑا بنانے سے آپ کے رابطوں اور کال کی سرگزشت تک رسائی حاصل ہو جاتی ہے۔"</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا نہیں بنا سکا۔"</string> - <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"غلط PIN یا پاس کلید کی وجہ سے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا نہیں بنا سکا۔"</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"غلط PIN یا پاس کلید کی وجہ سے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا نہیں بنایا جا سکا۔"</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ مواصلت نہیں ہو سکتی۔"</string> <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> نے جوڑا بنانے کو مسترد کر دیا۔"</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"کمپیوٹر"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 56282ec6fed1..25ca0c214d69 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -706,9 +706,9 @@ <string name="physical_keyboard_title" msgid="4811935435315835220">"实体键盘"</string> <string name="keyboard_layout_dialog_title" msgid="3927180147005616290">"选择键盘布局"</string> <string name="keyboard_layout_default_label" msgid="1997292217218546957">"默认"</string> - <string name="turn_screen_on_title" msgid="3266937298097573424">"开启屏幕"</string> + <string name="turn_screen_on_title" msgid="3266937298097573424">"唤醒屏幕"</string> <string name="allow_turn_screen_on" msgid="6194845766392742639">"允许开启屏幕"</string> - <string name="allow_turn_screen_on_description" msgid="43834403291575164">"允许应用开启屏幕。如获授权,该应用便可在您未明确表达意愿的情况下随时开启屏幕。"</string> + <string name="allow_turn_screen_on_description" msgid="43834403291575164">"允许应用唤醒屏幕。如获授权,该应用便可在您未明确表达意愿的情况下随时唤醒屏幕。"</string> <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"要停止广播“<xliff:g id="APP_NAME">%1$s</xliff:g>”的内容吗?"</string> <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如果广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容或更改输出来源,当前的广播就会停止"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index adbfc72cd684..4ea746007f76 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1712,7 +1712,7 @@ <string name="keyboard_layout_default_label">Default</string> <!-- Special access > Title for managing turn screen on settings. [CHAR LIMIT=50] --> - <string name="turn_screen_on_title">Turn screen on</string> + <string name="turn_screen_on_title">Screen turn-on control</string> <!-- Label for a setting which controls whether an app can turn the screen on [CHAR LIMIT=45] --> <string name="allow_turn_screen_on">Allow turning the screen on</string> <!-- Description for a setting which controls whether an app can turn the screen on [CHAR LIMIT=NONE] --> diff --git a/packages/SettingsLib/src/com/android/settingslib/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..89174125a296 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -10,8 +10,10 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; @@ -37,12 +39,9 @@ import com.android.settingslib.flags.Flags; import com.android.settingslib.widget.AdaptiveIcon; import com.android.settingslib.widget.AdaptiveOutlineDrawable; -import com.google.common.collect.ImmutableSet; - import java.io.IOException; import java.util.List; import java.util.Locale; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -56,8 +55,6 @@ public class BluetoothUtils { public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled"; private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH"; - private static final Set<String> EXCLUSIVE_MANAGERS = - ImmutableSet.of("com.google.android.gms.dck"); private static ErrorListener sErrorListener; @@ -740,14 +737,13 @@ public class BluetoothUtils { /** * Returns the BluetoothDevice's exclusive manager ({@link - * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given - * set, otherwise null. + * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists, otherwise null. */ @Nullable - private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) { - byte[] exclusiveManagerNameBytes = + private static String getExclusiveManager(BluetoothDevice bluetoothDevice) { + byte[] exclusiveManagerBytes = bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER); - if (exclusiveManagerNameBytes == null) { + if (exclusiveManagerBytes == null) { Log.d( TAG, "Bluetooth device " @@ -755,47 +751,46 @@ public class BluetoothUtils { + " doesn't have exclusive manager"); return null; } - String exclusiveManagerName = new String(exclusiveManagerNameBytes); - return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null; + return new String(exclusiveManagerBytes); } - /** Checks if given package is installed */ - private static boolean isPackageInstalled(Context context, String packageName) { + /** Checks if given package is installed and enabled */ + private static boolean isPackageInstalledAndEnabled(Context context, String packageName) { PackageManager packageManager = context.getPackageManager(); try { - packageManager.getPackageInfo(packageName, 0); - return true; + ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0); + return appInfo.enabled; } catch (PackageManager.NameNotFoundException e) { - Log.d(TAG, "Package " + packageName + " is not installed"); + Log.d(TAG, "Package " + packageName + " is not installed/enabled"); } return false; } /** * A BluetoothDevice is exclusively managed if 1) it has field {@link - * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is - * in the allowlist. 3) the exclusive manager app is installed. + * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app is + * installed and enabled. */ public static boolean isExclusivelyManagedBluetoothDevice( @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) { - String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice); + String exclusiveManagerName = getExclusiveManager(bluetoothDevice); if (exclusiveManagerName == null) { return false; } - if (!isPackageInstalled(context, exclusiveManagerName)) { + + ComponentName exclusiveManagerComponent = + ComponentName.unflattenFromString(exclusiveManagerName); + String exclusiveManagerPackage = exclusiveManagerComponent != null + ? exclusiveManagerComponent.getPackageName() : exclusiveManagerName; + + if (!isPackageInstalledAndEnabled(context, exclusiveManagerPackage)) { return false; } else { - Log.d(TAG, "Found exclusively managed app " + exclusiveManagerName); + Log.d(TAG, "Found exclusively managed app " + exclusiveManagerPackage); return true; } } - /** Return the allowlist for exclusive manager names. */ - @NonNull - public static Set<String> getExclusiveManagers() { - return EXCLUSIVE_MANAGERS; - } - /** * Get CSIP group id for {@link CachedBluetoothDevice}. * diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 30bec7724dd5..c2a83b1e772f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -68,6 +68,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -1442,14 +1443,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> int stringRes = R.string.bluetooth_pairing; //when profile is connected, information would be available if (profileConnected) { - // Update Meta data for connected device - if (BluetoothUtils.getBooleanMetaData( - mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { - leftBattery = BluetoothUtils.getIntMetaData(mDevice, - BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY); - rightBattery = BluetoothUtils.getIntMetaData(mDevice, - BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY); - } + leftBattery = getLeftBatteryLevel(); + rightBattery = getRightBatteryLevel(); // Set default string with battery level in device connected situation. if (isTwsBatteryAvailable(leftBattery, rightBattery)) { @@ -1485,7 +1480,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio && isConnectedHapClientDevice(); if (isActiveAshaHearingAid || isActiveLeAudioHearingAid) { - return getHearingDeviceSummary(leftBattery, rightBattery, shortSummary); + stringRes = getHearingDeviceSummaryRes(leftBattery, rightBattery, shortSummary); } } } @@ -1498,6 +1493,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean summaryIncludesBatteryLevel = stringRes == R.string.bluetooth_battery_level || stringRes == R.string.bluetooth_active_battery_level || stringRes == R.string.bluetooth_active_battery_level_untethered + || stringRes == R.string.bluetooth_active_battery_level_untethered_left + || stringRes == R.string.bluetooth_active_battery_level_untethered_right || stringRes == R.string.bluetooth_battery_level_untethered; if (isTvSummary && summaryIncludesBatteryLevel && Flags.enableTvMediaOutputDialog()) { return getTvBatterySummary( @@ -1510,6 +1507,14 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (isTwsBatteryAvailable(leftBattery, rightBattery)) { return mContext.getString(stringRes, Utils.formatPercentage(leftBattery), Utils.formatPercentage(rightBattery)); + } else if (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN + && !BluetoothUtils.getBooleanMetaData(mDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + return mContext.getString(stringRes, Utils.formatPercentage(leftBattery)); + } else if (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN + && !BluetoothUtils.getBooleanMetaData(mDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + return mContext.getString(stringRes, Utils.formatPercentage(rightBattery)); } else { return mContext.getString(stringRes, batteryLevelPercentageString); } @@ -1553,60 +1558,34 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return spannableBuilder; } - private CharSequence getHearingDeviceSummary(int leftBattery, int rightBattery, + private int getHearingDeviceSummaryRes(int leftBattery, int rightBattery, boolean shortSummary) { + boolean isLeftDeviceConnected = getConnectedHearingAidSide( + HearingAidInfo.DeviceSide.SIDE_LEFT).isPresent(); + boolean isRightDeviceConnected = getConnectedHearingAidSide( + HearingAidInfo.DeviceSide.SIDE_RIGHT).isPresent(); + boolean shouldShowLeftBattery = + !shortSummary && (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + boolean shouldShowRightBattery = + !shortSummary && (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN); - CachedBluetoothDevice memberDevice = getMemberDevice().stream().filter( - CachedBluetoothDevice::isConnected).findFirst().orElse(null); - if (memberDevice == null && mSubDevice != null && mSubDevice.isConnected()) { - memberDevice = mSubDevice; - } - - CachedBluetoothDevice leftDevice = null; - CachedBluetoothDevice rightDevice = null; - final int deviceSide = getDeviceSide(); - if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) { - leftDevice = this; - rightDevice = memberDevice; - } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) { - leftDevice = memberDevice; - rightDevice = this; - } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) { - leftDevice = this; - rightDevice = this; + if (isLeftDeviceConnected && isRightDeviceConnected) { + return (shouldShowLeftBattery && shouldShowRightBattery) + ? R.string.bluetooth_active_battery_level_untethered + : R.string.bluetooth_hearing_aid_left_and_right_active; } - - if (leftBattery < 0 && leftDevice != null) { - leftBattery = leftDevice.getBatteryLevel(); + if (isLeftDeviceConnected) { + return shouldShowLeftBattery + ? R.string.bluetooth_active_battery_level_untethered_left + : R.string.bluetooth_hearing_aid_left_active; } - if (rightBattery < 0 && rightDevice != null) { - rightBattery = rightDevice.getBatteryLevel(); - } - - if (leftDevice != null && rightDevice != null) { - if (leftBattery >= 0 && rightBattery >= 0 && !shortSummary) { - return mContext.getString(R.string.bluetooth_active_battery_level_untethered, - Utils.formatPercentage(leftBattery), Utils.formatPercentage(rightBattery)); - } else { - return mContext.getString(R.string.bluetooth_hearing_aid_left_and_right_active); - } - } else if (leftDevice != null) { - if (leftBattery >= 0 && !shortSummary) { - return mContext.getString(R.string.bluetooth_active_battery_level_untethered_left, - Utils.formatPercentage(leftBattery)); - } else { - return mContext.getString(R.string.bluetooth_hearing_aid_left_active); - } - } else if (rightDevice != null) { - if (rightBattery >= 0 && !shortSummary) { - return mContext.getString(R.string.bluetooth_active_battery_level_untethered_right, - Utils.formatPercentage(rightBattery)); - } else { - return mContext.getString(R.string.bluetooth_hearing_aid_right_active); - } + if (isRightDeviceConnected) { + return shouldShowRightBattery + ? R.string.bluetooth_active_battery_level_untethered_right + : R.string.bluetooth_hearing_aid_right_active; } - return mContext.getString(R.string.bluetooth_active_no_battery_level); + return R.string.bluetooth_active_no_battery_level; } private void addBatterySpan(SpannableStringBuilder builder, @@ -1632,6 +1611,56 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return leftBattery >= 0 && rightBattery >= 0; } + private Optional<CachedBluetoothDevice> getConnectedHearingAidSide( + @HearingAidInfo.DeviceSide int side) { + return Stream.concat(Stream.of(this, mSubDevice), mMemberDevices.stream()) + .filter(Objects::nonNull) + .filter(device -> device.getDeviceSide() == side + || device.getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) + .filter(device -> device.getDevice().isConnected()) + // For hearing aids, we should expect only one device assign to one side, but if + // it happens, we don't care which one. + .findAny(); + } + + private int getLeftBatteryLevel() { + int leftBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; + if (BluetoothUtils.getBooleanMetaData(mDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + leftBattery = BluetoothUtils.getIntMetaData(mDevice, + BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY); + } + + // Retrieve hearing aids (ASHA, HAP) individual side battery level + if (leftBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { + leftBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_LEFT) + .map(CachedBluetoothDevice::getBatteryLevel) + .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) + .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + } + + return leftBattery; + } + + private int getRightBatteryLevel() { + int rightBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; + if (BluetoothUtils.getBooleanMetaData( + mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + rightBattery = BluetoothUtils.getIntMetaData(mDevice, + BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY); + } + + // Retrieve hearing aids (ASHA, HAP) individual side battery level + if (rightBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { + rightBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_RIGHT) + .map(CachedBluetoothDevice::getBatteryLevel) + .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) + .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + } + + return rightBattery; + } + private boolean isProfileConnectedFail() { Log.d(TAG, "anonymizedAddress=" + mDevice.getAnonymizedAddress() + " mIsA2dpProfileConnectedFail=" + mIsA2dpProfileConnectedFail diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java index 822a60889931..1040ac6bc860 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java @@ -36,6 +36,7 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.GuardedBy; import androidx.annotation.VisibleForTesting; import com.android.internal.telephony.SmsApplication; @@ -56,13 +57,22 @@ public class PowerAllowlistBackend { private static PowerAllowlistBackend sInstance; + private final Object mAllowlistedAppsLock = new Object(); + private final Object mSysAllowlistedAppsLock = new Object(); + private final Object mDefaultActiveAppsLock = new Object(); + private final Context mAppContext; private final IDeviceIdleController mDeviceIdleService; + + @GuardedBy("mAllowlistedAppsLock") private final ArraySet<String> mAllowlistedApps = new ArraySet<>(); + @GuardedBy("mSysAllowlistedAppsLock") private final ArraySet<String> mSysAllowlistedApps = new ArraySet<>(); + @GuardedBy("mDefaultActiveAppsLock") private final ArraySet<String> mDefaultActiveApps = new ArraySet<>(); - public PowerAllowlistBackend(Context context) { + @VisibleForTesting + PowerAllowlistBackend(Context context) { this(context, IDeviceIdleController.Stub.asInterface( ServiceManager.getService(DEVICE_IDLE_SERVICE))); } @@ -75,24 +85,25 @@ public class PowerAllowlistBackend { } public int getAllowlistSize() { - return mAllowlistedApps.size(); + synchronized (mAllowlistedAppsLock) { + return mAllowlistedApps.size(); + } } - /** - * Check if target package is in System allow list - */ + /** Check if target package is in System allow list */ public boolean isSysAllowlisted(String pkg) { - return mSysAllowlistedApps.contains(pkg); + synchronized (mSysAllowlistedAppsLock) { + return mSysAllowlistedApps.contains(pkg); + } } - /** - * Check if target package is in allow list - */ + /** Check if target package is in allow list */ public boolean isAllowlisted(String pkg, int uid) { - if (mAllowlistedApps.contains(pkg)) { - return true; + synchronized (mAllowlistedAppsLock) { + if (mAllowlistedApps.contains(pkg)) { + return true; + } } - if (isDefaultActiveApp(pkg, uid)) { return true; } @@ -100,16 +111,16 @@ public class PowerAllowlistBackend { return false; } - /** - * Check if it is default active app in multiple area(i.e. SMS, Dialer, Device admin..) - */ + /** Check if it is default active app in multiple area */ public boolean isDefaultActiveApp(String pkg, int uid) { // Additionally, check if pkg is default dialer/sms. They are considered essential apps and // should be automatically allowlisted (otherwise user may be able to set restriction on // them, leading to bad device behavior.) - if (mDefaultActiveApps.contains(pkg)) { - return true; + synchronized (mDefaultActiveAppsLock) { + if (mDefaultActiveApps.contains(pkg)) { + return true; + } } final DevicePolicyManager devicePolicyManager = mAppContext.getSystemService( @@ -143,9 +154,7 @@ public class PowerAllowlistBackend { DEFAULT_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED); } - /** - * Check if target package is in allow list except idle app - */ + /** Check if target package is in allow list except idle app */ public boolean isAllowlistedExceptIdle(String pkg) { try { return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg); @@ -156,6 +165,7 @@ public class PowerAllowlistBackend { } /** + * Check if target package is in allow list except idle app * * @param pkgs a list of packageName * @return true when one of package is in allow list @@ -174,20 +184,21 @@ public class PowerAllowlistBackend { } /** - * Add app into power save allow list. + * Add app into power save allow list + * * @param pkg packageName of the app */ - // TODO: Fix all callers to pass in UID public void addApp(String pkg) { addApp(pkg, Process.INVALID_UID); } /** - * Add app into power save allow list. + * Add app into power save allow list + * * @param pkg packageName of the app * @param uid uid of the app */ - public void addApp(String pkg, int uid) { + public synchronized void addApp(String pkg, int uid) { try { if (android.app.Flags.appRestrictionsApi()) { if (uid == Process.INVALID_UID) { @@ -204,7 +215,9 @@ public class PowerAllowlistBackend { } mDeviceIdleService.addPowerSaveWhitelistApp(pkg); - mAllowlistedApps.add(pkg); + synchronized (mAllowlistedAppsLock) { + mAllowlistedApps.add(pkg); + } } catch (RemoteException e) { Log.w(TAG, "Unable to reach IDeviceIdleController", e); } catch (NameNotFoundException e) { @@ -213,7 +226,8 @@ public class PowerAllowlistBackend { } /** - * Remove package from power save allow list. + * Remove package from power save allow list + * * @param pkg packageName of the app */ public void removeApp(String pkg) { @@ -222,10 +236,11 @@ public class PowerAllowlistBackend { /** * Remove package from power save allow list. + * * @param pkg packageName of the app * @param uid uid of the app */ - public void removeApp(String pkg, int uid) { + public synchronized void removeApp(String pkg, int uid) { try { if (android.app.Flags.appRestrictionsApi()) { if (uid == Process.INVALID_UID) { @@ -241,7 +256,9 @@ public class PowerAllowlistBackend { } mDeviceIdleService.removePowerSaveWhitelistApp(pkg); - mAllowlistedApps.remove(pkg); + synchronized (mAllowlistedAppsLock) { + mAllowlistedApps.remove(pkg); + } } catch (RemoteException e) { Log.w(TAG, "Unable to reach IDeviceIdleController", e); } catch (NameNotFoundException e) { @@ -249,25 +266,33 @@ public class PowerAllowlistBackend { } } - /** - * Refresh all of lists - */ + /** Refresh all of lists */ @VisibleForTesting - public void refreshList() { - mSysAllowlistedApps.clear(); - mAllowlistedApps.clear(); - mDefaultActiveApps.clear(); + public synchronized void refreshList() { + synchronized (mSysAllowlistedAppsLock) { + mSysAllowlistedApps.clear(); + } + synchronized (mAllowlistedAppsLock) { + mAllowlistedApps.clear(); + } + synchronized (mDefaultActiveAppsLock) { + mDefaultActiveApps.clear(); + } if (mDeviceIdleService == null) { return; } try { final String[] allowlistedApps = mDeviceIdleService.getFullPowerWhitelist(); - for (String app : allowlistedApps) { - mAllowlistedApps.add(app); + synchronized (mAllowlistedAppsLock) { + for (String app : allowlistedApps) { + mAllowlistedApps.add(app); + } } final String[] sysAllowlistedApps = mDeviceIdleService.getSystemPowerWhitelist(); - for (String app : sysAllowlistedApps) { - mSysAllowlistedApps.add(app); + synchronized (mSysAllowlistedAppsLock) { + for (String app : sysAllowlistedApps) { + mSysAllowlistedApps.add(app); + } } final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEPHONY); @@ -278,26 +303,28 @@ public class PowerAllowlistBackend { if (hasTelephony) { if (defaultSms != null) { - mDefaultActiveApps.add(defaultSms.getPackageName()); + synchronized (mDefaultActiveAppsLock) { + mDefaultActiveApps.add(defaultSms.getPackageName()); + } } if (!TextUtils.isEmpty(defaultDialer)) { - mDefaultActiveApps.add(defaultDialer); + synchronized (mDefaultActiveAppsLock) { + mDefaultActiveApps.add(defaultDialer); + } } } - } catch (RemoteException e) { - Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } catch (Exception e) { + Log.e(TAG, "Failed to invoke refreshList()", e); } } - /** - * @param context - * @return a PowerAllowlistBackend object - */ + /** Get the {@link PowerAllowlistBackend} instance */ public static PowerAllowlistBackend getInstance(Context context) { - if (sInstance == null) { - sInstance = new PowerAllowlistBackend(context); + synchronized (PowerAllowlistBackend.class) { + if (sInstance == null) { + sInstance = new PowerAllowlistBackend(context); + } + return sInstance; } - return sInstance; } - } diff --git a/packages/SettingsLib/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/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/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/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..7a2818dbf299 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -28,7 +28,7 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.content.Context; -import android.content.pm.PackageInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.AudioManager; @@ -80,7 +80,8 @@ public class BluetoothUtilsTest { private static final String CONTROL_METADATA = "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>"; - private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"; + private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager"; + private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component"; @Before public void setUp() { @@ -399,7 +400,7 @@ public class BluetoothUtilsTest { } @Test - public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() { + public void isExclusivelyManaged_hasNoManager_returnFalse() { when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( null); @@ -408,45 +409,85 @@ public class BluetoothUtilsTest { } @Test - public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() { + public void isExclusivelyManaged_hasPackageName_packageNotInstalled_returnFalse() + throws Exception { when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - FAKE_EXCLUSIVE_MANAGER_NAME.getBytes()); + TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager) + .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice)).isEqualTo(false); } @Test - public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse() + public void isExclusivelyManaged_hasComponentName_packageNotInstalled_returnFalse() throws Exception { - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( + TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager) + .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); + + assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, + mBluetoothDevice)).isEqualTo(false); + } + @Test + public void isExclusivelyManaged_hasPackageName_packageNotEnabled_returnFalse() + throws Exception { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.enabled = false; + when(mContext.getPackageManager()).thenReturn(mPackageManager); + doReturn(appInfo).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); + TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes()); + + assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, + mBluetoothDevice)).isEqualTo(false); + } + + @Test + public void isExclusivelyManaged_hasComponentName_packageNotEnabled_returnFalse() + throws Exception { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.enabled = false; when(mContext.getPackageManager()).thenReturn(mPackageManager); - doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo( - exclusiveManagerName, 0); + doReturn(appInfo).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( + TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes()); assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, - mBluetoothDevice)).isEqualTo(false); + mBluetoothDevice)).isEqualTo(false); } @Test - public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue() + public void isExclusivelyManaged_hasPackageName_packageInstalledAndEnabled_returnTrue() throws Exception { - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( + TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); + + assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, + mBluetoothDevice)).isEqualTo(true); + } + @Test + public void isExclusivelyManaged_hasComponentName_packageInstalledAndEnabled_returnTrue() + throws Exception { when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); + TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes()); when(mContext.getPackageManager()).thenReturn(mPackageManager); - doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0); + doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, - mBluetoothDevice)).isEqualTo(true); + mBluetoothDevice)).isEqualTo(true); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index b4bd4826ea08..b9bf9caddac7 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -753,33 +753,71 @@ public class CachedBluetoothDeviceTest { } @Test - public void getConnectionSummary_testHearingAidBatteryWithoutInCall_returnActiveBattery() { + public void getConnectionSummary_testHearingAidLeftEarBatteryNotInCall_returnActiveBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} // 2. Battery Level: 10 // 3. Audio Manager: Normal (Without In Call) updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery"); + // Get "Active. L: 10% battery." result with Battery Level 10. + assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery."); } @Test - public void getTvConnectionSummary_testHearingAidBatteryWithoutInCall_returnBattery() { + public void getTvConnectionSummary_testHearingAidLeftEarBatteryWithoutInCall_returnBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} // 2. Battery Level: 10 // 3. Audio Manager: Normal (Without In Call) updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%"); + // Get "Left: 10% battery" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( + "Left: 10% battery"); + } + + @Test + public void getConnectionSummary_testHearingAidLeftEarBatteryInCall_returnActiveBattery() { + // Arrange: + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} + // 2. Battery Level: 10 + // 3. Audio Manager: In Call + updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mAudioManager.setMode(AudioManager.MODE_IN_CALL); + mBatteryLevel = 10; + + // Act & Assert: + // Get "Active. L: 10% battery." result with Battery Level 10. + assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery."); + } + + @Test + public void getTvConnectionSummary_testHearingAidLeftEarBatteryInCall_returnBattery() { + // Arrange: + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} + // 2. Battery Level: 10 + // 3. Audio Manager: In Call + updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mAudioManager.setMode(AudioManager.MODE_IN_CALL); + mBatteryLevel = 10; + + // Act & Assert: + // Get "Left: 10% battery" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( + "Left: 10% battery"); } @Test @@ -851,35 +889,45 @@ public class CachedBluetoothDeviceTest { } @Test - public void getConnectionSummary_testHearingAidBatteryInCall_returnActiveBattery() { + public void getConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Both ear} // 2. Battery Level: 10 // 3. Audio Manager: In Call + mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo()); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setSubDevice(mSubCachedDevice); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mAudioManager.setMode(AudioManager.MODE_IN_CALL); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery"); + // Get "Active. L: 10%, R: 10% battery." result with Battery Level 10. + assertThat(mCachedDevice.getConnectionSummary().toString()) + .isEqualTo("Active. L: 10%, R: 10% battery."); } @Test - public void getTvConnectionSummary_testHearingAidBatteryInCall_returnBattery() { + public void getTvConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Both ear} // 2. Battery Level: 10 // 3. Audio Manager: In Call + mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo()); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setSubDevice(mSubCachedDevice); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mAudioManager.setMode(AudioManager.MODE_IN_CALL); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%"); + // Get "Left: 10% battery Right: 10% battery" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()) + .isEqualTo("Left: 10% battery Right: 10% battery"); } @Test @@ -1151,7 +1199,7 @@ public class CachedBluetoothDeviceTest { updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP); when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( "true".getBytes()); when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn( @@ -1160,7 +1208,7 @@ public class CachedBluetoothDeviceTest { TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getConnectionSummary()).isEqualTo( - "Active, L: 15% battery, R: 25% battery"); + "Active. L: 15%, R: 25% battery."); } @Test @@ -1169,7 +1217,7 @@ public class CachedBluetoothDeviceTest { updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP); when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( "true".getBytes()); when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn( @@ -1178,7 +1226,7 @@ public class CachedBluetoothDeviceTest { TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( - "Left 15% Right 25%"); + "Left: 15% battery Right: 25% battery"); } @Test @@ -1741,16 +1789,6 @@ public class CachedBluetoothDeviceTest { BluetoothProfile.STATE_CONNECTED); } - private void updateProfileStatus(LocalBluetoothProfile profile, int status) { - doReturn(status).when(profile).getConnectionStatus(mDevice); - mCachedDevice.onProfileStateChanged(profile, status); - } - - private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) { - doReturn(status).when(profile).getConnectionStatus(mSubDevice); - mSubCachedDevice.onProfileStateChanged(profile, status); - } - @Test public void getSubDevice_setSubDevice() { mCachedDevice.setSubDevice(mSubCachedDevice); @@ -2030,6 +2068,29 @@ public class CachedBluetoothDeviceTest { assertThat(mCachedDevice.getConnectionSummary(false)).isNull(); } + private void updateProfileStatus(LocalBluetoothProfile profile, int status) { + doReturn(status).when(profile).getConnectionStatus(mDevice); + mCachedDevice.onProfileStateChanged(profile, status); + updateConnectionStatus(mCachedDevice); + } + + private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) { + doReturn(status).when(profile).getConnectionStatus(mSubDevice); + mSubCachedDevice.onProfileStateChanged(profile, status); + updateConnectionStatus(mSubCachedDevice); + } + + private void updateConnectionStatus(CachedBluetoothDevice cachedBluetoothDevice) { + for (LocalBluetoothProfile profile : cachedBluetoothDevice.getProfiles()) { + int status = cachedBluetoothDevice.getProfileConnectionState(profile); + if (status == BluetoothProfile.STATE_CONNECTED) { + when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(true); + return; + } + } + when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(false); + } + private HearingAidInfo getLeftAshaHearingAidInfo() { return new HearingAidInfo.Builder() .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT) diff --git a/packages/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/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/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/SystemUI/Android.bp b/packages/SystemUI/Android.bp index fce7a00f9744..d2ca11207084 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -78,11 +78,23 @@ 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", + ], +} + // 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", @@ -569,7 +581,7 @@ android_library { "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", "mockito-target-extended-minus-junit4", - "mockito-kotlin2", + "mockito-kotlin-nodeps", "androidx.test.ext.junit", "androidx.test.ext.truth", "kotlin-test", @@ -656,6 +668,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", @@ -702,7 +715,8 @@ 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", @@ -740,6 +754,7 @@ android_ravenwood_test { "androidx.core_core-animation-testing", "androidx.test.ext.junit", "kosmos", + "mockito-kotlin-nodeps", ], libs: [ "android.test.runner", diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 14ebc3907c04..755fe2a4476a 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -4,6 +4,16 @@ container: "system" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. flag { + name: "delay_show_magnification_button" + namespace: "accessibility" + description: "Delays the showing of magnification mode switch button." + bug: "338259519" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "floating_menu_animated_tuck" namespace: "accessibility" description: "Sets up animations for tucking/untucking and adjusts clipbounds." diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 21881f657e6d..0ef12e2124fb 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -34,6 +34,13 @@ flag { } flag { + name: "priority_people_section" + namespace: "systemui" + description: "Add a new section for priority people (aka important conversations)." + bug: "340294566" +} + +flag { name: "notification_minimalism_prototype" namespace: "systemui" description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal." @@ -348,6 +355,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" @@ -398,6 +413,16 @@ flag { } 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." @@ -470,6 +495,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" @@ -550,6 +595,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" @@ -837,6 +889,9 @@ flag { namespace: "systemui" description: "Enforce BaseUserRestriction for DISALLOW_CONFIG_BRIGHTNESS." bug: "329205638" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -850,6 +905,16 @@ flag { } flag { + name: "restart_dream_on_unocclude" + namespace: "systemui" + description: "re-enters dreaming upon unocclude when dreaming when originally occluding" + bug: "338051457" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "communal_bouncer_do_not_modify_plugin_open" namespace: "systemui" description: "do not modify notification shade when handling bouncer expansion." @@ -858,3 +923,47 @@ 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" +} 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/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/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt index cecbc474a18a..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, 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/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt index c7f0a965206e..17a606171a9e 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt @@ -134,13 +134,15 @@ internal class ExpandableControllerImpl( override val expandable: Expandable = object : Expandable { override fun activityTransitionController( - cujType: Int?, + launchCujType: Int?, + cookie: ActivityTransitionAnimator.TransitionCookie?, + returnCujType: Int? ): ActivityTransitionAnimator.Controller? { if (!isComposed.value) { return null } - return activityController(cujType) + return activityController(launchCujType, cookie, returnCujType) } override fun dialogTransitionController( @@ -262,10 +264,27 @@ internal class ExpandableControllerImpl( } /** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */ - private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller { + private fun activityController( + launchCujType: Int?, + cookie: ActivityTransitionAnimator.TransitionCookie?, + returnCujType: Int? + ): ActivityTransitionAnimator.Controller { val delegate = transitionController() return object : ActivityTransitionAnimator.Controller, TransitionAnimator.Controller by delegate { + /** + * CUJ identifier accounting for whether this controller is for a launch or a return. + */ + private val cujType: Int? + get() = + if (isLaunching) { + launchCujType + } else { + returnCujType + } + + override val transitionCookie = cookie + override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { delegate.onTransitionAnimationStart(isExpandingFullyAbove) overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index c22b50d9dd64..fa01a4bf46c5 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 @@ -56,7 +56,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 @@ -77,6 +76,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 @@ -111,7 +111,7 @@ fun BouncerContent( dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, ) { - val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() + val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsStateWithLifecycle() val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) Box( @@ -220,7 +220,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 +316,7 @@ 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() Row( modifier = @@ -480,7 +480,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 +562,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 +612,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 +642,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 +668,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 +736,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 +773,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, 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 2dcd0ff05c73..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,6 +48,7 @@ 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 @@ -62,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() } } 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 d7e9c10e7224..9c2fd64052b4 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 @@ -30,7 +30,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,6 +46,7 @@ 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 @@ -86,14 +86,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) } } 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 3eb1b14e72ba..604b517e19e5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt @@ -35,6 +35,7 @@ data class DisplayCutout( val viewDisplayCutoutKeyguardStatusBarView: ViewDisplayCutout? = null, ) { fun width() = abs(right.value - left.value).dp + fun height() = abs(bottom.value - top.value).dp } enum class CutoutLocation { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/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 79b57ca74f7d..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 } } @@ -549,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 @@ -577,7 +546,6 @@ private fun LockStateIcon( */ @Composable private fun Toolbar( - isDraggingToRemove: Boolean, removeEnabled: Boolean, onRemoveClicked: () -> Unit, setToolbarSize: (toolbarSize: IntSize) -> Unit, @@ -591,7 +559,7 @@ private fun Toolbar( label = "RemoveButtonAlphaAnimation" ) - Row( + Box( modifier = Modifier.fillMaxWidth() .padding( @@ -600,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), + ) + } } } } @@ -762,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), @@ -804,6 +761,8 @@ private fun CommunalContent( is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier) is CommunalContentModel.WidgetContent.DisabledWidget -> DisabledWidgetPlaceholder(model, viewModel, modifier) + is CommunalContentModel.WidgetContent.PendingWidget -> + PendingWidgetPlaceholder(model, modifier) is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier) is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) @@ -836,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( @@ -849,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, @@ -869,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( @@ -883,6 +843,7 @@ private fun CtaTileInViewModeContent( ) { Text( text = stringResource(R.string.cta_tile_button_to_open_widget_editor), + fontSize = 12.sp, ) } } @@ -902,7 +863,7 @@ private fun WidgetContent( 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() @@ -910,7 +871,7 @@ private fun WidgetContent( 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.collectAsState() + val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle() val selectedIndex = selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } } Box( @@ -929,36 +890,36 @@ private fun WidgetContent( Modifier.semantics { contentDescription = accessibilityLabel onClick(label = clickActionLabel, action = null) - val deleteAction = - CustomAccessibilityAction(removeWidgetActionLabel) { - contentListState.onRemove(index) + val deleteAction = + CustomAccessibilityAction(removeWidgetActionLabel) { + contentListState.onRemove(index) + contentListState.onSaveList() + true + } + val selectWidgetAction = + CustomAccessibilityAction(clickActionLabel) { + val currentWidgetKey = + index?.let { + keyAtIndexIfEditable(contentListState.list, index) + } + viewModel.setSelectedKey(currentWidgetKey) + true + } + + val actions = mutableListOf(deleteAction, selectWidgetAction) + + if (selectedIndex != null && selectedIndex != index) { + actions.add( + CustomAccessibilityAction(placeWidgetActionLabel) { + contentListState.onMove(selectedIndex!!, index) contentListState.onSaveList() + viewModel.setSelectedKey(null) true } - val selectWidgetAction = - CustomAccessibilityAction(clickActionLabel) { - val currentWidgetKey = - index?.let { - keyAtIndexIfEditable(contentListState.list, index) - } - viewModel.setSelectedKey(currentWidgetKey) - true - } - - val actions = mutableListOf(deleteAction, selectWidgetAction) - - if (selectedIndex != null && selectedIndex != index) { - actions.add( - CustomAccessibilityAction(placeWidgetActionLabel) { - contentListState.onMove(selectedIndex!!, index) - contentListState.onSaveList() - viewModel.setSelectedKey(null) - true - } - ) - } + ) + } - customActions = actions + customActions = actions } } ) { @@ -968,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 } }, @@ -1074,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, @@ -1121,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( @@ -1164,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 @@ -1227,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 = @@ -1246,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..6d8c47d84850 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,11 +18,11 @@ 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.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint @@ -51,7 +51,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) 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..abff93d15c55 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,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 @@ -67,8 +67,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/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/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index 63c70c97ed07..88b8298335aa 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 @@ -62,9 +62,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 -> @@ -133,7 +133,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) { @@ -170,8 +170,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/NotificationStackContentHeight.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt new file mode 100644 index 000000000000..9f829cccfb32 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.notifications.ui.composable + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateMeasurement +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntOffset +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView + +/** + * Modify element, which updates the height to be the same as the Notification stack height returned + * by the legacy Notification stack scroll view in [NotificationScrollView.intrinsicStackHeight]. + * + * @param view Notification stack scroll view + * @param padding extra padding in pixels to be added to the received content height. + */ +fun Modifier.notificationStackHeight(view: NotificationScrollView, padding: Int = 0) = + this then StackLayoutElement(view, padding) + +private data class StackLayoutElement( + val view: NotificationScrollView, + val padding: Int, +) : ModifierNodeElement<StackLayoutNode>() { + + override fun create(): StackLayoutNode = StackLayoutNode(view, padding) + + override fun update(node: StackLayoutNode) { + check(view == node.view) { "Trying to reuse the node with a new View." } + if (node.padding != padding) { + node.padding = padding + node.invalidateMeasureIfAttached() + } + } +} + +private class StackLayoutNode(val view: NotificationScrollView, var padding: Int) : + LayoutModifierNode, Modifier.Node() { + + private val stackHeightChangedListener = Runnable { invalidateMeasureIfAttached() } + + override fun onAttach() { + super.onAttach() + view.addStackHeightChangedListener(stackHeightChangedListener) + } + + override fun onDetach() { + super.onDetach() + view.removeStackHeightChangedListener(stackHeightChangedListener) + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + val contentHeight = padding + view.intrinsicStackHeight + val placeable = + measurable.measure( + constraints.copy(minHeight = contentHeight, maxHeight = contentHeight) + ) + + return layout(placeable.width, placeable.height) { placeable.place(IntOffset.Zero) } + } + + override fun toString(): String { + return "StackLayoutNode(view=$view padding:$padding)" + } + + fun invalidateMeasureIfAttached() { + if (isAttached) { + this.invalidateMeasurement() + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 3ce0feb0af56..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 @@ -38,8 +38,9 @@ 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.mutableIntStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment @@ -62,19 +63,23 @@ 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 @@ -107,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, @@ -133,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, ) { @@ -141,6 +147,7 @@ fun SceneScope.ConstrainedNotificationStack( modifier.onSizeChanged { viewModel.onConstrainedAvailableSpaceChanged(it.height) } ) { NotificationPlaceholder( + stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.fillMaxSize(), ) @@ -158,9 +165,11 @@ fun SceneScope.ConstrainedNotificationStack( @Composable fun SceneScope.NotificationScrollingStack( shadeSession: SaveableSession, + stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, maxScrimTop: () -> Float, shouldPunchHoleBehindScrim: Boolean, + shadeMode: ShadeMode, modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() @@ -171,17 +180,24 @@ fun SceneScope.NotificationScrollingStack( shadeSession.rememberSaveableSession(saver = ScrollState.Saver, key = null) { ScrollState(initial = 0) } - val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f) - val isCurrentGestureOverscroll = viewModel.isCurrentGestureOverscroll.collectAsState(false) - val expansionFraction by viewModel.expandFraction.collectAsState(0f) + 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 @@ -212,7 +228,7 @@ fun SceneScope.NotificationScrollingStack( // if contentHeight drops below minimum visible scrim height while scrim is // expanded, reset scrim offset. LaunchedEffect(stackHeight, scrimOffset) { - snapshotFlow { stackHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f } + snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f } .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) } } @@ -233,6 +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 @@ -301,40 +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( - shadeSession.rememberSession( - scrimOffset, - maxScrimTop, - minScrimTop, - isCurrentGestureOverscroll, - ) { - NotificationScrimNestedScrollConnection( - scrimOffset = { scrimOffset.value }, - snapScrimOffset = { value -> - coroutineScope.launch { scrimOffset.snapTo(value) } - }, - animateScrimOffset = { value -> - coroutineScope.launch { scrimOffset.animateTo(value) } - }, - minScrimOffset = minScrimOffset, - maxScrimOffset = 0f, - contentHeight = { stackHeight.value }, - minVisibleScrimHeight = minVisibleScrimHeight, - isCurrentGestureOverscroll = { - isCurrentGestureOverscroll.value - }, - ) - } - ) + .thenIf(shadeMode == ShadeMode.Single) { + Modifier.nestedScroll(scrimNestedScrollConnection) + } .verticalScroll(scrollState) .fillMaxWidth() - .height { (stackHeight.value + navBarHeight).roundToInt() }, + .notificationStackHeight( + view = stackScrollView, + padding = navBarHeight.toInt() + ) + .onSizeChanged { size -> stackHeight.intValue = size.height }, ) } HeadsUpNotificationSpace(viewModel = viewModel) @@ -375,6 +395,7 @@ fun SceneScope.NotificationShelfSpace( @Composable private fun SceneScope.NotificationPlaceholder( + stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, ) { @@ -393,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/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 5f84dd47a240..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 @@ -44,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 @@ -69,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 @@ -132,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 46be6b898b8d..d1099883c5e5 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 @@ -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) @@ -161,9 +164,11 @@ private fun QuickSettingsContent( state: QSSceneAdapter.State, modifier: Modifier = Modifier, ) { - val qsView by qsSceneAdapter.qsView.collectAsState(null) + val qsView by qsSceneAdapter.qsView.collectAsStateWithLifecycle(null) val isCustomizing by - qsSceneAdapter.isCustomizerShowing.collectAsState(qsSceneAdapter.isCustomizerShowing.value) + qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle( + qsSceneAdapter.isCustomizerShowing.value + ) QuickSettingsTheme { val context = LocalContext.current diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index ec9136d50e4c..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 @@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -50,7 +51,6 @@ 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 @@ -62,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 @@ -83,14 +86,17 @@ import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader import com.android.systemui.shade.ui.composable.Shade import com.android.systemui.shade.ui.composable.ShadeHeader +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager +import dagger.Lazy import javax.inject.Inject import javax.inject.Named import kotlin.math.roundToInt @@ -105,6 +111,7 @@ class QuickSettingsScene constructor( @Application private val applicationScope: CoroutineScope, private val shadeSession: SaveableSession, + private val notificationStackScrollView: Lazy<NotificationScrollView>, private val viewModel: QuickSettingsSceneViewModel, private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, private val tintedIconManagerFactory: TintedIconManager.Factory, @@ -127,6 +134,7 @@ constructor( modifier: Modifier, ) { QuickSettingsScene( + notificationStackScrollView = notificationStackScrollView.get(), viewModel = viewModel, notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, createTintedIconManager = tintedIconManagerFactory::create, @@ -142,6 +150,7 @@ constructor( @Composable private fun SceneScope.QuickSettingsScene( + notificationStackScrollView: NotificationScrollView, viewModel: QuickSettingsSceneViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, @@ -152,7 +161,10 @@ private fun SceneScope.QuickSettingsScene( 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, @@ -183,11 +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 isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState() + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() + val isCustomizerShowing by + viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() val customizingAnimationDuration by - viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsState() + viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() val screenHeight = LocalRawScreenHeight.current BackHandler( @@ -280,7 +296,8 @@ private fun SceneScope.QuickSettingsScene( } Column( - modifier = shadeHeaderAndQuickSettingsModifier, + modifier = + shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"), ) { when (LocalWindowSizeClass.current.widthSizeClass) { WindowWidthSizeClass.Compact -> @@ -320,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)) @@ -329,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, @@ -346,14 +364,17 @@ private fun SceneScope.QuickSettingsScene( 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/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 7eaebc21355d..cbaa89438f2e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -6,14 +6,17 @@ import com.android.systemui.bouncer.ui.composable.Bouncer import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition +import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToCommunalTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition +import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition import com.android.systemui.shade.ui.composable.Shade @@ -38,6 +41,13 @@ val SceneContainerTransitions = transitions { from( Scenes.Gone, to = Scenes.Shade, + key = ToSplitShade, + ) { + goneToSplitShadeTransition() + } + from( + Scenes.Gone, + to = Scenes.Shade, key = SlightlyFasterShadeCollapse, ) { goneToShadeTransition(durationScale = 0.9) @@ -49,6 +59,13 @@ val SceneContainerTransitions = transitions { from( Scenes.Lockscreen, to = Scenes.Shade, + key = ToSplitShade, + ) { + lockscreenToSplitShadeTransition() + } + from( + Scenes.Lockscreen, + to = Scenes.Shade, key = SlightlyFasterShadeCollapse, ) { lockscreenToShadeTransition(durationScale = 0.9) @@ -68,5 +85,9 @@ val SceneContainerTransitions = transitions { Notifications.Elements.NotificationScrim, y = { Shade.Dimensions.ScrimOverscrollLimit } ) + translate( + Shade.Elements.SplitShadeStartColumn, + y = { Shade.Dimensions.ScrimOverscrollLimit } + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt new file mode 100644 index 000000000000..f14ff76df5db --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.ui.composable.transitions + +import com.android.compose.animation.scene.TransitionBuilder + +fun TransitionBuilder.goneToSplitShadeTransition( + durationScale: Double = 1.0, +) { + toSplitShadeTransition(durationScale) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt new file mode 100644 index 000000000000..70c343ceacda --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.ui.composable.transitions + +import com.android.compose.animation.scene.TransitionBuilder + +fun TransitionBuilder.lockscreenToSplitShadeTransition( + durationScale: Double = 1.0, +) { + toSplitShadeTransition(durationScale = durationScale) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt new file mode 100644 index 000000000000..a8315c05c7d1 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.ui.composable.transitions + +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.ui.unit.IntSize +import com.android.compose.animation.scene.TransitionBuilder +import com.android.compose.animation.scene.UserActionDistance +import com.android.compose.animation.scene.UserActionDistanceScope +import com.android.systemui.notifications.ui.composable.Notifications +import com.android.systemui.qs.ui.composable.QuickSettings +import com.android.systemui.shade.ui.composable.Shade +import com.android.systemui.shade.ui.composable.ShadeHeader +import kotlin.time.Duration.Companion.milliseconds + +fun TransitionBuilder.toSplitShadeTransition( + durationScale: Double = 1.0, +) { + spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) + swipeSpec = + spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold, + ) + distance = + object : UserActionDistance { + override fun UserActionDistanceScope.absoluteDistance( + fromSceneSize: IntSize, + orientation: Orientation, + ): Float { + return fromSceneSize.height.toFloat() * 2 / 3f + } + } + + fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) } + + fractionRange(start = .33f) { + fade(ShadeHeader.Elements.Clock) + fade(ShadeHeader.Elements.CollapsedContentStart) + fade(ShadeHeader.Elements.CollapsedContentEnd) + fade(ShadeHeader.Elements.PrivacyChip) + fade(QuickSettings.Elements.SplitShadeQuickSettings) + fade(QuickSettings.Elements.FooterActions) + fade(Notifications.Elements.NotificationScrim) + } +} + +private val DefaultDuration = 500.milliseconds diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index d5287362c36d..00ef11d3b745 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -30,13 +30,13 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.collectAsState 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 @@ -51,7 +51,7 @@ fun SceneScope.OverlayShade( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { - val backgroundScene by viewModel.backgroundScene.collectAsState() + val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle() Box(modifier) { if (backgroundScene == Scenes.Lockscreen) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index 0bd38a1daf43..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,7 @@ import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout +import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.res.R @@ -77,6 +79,7 @@ import com.android.systemui.statusbar.phone.ui.TintedIconManager import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel import com.android.systemui.statusbar.policy.Clock +import kotlin.math.max object ShadeHeader { object Elements { @@ -115,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) { @@ -131,7 +138,7 @@ 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. @@ -140,7 +147,7 @@ fun SceneScope.CollapsedShadeHeader( contents = listOf( { - Row { + Row(modifier = Modifier.padding(horizontal = horizontalPadding)) { Clock( scale = 1f, viewModel = viewModel, @@ -157,7 +164,12 @@ fun SceneScope.CollapsedShadeHeader( }, { if (isPrivacyChipVisible) { - Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) { + Box( + modifier = + Modifier.height(CollapsedHeight) + .fillMaxWidth() + .padding(horizontal = horizontalPadding) + ) { PrivacyChip( viewModel = viewModel, modifier = Modifier.align(Alignment.CenterEnd), @@ -166,9 +178,13 @@ fun SceneScope.CollapsedShadeHeader( } else { Row( horizontalArrangement = Arrangement.End, - modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentEnd) + modifier = + Modifier.element(ShadeHeader.Elements.CollapsedContentEnd) + .padding(horizontal = horizontalPadding) ) { - SystemIconContainer { + SystemIconContainer( + modifier = Modifier.align(Alignment.CenterVertically) + ) { when (LocalWindowSizeClass.current.widthSizeClass) { WindowWidthSizeClass.Medium, WindowWidthSizeClass.Expanded -> @@ -206,7 +222,7 @@ fun SceneScope.CollapsedShadeHeader( val screenWidth = constraints.maxWidth val cutoutWidthPx = cutoutWidth.roundToPx() - val height = CollapsedHeight.roundToPx() + val height = max(cutoutHeight + (cutoutTop * 2), CollapsedHeight).roundToPx() val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height) val startMeasurable = measurables[0][0] @@ -255,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 } @@ -264,7 +280,7 @@ fun SceneScope.ExpandedShadeHeader( derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) } } - val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState() + val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) { if (isPrivacyChipVisible) { @@ -419,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)) @@ -456,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 -> @@ -528,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 4ad8b9f47f70..a0278a616857 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -44,7 +45,6 @@ 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 @@ -57,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 @@ -67,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 @@ -84,9 +89,11 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager +import dagger.Lazy import javax.inject.Inject import javax.inject.Named import kotlin.math.roundToInt @@ -97,6 +104,7 @@ object Shade { val MediaCarousel = ElementKey("ShadeMediaCarousel") val BackgroundScrim = ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker) + val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn") } object Dimensions { @@ -121,6 +129,7 @@ class ShadeScene @Inject constructor( private val shadeSession: SaveableSession, + private val notificationStackScrollView: Lazy<NotificationScrollView>, private val viewModel: ShadeSceneViewModel, private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, @@ -139,6 +148,7 @@ constructor( modifier: Modifier, ) = ShadeScene( + notificationStackScrollView.get(), viewModel = viewModel, createTintedIconManager = tintedIconManagerFactory::create, createBatteryMeterViewController = batteryMeterViewControllerFactory::create, @@ -158,6 +168,7 @@ constructor( @Composable private fun SceneScope.ShadeScene( + notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -167,10 +178,11 @@ private fun SceneScope.ShadeScene( 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, @@ -182,6 +194,7 @@ private fun SceneScope.ShadeScene( ) is ShadeMode.Split -> SplitShade( + notificationStackScrollView = notificationStackScrollView, viewModel = viewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, @@ -197,6 +210,7 @@ private fun SceneScope.ShadeScene( @Composable private fun SceneScope.SingleShade( + notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -206,6 +220,8 @@ private fun SceneScope.SingleShade( modifier: Modifier = Modifier, shadeSession: SaveableSession, ) { + val cutoutLocation = LocalDisplayCutout.current.location + val maxNotifScrimTop = remember { mutableStateOf(0f) } val tileSquishiness by animateSceneFloatAsState( @@ -213,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) || @@ -241,19 +258,21 @@ private fun SceneScope.SingleShade( Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = - Modifier.fillMaxWidth().thenIf(isClickable) { - Modifier.clickable(onClick = { viewModel.onContentClicked() }) - } + Modifier.fillMaxWidth() + .thenIf(isClickable) { + Modifier.clickable( + onClick = { viewModel.onContentClicked() } + ) + } + .thenIf(cutoutLocation != CutoutLocation.CENTER) { + Modifier.displayCutoutPadding() + }, ) { CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, - modifier = - Modifier.padding( - horizontal = Shade.Dimensions.HorizontalPadding - ) ) Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) { QuickSettings( @@ -265,7 +284,7 @@ private fun SceneScope.SingleShade( } MediaCarousel( - isVisible = viewModel::isMediaVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, @@ -277,8 +296,10 @@ private fun SceneScope.SingleShade( { NotificationScrollingStack( shadeSession = shadeSession, + stackScrollView = notificationStackScrollView, viewModel = viewModel.notifications, maxScrimTop = { maxNotifScrimTop.value }, + shadeMode = ShadeMode.Single, shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, ) }, @@ -303,6 +324,7 @@ private fun SceneScope.SingleShade( @Composable private fun SceneScope.SplitShade( + notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -312,27 +334,34 @@ private fun SceneScope.SplitShade( modifier: Modifier = Modifier, shadeSession: SaveableSession, ) { - val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() - val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.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.collectAsState() + 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 @@ -356,7 +385,8 @@ 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, @@ -366,6 +396,8 @@ private fun SceneScope.SplitShade( viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha) DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } } + val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle() + val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha } Box( @@ -388,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() }, ) @@ -398,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, @@ -416,6 +447,7 @@ private fun SceneScope.SplitShade( Column( modifier = Modifier.fillMaxSize() + .sysuiResTag("expanded_qs_scroll_view") .weight(1f) .thenIf(!isCustomizerShowing) { Modifier.verticalNestedScrollToScene() @@ -441,7 +473,7 @@ private fun SceneScope.SplitShade( } MediaCarousel( - isVisible = viewModel::isMediaVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, @@ -454,6 +486,7 @@ private fun SceneScope.SplitShade( lifecycleOwner = lifecycleOwner, modifier = Modifier.align(Alignment.CenterHorizontally) + .sysuiResTag("qs_footer_actions") .then(brightnessMirrorShowingModifier), ) } @@ -461,13 +494,15 @@ private fun SceneScope.SplitShade( NotificationScrollingStack( shadeSession = shadeSession, + stackScrollView = notificationStackScrollView, viewModel = viewModel.notifications, maxScrimTop = { 0f }, shouldPunchHoleBehindScrim = false, + shadeMode = ShadeMode.Split, modifier = Modifier.weight(1f) .fillMaxHeight() - .padding(bottom = navBarBottomHeight) + .padding(end = screenCornerRadius / 2f, bottom = navBarBottomHeight) .then(brightnessMirrorShowingModifier) ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/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 79d17efcacc1..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.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,34 +63,47 @@ 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) - val onClick = - if (isClickable) { - { ancPopup.show(null) } - } else { - null - } 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, - isEnabled = onClick != null, - onWidthChanged = viewModel::onButtonSliceWidthChanged, - onClick = onClick, - ) + }, + 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 {}.basicMarquee(), text = label, 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..15df1be02f56 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,14 @@ 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() 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 fc5d212a0be7..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 @@ -32,14 +33,13 @@ import com.android.systemui.res.R fun SliceAndroidView( slice: Slice?, modifier: Modifier = Modifier, - isEnabled: Boolean = true, 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), ) .apply { @@ -47,17 +47,18 @@ fun SliceAndroidView( isScrollable = false importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO setShowTitleItems(true) - if (onWidthChanged != null) { - addOnLayoutChangeListener(OnWidthChangedLayoutListener(onWidthChanged)) - } } }, - update = { sliceView: ClickableSliceView -> + update = { sliceView: ComposeSliceView -> sliceView.slice = slice - sliceView.onClick = onClick - sliceView.isEnabled = isEnabled - sliceView.isClickable = isEnabled - } + sliceView.layoutListener = onWidthChanged?.let(::OnWidthChangedLayoutListener) + sliceView.enableAccessibility = enableAccessibility + }, + onRelease = { sliceView: ComposeSliceView -> + sliceView.layoutListener = null + sliceView.slice = null + sliceView.enableAccessibility = true + }, ) } @@ -83,26 +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) : 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) } + } - var onClick: (() -> Unit)? = null + override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) { + if (enableAccessibility) { + super.onInitializeAccessibilityNodeInfo(info) + } + } - init { - if (onClick != null) { - setOnClickListener {} + override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) { + if (enableAccessibility) { + super.onInitializeAccessibilityEvent(event) } } - override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { - return (isSliceViewClickable && onClick != null) || super.onInterceptTouchEvent(ev) + override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean { + return if (enableAccessibility) { + super.performAccessibilityAction(action, arguments) + } else { + false + } } - override fun onClick(v: View?) { - onClick?.takeIf { isSliceViewClickable }?.let { it() } ?: super.onClick(v) + 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 12debbc9c011..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 @@ -29,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 @@ -42,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 @@ -56,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() 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 ded63a107e70..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,9 +77,9 @@ 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( 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 a46f4e5fef1a..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,14 +63,26 @@ fun VolumeSlider( PlatformSlider( modifier = modifier.clearAndSetSemantics { - if (!state.isEnabled) disabled() - contentDescription = - state.disabledMessage?.let { "${state.label}, $it" } ?: state.label - - // provide a not animated value to the a11y because it fails to announce the - // settled value when it changes rapidly. if (state.isEnabled) { + contentDescription = state.label + state.a11yClickDescription?.let { + customActions = + listOf( + CustomAccessibilityAction( + it, + ) { + onIconTapped() + true + } + ) + } + + state.a11yStateDescription?.let { stateDescription = it } progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) + } else { + disabled() + contentDescription = + state.disabledMessage?.let { "${state.label}, $it" } ?: state.label } setProgress { targetValue -> val targetDirection = 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 a602e25e05c1..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,7 +24,6 @@ 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 @@ -32,6 +31,7 @@ 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 @@ -54,8 +54,8 @@ fun VolumePanelRoot( } val accessibilityTitle = stringResource(R.string.accessibility_volume_settings) - val state: VolumePanelState by viewModel.volumePanelState.collectAsState() - val components by viewModel.componentsLayout.collectAsState(null) + val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle() + val components by viewModel.componentsLayout.collectAsStateWithLifecycle(null) with(VolumePanelComposeScope(state)) { components?.let { componentsState -> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt index d924d88d1e59..92d5c26148e4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt @@ -74,6 +74,16 @@ sealed interface ObservableTransitionState { */ val isUserInputOngoing: Flow<Boolean>, ) : ObservableTransitionState + + fun isIdle(scene: SceneKey?): Boolean { + return this is Idle && (scene == null || this.currentScene == scene) + } + + fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean { + return this is Transition && + (from == null || this.fromScene == from) && + (to == null || this.toScene == to) + } } /** diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index f539a23d9cb0..bdeab797d165 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -28,16 +28,16 @@ import android.text.format.DateFormat import android.util.AttributeSet import android.util.MathUtils.constrainedMap import android.util.TypedValue -import android.view.View.MeasureSpec.EXACTLY import android.view.View +import android.view.View.MeasureSpec.EXACTLY import android.widget.TextView import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R -import com.android.systemui.log.core.LogcatOnlyMessageBuffer import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogcatOnlyMessageBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.core.MessageBuffer import java.io.PrintWriter @@ -47,11 +47,13 @@ import java.util.TimeZone import kotlin.math.min /** - * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30) - * The time's text color is a gradient that changes its colors based on its controller. + * Displays the time with the hour positioned above the minutes (ie: 09 above 30 is 9:30). The + * time's text color is a gradient that changes its colors based on its controller. */ @SuppressLint("AppCompatCustomView") -class AnimatableClockView @JvmOverloads constructor( +class AnimatableClockView +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, @@ -63,7 +65,9 @@ class AnimatableClockView @JvmOverloads constructor( get() = field ?: DEFAULT_LOGGER var messageBuffer: MessageBuffer get() = logger.buffer - set(value) { logger = Logger(value, TAG) } + set(value) { + logger = Logger(value, TAG) + } var hasCustomPositionUpdatedAnimation: Boolean = false var migratedClocks: Boolean = false @@ -77,16 +81,13 @@ class AnimatableClockView @JvmOverloads constructor( private var format: CharSequence? = null private var descFormat: CharSequence? = null - @ColorInt - private var dozingColor = 0 - - @ColorInt - private var lockScreenColor = 0 + @ColorInt private var dozingColor = 0 + @ColorInt private var lockScreenColor = 0 private var lineSpacingScale = 1f private val chargeAnimationDelay: Int private var textAnimator: TextAnimator? = null - private var onTextAnimatorInitialized: Runnable? = null + private var onTextAnimatorInitialized: ((TextAnimator) -> Unit)? = null private var translateForCenterAnimation = false private val parentWidth: Int @@ -94,9 +95,11 @@ class AnimatableClockView @JvmOverloads constructor( // last text size which is not constrained by view height private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE - @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = - { layout, invalidateCb -> - TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) } + + @VisibleForTesting + var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb -> + TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) + } // Used by screenshot tests to provide stability @VisibleForTesting var isAnimationEnabled: Boolean = true @@ -109,40 +112,55 @@ class AnimatableClockView @JvmOverloads constructor( get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal /** - * The number of pixels below the baseline. For fonts that support languages such as - * Burmese, this space can be significant and should be accounted for when computing layout. + * The number of pixels below the baseline. For fonts that support languages such as Burmese, + * this space can be significant and should be accounted for when computing layout. */ - val bottom get() = paint?.fontMetrics?.bottom ?: 0f + val bottom: Float + get() = paint?.fontMetrics?.bottom ?: 0f init { - val animatableClockViewAttributes = context.obtainStyledAttributes( - attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes - ) + val animatableClockViewAttributes = + context.obtainStyledAttributes( + attrs, + R.styleable.AnimatableClockView, + defStyleAttr, + defStyleRes + ) try { - dozingWeightInternal = animatableClockViewAttributes.getInt( - R.styleable.AnimatableClockView_dozeWeight, - /* default = */ 100 - ) - lockScreenWeightInternal = animatableClockViewAttributes.getInt( - R.styleable.AnimatableClockView_lockScreenWeight, - /* default = */ 300 - ) - chargeAnimationDelay = animatableClockViewAttributes.getInt( - R.styleable.AnimatableClockView_chargeAnimationDelay, /* default = */ 200 - ) + dozingWeightInternal = + animatableClockViewAttributes.getInt( + R.styleable.AnimatableClockView_dozeWeight, + /* default = */ 100 + ) + lockScreenWeightInternal = + animatableClockViewAttributes.getInt( + R.styleable.AnimatableClockView_lockScreenWeight, + /* default = */ 300 + ) + chargeAnimationDelay = + animatableClockViewAttributes.getInt( + R.styleable.AnimatableClockView_chargeAnimationDelay, + /* default = */ 200 + ) } finally { animatableClockViewAttributes.recycle() } - val textViewAttributes = context.obtainStyledAttributes( - attrs, android.R.styleable.TextView, - defStyleAttr, defStyleRes - ) + val textViewAttributes = + context.obtainStyledAttributes( + attrs, + android.R.styleable.TextView, + defStyleAttr, + defStyleRes + ) try { - isSingleLineInternal = textViewAttributes.getBoolean( - android.R.styleable.TextView_singleLine, /* default = */ false) + isSingleLineInternal = + textViewAttributes.getBoolean( + android.R.styleable.TextView_singleLine, + /* default = */ false + ) } finally { textViewAttributes.recycle() } @@ -156,9 +174,7 @@ class AnimatableClockView @JvmOverloads constructor( refreshFormat() } - /** - * Whether to use a bolded version based on the user specified fontWeightAdjustment. - */ + /** Whether to use a bolded version based on the user specified fontWeightAdjustment. */ fun useBoldedVersion(): Boolean { // "Bold text" fontWeightAdjustment is 300. return resources.configuration.fontWeightAdjustment > 100 @@ -169,25 +185,30 @@ class AnimatableClockView @JvmOverloads constructor( contentDescription = DateFormat.format(descFormat, time) val formattedText = DateFormat.format(format, time) logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() } - // Setting text actually triggers a layout pass (because the text view is set to - // wrap_content width and TextView always relayouts for this). Avoid needless - // relayout if the text didn't actually change. - if (!TextUtils.equals(text, formattedText)) { - text = formattedText - logger.d({ "refreshTime: done setting new time text to: $str1" }) { - str1 = formattedText?.toString() - } - // Because the TextLayout may mutate under the hood as a result of the new text, we - // notify the TextAnimator that it may have changed and request a measure/layout. A - // crash will occur on the next invocation of setTextStyle if the layout is mutated - // without being notified TextInterpolator being notified. - if (layout != null) { - textAnimator?.updateLayout(layout) - logger.d("refreshTime: done updating textAnimator layout") - } - requestLayout() - logger.d("refreshTime: after requestLayout") + + // Setting text actually triggers a layout pass in TextView (because the text view is set to + // wrap_content width and TextView always relayouts for this). This avoids needless relayout + // if the text didn't actually change. + if (TextUtils.equals(text, formattedText)) { + return } + + text = formattedText + logger.d({ "refreshTime: done setting new time text to: $str1" }) { + str1 = formattedText?.toString() + } + + // Because the TextLayout may mutate under the hood as a result of the new text, we notify + // the TextAnimator that it may have changed and request a measure/layout. A crash will + // occur on the next invocation of setTextStyle if the layout is mutated without being + // notified TextInterpolator being notified. + if (layout != null) { + textAnimator?.updateLayout(layout) + logger.d("refreshTime: done updating textAnimator layout") + } + + requestLayout() + logger.d("refreshTime: after requestLayout") } fun onTimeZoneChanged(timeZone: TimeZone?) { @@ -206,19 +227,27 @@ class AnimatableClockView @JvmOverloads constructor( @SuppressLint("DrawAllocation") override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { logger.d("onMeasure") - if (migratedClocks && !isSingleLineInternal && - MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) { + + if ( + migratedClocks && + !isSingleLineInternal && + MeasureSpec.getMode(heightMeasureSpec) == EXACTLY + ) { // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize - super.setTextSize(TypedValue.COMPLEX_UNIT_PX, - min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F)) + super.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) + ) } super.onMeasure(widthMeasureSpec, heightMeasureSpec) val animator = textAnimator if (animator == null) { - textAnimator = textAnimatorFactory(layout, ::invalidate) - onTextAnimatorInitialized?.run() - onTextAnimatorInitialized = null + textAnimator = + textAnimatorFactory(layout, ::invalidate)?.also { + onTextAnimatorInitialized?.invoke(it) + onTextAnimatorInitialized = null + } } else { animator.updateLayout(layout) } @@ -243,15 +272,13 @@ class AnimatableClockView @JvmOverloads constructor( canvas.translate(parentWidth / 4f, 0f) } - logger.d({ "onDraw($str1)"}) { str1 = text.toString() } + logger.d({ "onDraw($str1)" }) { str1 = text.toString() } // intentionally doesn't call super.onDraw here or else the text will be rendered twice textAnimator?.draw(canvas) canvas.restore() } override fun invalidate() { - @Suppress("UNNECESSARY_SAFE_CALL") - // logger won't be initialized when called by TextView's constructor logger.d("invalidate") super.invalidate() } @@ -325,6 +352,7 @@ class AnimatableClockView @JvmOverloads constructor( if (textAnimator == null) { return } + logger.d("animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, @@ -348,10 +376,11 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateCharge(isDozing: () -> Boolean) { + // Skip charge animation if dozing animation is already playing. if (textAnimator == null || textAnimator!!.isRunning()) { - // Skip charge animation if dozing animation is already playing. return } + logger.d("animateCharge") val startAnimPhase2 = Runnable { setTextStyle( @@ -409,10 +438,9 @@ class AnimatableClockView @JvmOverloads constructor( /** * Set text style with an optional animation. - * - * By passing -1 to weight, the view preserves its current weight. - * By passing -1 to textSize, the view preserves its current text size. - * By passing null to color, the view preserves its current color. + * - By passing -1 to weight, the view preserves its current weight. + * - By passing -1 to textSize, the view preserves its current text size. + * - By passing null to color, the view preserves its current color. * * @param weight text weight. * @param textSize font size. @@ -428,8 +456,8 @@ class AnimatableClockView @JvmOverloads constructor( delay: Long, onAnimationEnd: Runnable? ) { - if (textAnimator != null) { - textAnimator?.setTextStyle( + textAnimator?.let { + it.setTextStyle( weight = weight, textSize = textSize, color = color, @@ -439,23 +467,24 @@ class AnimatableClockView @JvmOverloads constructor( delay = delay, onAnimationEnd = onAnimationEnd ) - textAnimator?.glyphFilter = glyphFilter - } else { - // when the text animator is set, update its start values - onTextAnimatorInitialized = Runnable { - textAnimator?.setTextStyle( - weight = weight, - textSize = textSize, - color = color, - animate = false, - duration = duration, - interpolator = interpolator, - delay = delay, - onAnimationEnd = onAnimationEnd - ) - textAnimator?.glyphFilter = glyphFilter - } + it.glyphFilter = glyphFilter } + ?: run { + // when the text animator is set, update its start values + onTextAnimatorInitialized = { textAnimator -> + textAnimator.setTextStyle( + weight = weight, + textSize = textSize, + color = color, + animate = false, + duration = duration, + interpolator = interpolator, + delay = delay, + onAnimationEnd = onAnimationEnd + ) + textAnimator.glyphFilter = glyphFilter + } + } } private fun setTextStyle( @@ -483,12 +512,13 @@ class AnimatableClockView @JvmOverloads constructor( fun refreshFormat(use24HourFormat: Boolean) { Patterns.update(context) - format = when { - isSingleLineInternal && use24HourFormat -> Patterns.sClockView24 - !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR - isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 - else -> DOUBLE_LINE_FORMAT_12_HOUR - } + format = + when { + isSingleLineInternal && use24HourFormat -> Patterns.sClockView24 + !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR + isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 + else -> DOUBLE_LINE_FORMAT_12_HOUR + } logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() } descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12 @@ -510,10 +540,10 @@ class AnimatableClockView @JvmOverloads constructor( pw.println(" time=$time") } - private val moveToCenterDelays + private val moveToCenterDelays: List<Int> get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS - private val moveToSideDelays + private val moveToSideDelays: List<Int> get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS /** @@ -531,7 +561,7 @@ class AnimatableClockView @JvmOverloads constructor( fun offsetGlyphsForStepClockAnimation( clockStartLeft: Int, clockMoveDirection: Int, - moveFraction: Float + moveFraction: Float, ) { val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0 val currentMoveAmount = left - clockStartLeft @@ -558,8 +588,8 @@ class AnimatableClockView @JvmOverloads constructor( * * @param distance is the total distance in pixels to offset the glyphs when animation * completes. Negative distance means we are animating the position towards the center. - * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 - * means it finished moving. + * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means + * it finished moving. */ fun offsetGlyphsForStepClockAnimation( distance: Float, @@ -568,13 +598,17 @@ class AnimatableClockView @JvmOverloads constructor( for (i in 0 until NUM_DIGITS) { val dir = if (isLayoutRtl) -1 else 1 val digitFraction = - getDigitFraction(digit = i, isMovingToCenter = distance > 0, fraction = fraction) + getDigitFraction( + digit = i, + isMovingToCenter = distance > 0, + fraction = fraction, + ) val moveAmountForDigit = dir * distance * digitFraction glyphOffsets[i] = moveAmountForDigit if (distance > 0) { - // If distance > 0 then we are moving from the left towards the center. - // We need ensure that the glyphs are offset to the initial position. + // If distance > 0 then we are moving from the left towards the center. We need to + // ensure that the glyphs are offset to the initial position. glyphOffsets[i] -= dir * distance } } @@ -582,27 +616,25 @@ class AnimatableClockView @JvmOverloads constructor( } private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float { - // The delay for the digit, in terms of fraction (i.e. the digit should not move - // during 0.0 - 0.1). - val digitInitialDelay = - if (isMovingToCenter) { - moveToCenterDelays[digit] * MOVE_DIGIT_STEP - } else { - moveToSideDelays[digit] * MOVE_DIGIT_STEP - } + // The delay for the digit, in terms of fraction. + // (i.e. the digit should not move during 0.0 - 0.1). + val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays + val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP return MOVE_INTERPOLATOR.getInterpolation( - constrainedMap( - 0.0f, - 1.0f, - digitInitialDelay, - digitInitialDelay + AVAILABLE_ANIMATION_TIME, - fraction, - ) + constrainedMap( + /* rangeMin= */ 0.0f, + /* rangeMax= */ 1.0f, + /* valueMin= */ digitInitialDelay, + /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME, + /* value= */ fraction, ) + ) } - // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. - // This is an optimization to ensure we only recompute the patterns when the inputs change. + /** + * DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. This + * is a cache optimization to ensure we only recompute the patterns when the inputs change. + */ private object Patterns { var sClockView12: String? = null var sClockView24: String? = null @@ -610,21 +642,22 @@ class AnimatableClockView @JvmOverloads constructor( fun update(context: Context) { val locale = Locale.getDefault() - val res = context.resources - val clockView12Skel = res.getString(R.string.clock_12hr_format) - val clockView24Skel = res.getString(R.string.clock_24hr_format) - val key = locale.toString() + clockView12Skel + clockView24Skel - if (key == sCacheKey) return - - val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel) - sClockView12 = clockView12 - - // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton - // format. The following code removes the AM/PM indicator if we didn't want it. - if (!clockView12Skel.contains("a")) { - sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' } + val clockView12Skel = context.resources.getString(R.string.clock_12hr_format) + val clockView24Skel = context.resources.getString(R.string.clock_24hr_format) + val key = "$locale$clockView12Skel$clockView24Skel" + if (key == sCacheKey) { + return } + sClockView12 = + DateFormat.getBestDateTimePattern(locale, clockView12Skel).let { + // CLDR insists on adding an AM/PM indicator even though it wasn't in the format + // string. The following code removes the AM/PM indicator if we didn't want it. + if (!clockView12Skel.contains("a")) { + it.replace("a".toRegex(), "").trim { it <= ' ' } + } else it + } + sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel) sCacheKey = key } diff --git a/packages/SystemUI/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/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt index 3d8159e70061..9c9ee53d9c56 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt @@ -24,7 +24,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.util.settings.FakeSettings -import com.google.common.truth.Truth import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -66,7 +65,7 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() { runCurrent() - Truth.assertThat(actualValue).isFalse() + assertThat(actualValue).isFalse() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt new file mode 100644 index 000000000000..ca824cbdd53b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.UserHandle +import android.provider.Settings +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.user.utils.UserScopedService +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.fakeGlobalSettings +import com.android.systemui.util.settings.fakeSettings +import com.android.systemui.utils.leaks.FakeLocationController +import com.google.common.truth.Truth.assertThat +import java.time.LocalTime +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.verify + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class NightDisplayRepositoryTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testUser = UserHandle.of(1)!! + private val testStartTime = LocalTime.MIDNIGHT + private val testEndTime = LocalTime.NOON + private val colorDisplayManager = + mock<ColorDisplayManager> { + whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED) + whenever(isNightDisplayActivated).thenReturn(false) + whenever(nightDisplayCustomStartTime).thenReturn(testStartTime) + whenever(nightDisplayCustomEndTime).thenReturn(testEndTime) + } + private val locationController = FakeLocationController(LeakCheck()) + private val nightDisplayListener = mock<NightDisplayListener>() + private val listenerBuilder = + mock<NightDisplayListenerModule.Builder> { + whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this) + whenever(build()).thenReturn(nightDisplayListener) + } + private val globalSettings = kosmos.fakeGlobalSettings + private val secureSettings = kosmos.fakeSettings + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val userScopedColorDisplayManager = + mock<UserScopedService<ColorDisplayManager>> { + whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager) + } + + private val underTest = + NightDisplayRepository( + testDispatcher, + scope.backgroundScope, + globalSettings, + secureSettings, + listenerBuilder, + userScopedColorDisplayManager, + locationController, + ) + + @Before + fun setup() { + enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser) + } + + @Test + fun nightDisplayState_matchesAutoMode() = + scope.runTest { + enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser) + val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>() + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + + runCurrent() + + verify(nightDisplayListener).setCallback(callbackCaptor.capture()) + val callback = callbackCaptor.value + + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_DISABLED) + + callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) + + callback.onCustomStartTimeChanged(testStartTime) + assertThat(lastState!!.startTime).isEqualTo(testStartTime) + + callback.onCustomEndTimeChanged(testEndTime) + assertThat(lastState!!.endTime).isEqualTo(testEndTime) + + callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_TWILIGHT) + + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT) + } + + @Test + fun nightDisplayState_matchesIsNightDisplayActivated() = + scope.runTest { + val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>() + + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + verify(nightDisplayListener).setCallback(callbackCaptor.capture()) + val callback = callbackCaptor.value + assertThat(lastState!!.isActivated) + .isEqualTo(colorDisplayManager.isNightDisplayActivated) + + callback.onActivated(true) + assertThat(lastState!!.isActivated).isTrue() + + callback.onActivated(false) + assertThat(lastState!!.isActivated).isFalse() + } + + @Test + fun nightDisplayState_matchesController_initiallyCustomAutoMode() = + scope.runTest { + whenever(colorDisplayManager.nightDisplayAutoMode) + .thenReturn(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) + + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) + } + + @Test + fun nightDisplayState_matchesController_initiallyTwilightAutoMode() = + scope.runTest { + whenever(colorDisplayManager.nightDisplayAutoMode) + .thenReturn(ColorDisplayManager.AUTO_MODE_TWILIGHT) + + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT) + } + + @Test + fun nightDisplayState_matchesForceAutoMode() = + scope.runTest { + enrollInForcedNightDisplayAutoMode(false, testUser) + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + assertThat(lastState!!.shouldForceAutoMode).isEqualTo(false) + + enrollInForcedNightDisplayAutoMode(true, testUser) + assertThat(lastState!!.shouldForceAutoMode).isEqualTo(true) + } + + private fun enrollInForcedNightDisplayAutoMode(enroll: Boolean, userHandle: UserHandle) { + globalSettings.putString( + Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE, + if (enroll) NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE + else NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE + ) + secureSettings.putIntForUser( + Settings.Secure.NIGHT_DISPLAY_AUTO_MODE, + if (enroll) NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET else NIGHT_DISPLAY_AUTO_MODE_RAW_SET, + userHandle.identifier + ) + } + + private companion object { + const val INITIALLY_FORCE_AUTO_MODE = false + const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1" + const val NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE = "0" + const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1 + const val NIGHT_DISPLAY_AUTO_MODE_RAW_SET = 0 + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/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/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 456fb79d0536..766798c2c2c3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -23,6 +23,7 @@ import android.app.smartspace.SmartspaceTarget import android.appwidget.AppWidgetProviderInfo import android.content.Intent import android.content.pm.UserInfo +import android.graphics.Bitmap import android.os.UserHandle import android.os.UserManager import android.os.userManager @@ -871,7 +872,14 @@ class CommunalInteractorTest : SysuiTestCase() { // One widget is filtered out and the remaining two link to main user id. assertThat(checkNotNull(widgetContent).size).isEqualTo(2) widgetContent!!.forEachIndexed { _, model -> - assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id) + assertThat(model is CommunalContentModel.WidgetContent.Widget).isTrue() + assertThat( + (model as CommunalContentModel.WidgetContent.Widget) + .providerInfo + .profile + ?.identifier + ) + .isEqualTo(MAIN_USER_INFO.id) } } @@ -1037,9 +1045,9 @@ class CommunalInteractorTest : SysuiTestCase() { runCurrent() val widgetContent by collectLastValue(underTest.widgetContent) - // Given three widgets, and one of them is associated with work profile. + // One available work widget, one pending work widget, and one regular available widget. val widget1 = createWidgetForUser(1, USER_INFO_WORK.id) - val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id) + val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id) val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id) val widgets = listOf(widget1, widget2, widget3) widgetRepository.setCommunalWidgets(widgets) @@ -1049,11 +1057,9 @@ class CommunalInteractorTest : SysuiTestCase() { DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL ) - // Widget under work profile is filtered out and the remaining two link to main user id. - assertThat(widgetContent).hasSize(2) - widgetContent!!.forEach { model -> - assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id) - } + // Widgets under work profile are filtered out. Only the regular widget remains. + assertThat(widgetContent).hasSize(1) + assertThat(widgetContent?.get(0)?.appWidgetId).isEqualTo(3) } @Test @@ -1076,7 +1082,7 @@ class CommunalInteractorTest : SysuiTestCase() { val widgetContent by collectLastValue(underTest.widgetContent) // Given three widgets, and one of them is associated with work profile. val widget1 = createWidgetForUser(1, USER_INFO_WORK.id) - val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id) + val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id) val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id) val widgets = listOf(widget1, widget2, widget3) widgetRepository.setCommunalWidgets(widgets) @@ -1086,10 +1092,11 @@ class CommunalInteractorTest : SysuiTestCase() { DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE ) - // Widget under work profile is available. + // Widgets under work profile are available. assertThat(widgetContent).hasSize(3) - assertThat(widgetContent!![0].providerInfo.profile?.identifier) - .isEqualTo(USER_INFO_WORK.id) + assertThat(widgetContent?.get(0)?.appWidgetId).isEqualTo(1) + assertThat(widgetContent?.get(1)?.appWidgetId).isEqualTo(2) + assertThat(widgetContent?.get(2)?.appWidgetId).isEqualTo(3) } @Test @@ -1182,8 +1189,11 @@ class CommunalInteractorTest : SysuiTestCase() { ) } - private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel = - mock<CommunalWidgetContentModel> { + private fun createWidgetForUser( + appWidgetId: Int, + userId: Int + ): CommunalWidgetContentModel.Available = + mock<CommunalWidgetContentModel.Available> { whenever(this.appWidgetId).thenReturn(appWidgetId) val providerInfo = mock<AppWidgetProviderInfo>().apply { @@ -1193,11 +1203,27 @@ class CommunalInteractorTest : SysuiTestCase() { whenever(this.providerInfo).thenReturn(providerInfo) } + private fun createPendingWidgetForUser( + appWidgetId: Int, + priority: Int = 0, + packageName: String = "", + icon: Bitmap? = null, + userId: Int = 0, + ): CommunalWidgetContentModel.Pending { + return CommunalWidgetContentModel.Pending( + appWidgetId = appWidgetId, + priority = priority, + packageName = packageName, + icon = icon, + user = UserHandle(userId), + ) + } + private fun createWidgetWithCategory( appWidgetId: Int, category: Int ): CommunalWidgetContentModel = - mock<CommunalWidgetContentModel> { + mock<CommunalWidgetContentModel.Available> { whenever(this.appWidgetId).thenReturn(appWidgetId) val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category } whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt index 02d927a0e5ab..f9d5073799db 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt @@ -86,4 +86,26 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { ) assertThat(isUmoOnCommunal).isFalse() } + + @Test + fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() = + testScope.runTest { + val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal) + assertThat(isUmoOnCommunal).isNull() + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.OCCLUDED, + to = KeyguardState.GLANCEABLE_HUB, + testScope + ) + assertThat(isUmoOnCommunal).isTrue() + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + testScope + ) + + assertThat(isUmoOnCommunal).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt index 89a4c04015b6..b3a12a69d1af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt @@ -28,6 +28,8 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -36,6 +38,9 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -96,4 +101,137 @@ class CommunalAppWidgetHostTest : SysuiTestCase() { assertThat(appWidgetIdToRemove).isEqualTo(2) } + + @Test + fun observer_onHostStartListeningTriggeredWhileObserverActive() = + testScope.runTest { + // Observer added + val observer = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer) + runCurrent() + + // Verify callback triggered + verify(observer, never()).onHostStartListening() + underTest.startListening() + runCurrent() + verify(observer).onHostStartListening() + + clearInvocations(observer) + + // Observer removed + underTest.removeObserver(observer) + runCurrent() + + // Verify callback not triggered + underTest.startListening() + runCurrent() + verify(observer, never()).onHostStartListening() + } + + @Test + fun observer_onHostStopListeningTriggeredWhileObserverActive() = + testScope.runTest { + // Observer added + val observer = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer) + runCurrent() + + // Verify callback triggered + verify(observer, never()).onHostStopListening() + underTest.stopListening() + runCurrent() + verify(observer).onHostStopListening() + + clearInvocations(observer) + + // Observer removed + underTest.removeObserver(observer) + runCurrent() + + // Verify callback not triggered + underTest.stopListening() + runCurrent() + verify(observer, never()).onHostStopListening() + } + + @Test + fun observer_onAllocateAppWidgetIdTriggeredWhileObserverActive() = + testScope.runTest { + // Observer added + val observer = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer) + runCurrent() + + // Verify callback triggered + verify(observer, never()).onAllocateAppWidgetId(any()) + val id = underTest.allocateAppWidgetId() + runCurrent() + verify(observer).onAllocateAppWidgetId(eq(id)) + + clearInvocations(observer) + + // Observer removed + underTest.removeObserver(observer) + runCurrent() + + // Verify callback not triggered + underTest.allocateAppWidgetId() + runCurrent() + verify(observer, never()).onAllocateAppWidgetId(any()) + } + + @Test + fun observer_onDeleteAppWidgetIdTriggeredWhileObserverActive() = + testScope.runTest { + // Observer added + val observer = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer) + runCurrent() + + // Verify callback triggered + verify(observer, never()).onDeleteAppWidgetId(any()) + underTest.deleteAppWidgetId(1) + runCurrent() + verify(observer).onDeleteAppWidgetId(eq(1)) + + clearInvocations(observer) + + // Observer removed + underTest.removeObserver(observer) + runCurrent() + + // Verify callback not triggered + underTest.deleteAppWidgetId(2) + runCurrent() + verify(observer, never()).onDeleteAppWidgetId(any()) + } + + @Test + fun observer_multipleObservers() = + testScope.runTest { + // Set up two observers + val observer1 = mock<CommunalAppWidgetHost.Observer>() + val observer2 = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer1) + underTest.addObserver(observer2) + runCurrent() + + // Verify both observers triggered + verify(observer1, never()).onHostStartListening() + verify(observer2, never()).onHostStartListening() + underTest.startListening() + runCurrent() + verify(observer1).onHostStartListening() + verify(observer2).onHostStartListening() + + // Observer 1 removed + underTest.removeObserver(observer1) + runCurrent() + + // Verify only observer 2 is triggered + underTest.stopListening() + runCurrent() + verify(observer2).onHostStopListening() + verify(observer1, never()).onHostStopListening() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 9aebc305fb56..6ca04dfca6a4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -123,12 +123,12 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { // Widgets available. val widgets = listOf( - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 0, priority = 30, providerInfo = providerInfo, ), - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 1, priority = 20, providerInfo = providerInfo, @@ -177,12 +177,12 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { // Widgets available. val widgets = listOf( - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 0, priority = 30, providerInfo = providerInfo, ), - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 1, priority = 20, providerInfo = providerInfo, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 569116c6124a..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/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/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index cb2d4e07e3a2..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 @@ -60,17 +60,19 @@ class KeyguardInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val repository = kosmos.fakeKeyguardRepository - private val sceneInteractor = kosmos.sceneInteractor - private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor - private val commandQueue = kosmos.fakeCommandQueue - private val configRepository = kosmos.fakeConfigurationRepository - private val bouncerRepository = kosmos.keyguardBouncerRepository - private val shadeRepository = kosmos.shadeRepository - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + 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 { 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 = kosmos.keyguardInteractor + + private val underTest by lazy { kosmos.keyguardInteractor } @Before fun setUp() { @@ -275,6 +277,28 @@ class KeyguardInteractorTest : SysuiTestCase() { } @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) 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 bf0939c6c46f..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)) @@ -482,6 +524,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun isInTransitionToState() = testScope.runTest { val results by collectValues(underTest.isInTransitionToState(GONE)) @@ -586,7 +629,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, STARTED), + TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED), ) assertThat(results) @@ -598,7 +641,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, RUNNING), + TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING), ) assertThat(results) @@ -610,7 +653,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, FINISHED), + TransitionStep(DOZING, LOCKSCREEN, 0f, FINISHED), ) assertThat(results) @@ -623,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) @@ -638,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) @@ -1404,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 f52c66e24907..cde703b70fe6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt @@ -43,7 +43,7 @@ import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope val repository = kosmos.fakeKeyguardTransitionRepository @@ -60,7 +60,7 @@ class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization?) : Sy } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt index fee18dd94a91..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/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 e3ae3ba4cedd..bc381f2abc6d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt @@ -49,7 +49,7 @@ import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) } @@ -73,7 +73,7 @@ class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization?) : Sy } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index adeb395fc142..933779312df5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -51,7 +51,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { @@ -73,7 +73,7 @@ class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization?) } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index f8da74fdd742..6ce7e88edc2b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -54,7 +54,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } @@ -76,7 +76,7 @@ class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization?) } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index d5df159d6d1e..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 @@ -18,8 +18,10 @@ package com.android.systemui.keyguard.ui.viewmodel 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 @@ -30,11 +32,16 @@ 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.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 @@ -46,7 +53,7 @@ import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization?) : +class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { @@ -58,6 +65,11 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza private val keyguardRepository = kosmos.fakeKeyguardRepository private lateinit var underTest: LockscreenToPrimaryBouncerTransitionViewModel + private val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + companion object { @JvmStatic @Parameters(name = "{0}") @@ -67,7 +79,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -76,6 +88,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza } @Test + @BrokenWithSceneContainer(330311871) fun deviceEntryParentViewAlpha_shadeExpanded() = testScope.runTest { val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) @@ -107,6 +120,17 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza 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() @@ -132,7 +156,9 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza ): 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" 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..365a7c3a296a 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) @@ -211,6 +215,21 @@ class MediaControlInteractorTest : SysuiTestCase() { ) } + @Test + fun removeMediaControl() { + val listener = mock<MediaDataProcessor.Listener>() + kosmos.mediaDataProcessor.addInternalListener(listener) + + var mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST) + kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData) + + underTest.removeMediaControl(null, instanceId, 0L) + kosmos.fakeExecutor.advanceClockToNext() + kosmos.fakeExecutor.runAllReady() + + verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) + } + companion object { private const val USER_ID = 0 private const val KEY = "key" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt index d1e475f590dd..4226a9d89ad1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt @@ -92,13 +92,29 @@ class MediaCarouselViewModelTest : SysuiTestCase() { val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) - loadMediaControl(KEY, instanceId1) - loadMediaControl(KEY_2, instanceId2) + loadMediaControl(KEY, instanceId1, isPlaying = true) + loadMediaControl(KEY_2, instanceId2, isPlaying = true) + loadMediaControl(KEY, instanceId1, isPlaying = false) - val firstMediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl - val secondMediaControl = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl - assertThat(firstMediaControl.instanceId).isEqualTo(instanceId2) - assertThat(secondMediaControl.instanceId).isEqualTo(instanceId1) + var mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl + var mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl + assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) + assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) + + loadMediaControl(KEY, instanceId1, isPlaying = true) + loadMediaControl(KEY_2, instanceId2, isPlaying = false) + + mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl + mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl + assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) + assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) + + underTest.onAttached() + + mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl + mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl + assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) + assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) } @Test @@ -147,6 +163,7 @@ class MediaCarouselViewModelTest : SysuiTestCase() { val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl assertThat(sortedMedia).hasSize(2) assertThat(mediaControl.instanceId).isEqualTo(instanceId) + assertThat(mediaControl.isMediaFromRec).isTrue() } private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt index 5661bd388757..9d8ec951dfe7 100644 --- 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 @@ -51,8 +51,8 @@ class NotificationsShadeSceneViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor - private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } private val underTest = kosmos.notificationsShadeSceneViewModel diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt new file mode 100644 index 000000000000..8d1aa73aa55c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import android.app.PendingIntent +import android.content.ComponentName +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.content.ServiceConnection +import android.content.applicationContext +import android.content.packageManager +import android.os.Binder +import android.os.Handler +import android.os.RemoteException +import android.os.UserHandle +import android.platform.test.annotations.EnableFlags +import android.service.quicksettings.Tile +import android.testing.TestableContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX +import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.testCase +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade +import com.android.systemui.qs.tiles.impl.custom.customTileSpec +import com.android.systemui.testKosmos +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.fakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString + +@RunWith(AndroidJUnit4::class) +@SmallTest +class CloseShadeRightAfterClickTestB339290820 : SysuiTestCase() { + + private val testableContext: TestableContext + private val bindDelayExecutor: FakeExecutor + private val kosmos = + testKosmos().apply { + testableContext = testCase.context + bindDelayExecutor = FakeExecutor(fakeSystemClock) + testableContext.setMockPackageManager(packageManager) + customTileSpec = TileSpec.create(testComponentName) + applicationContext = ContextWrapperDelayedBind(testableContext, bindDelayExecutor) + } + + @Before + fun setUp() { + kosmos.apply { + whenever(packageManager.getPackageUidAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(Binder.getCallingUid()) + packageManagerAdapterFacade.setIsActive(true) + testableContext.addMockService(testComponentName, iQSTileService.asBinder()) + } + } + + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + fun testStopListeningShortlyAfterClick_clickIsSent() { + with(kosmos) { + val tile = FakeCustomTileInterface(tileServices) + // Flush any bind from startup + FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor) + + // Open QS + tile.setListening(true) + fakeExecutor.runAllReady() + tile.click() + fakeExecutor.runAllReady() + + // No clicks yet because the latch is preventing the bind + assertThat(iQSTileService.clicks).isEmpty() + + // Close QS + tile.setListening(false) + fakeExecutor.runAllReady() + // And finally bind + FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor) + + assertThat(iQSTileService.clicks).containsExactly(tile.token) + } + } +} + +private val testComponentName = ComponentName("pkg", "srv") + +// This is a fake `CustomTile` that implements what we need for the test. Mainly setListening and +// click +private class FakeCustomTileInterface(tileServices: TileServices) : CustomTileInterface { + override val user: Int + get() = 0 + override val qsTile: Tile = Tile() + override val component: ComponentName = testComponentName + private var listening = false + private val serviceManager = tileServices.getTileWrapper(this) + private val serviceInterface = serviceManager.tileService + + val token = Binder() + + override fun getTileSpec(): String { + return CustomTile.toSpec(component) + } + + override fun refreshState() {} + + override fun updateTileState(tile: Tile, uid: Int) {} + + override fun onDialogShown() {} + + override fun onDialogHidden() {} + + override fun startActivityAndCollapse(pendingIntent: PendingIntent) {} + + override fun startUnlockAndRun() {} + + fun setListening(listening: Boolean) { + if (listening == this.listening) return + this.listening = listening + + try { + if (listening) { + if (!serviceManager.isActiveTile) { + serviceManager.setBindRequested(true) + serviceInterface.onStartListening() + } + } else { + serviceInterface.onStopListening() + serviceManager.setBindRequested(false) + } + } catch (e: RemoteException) { + // Called through wrapper, won't happen here. + } + } + + fun click() { + try { + if (serviceManager.isActiveTile) { + serviceManager.setBindRequested(true) + serviceInterface.onStartListening() + } + serviceInterface.onClick(token) + } catch (e: RemoteException) { + // Called through wrapper, won't happen here. + } + } +} + +private class ContextWrapperDelayedBind( + val context: Context, + val executor: FakeExecutor, +) : ContextWrapper(context) { + override fun bindServiceAsUser( + service: Intent, + conn: ServiceConnection, + flags: Int, + user: UserHandle + ): Boolean { + executor.execute { super.bindServiceAsUser(service, conn, flags, user) } + return true + } + + override fun bindServiceAsUser( + service: Intent, + conn: ServiceConnection, + flags: BindServiceFlags, + user: UserHandle + ): Boolean { + executor.execute { super.bindServiceAsUser(service, conn, flags, user) } + return true + } + + override fun bindServiceAsUser( + service: Intent?, + conn: ServiceConnection?, + flags: Int, + handler: Handler?, + user: UserHandle? + ): Boolean { + executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) } + return true + } + + override fun bindServiceAsUser( + service: Intent, + conn: ServiceConnection, + flags: BindServiceFlags, + handler: Handler, + user: UserHandle + ): Boolean { + executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) } + return true + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt index da60c18dcfd7..dfc004a126bd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt @@ -30,10 +30,10 @@ import com.android.systemui.qs.external.TileServiceKey import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat import com.android.systemui.qs.tiles.impl.custom.commons.copy +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first @@ -47,11 +47,11 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) class CustomTileRepositoryTest : SysuiTestCase() { - private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) } + private val kosmos = Kosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) } private val underTest: CustomTileRepository = with(kosmos) { CustomTileRepositoryImpl( - tileSpec, + customTileSpec, customTileStatePersister, packageManagerAdapterFacade.packageManagerAdapter, testScope.testScheduler, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt index a5c554406848..a29289a7409a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt @@ -38,8 +38,8 @@ import com.android.systemui.qs.tiles.impl.custom.customTileInteractor import com.android.systemui.qs.tiles.impl.custom.customTilePackagesUpdatesRepository import com.android.systemui.qs.tiles.impl.custom.customTileRepository import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.user.data.repository.userRepository @@ -60,12 +60,12 @@ class CustomTileDataInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().apply { componentName = TEST_COMPONENT - tileSpec = TileSpec.create(componentName) + customTileSpec = TileSpec.create(componentName) } private val underTest = with(kosmos) { CustomTileDataInteractor( - tileSpec = tileSpec, + tileSpec = customTileSpec, defaultsRepository = customTileDefaultsRepository, serviceInteractor = customTileServiceInteractor, customTileInteractor = customTileInteractor, @@ -180,7 +180,7 @@ class CustomTileDataInteractorTest : SysuiTestCase() { setup() customTileDefaultsRepository.putDefaults( TEST_USER_1.userHandle, - tileSpec.componentName, + customTileSpec.componentName, CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label), ) @@ -198,7 +198,7 @@ class CustomTileDataInteractorTest : SysuiTestCase() { setup() customTileDefaultsRepository.putDefaults( TEST_USER_1.userHandle, - tileSpec.componentName, + customTileSpec.componentName, CustomTileDefaults.Error, ) @@ -216,7 +216,7 @@ class CustomTileDataInteractorTest : SysuiTestCase() { setup() customTileDefaultsRepository.putDefaults( TEST_USER_2.userHandle, - tileSpec.componentName, + customTileSpec.componentName, CustomTileDefaults.Error, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt index 9546a32e2a06..33299d9ce74c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt @@ -31,9 +31,9 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.customTileRepository +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,12 +50,12 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) class CustomTileInteractorTest : SysuiTestCase() { - private val kosmos = testKosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) } + private val kosmos = testKosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) } private val underTest: CustomTileInteractor = with(kosmos) { CustomTileInteractor( - tileSpec = tileSpec, + tileSpec = customTileSpec, defaultsRepository = customTileDefaultsRepository, customTileRepository = customTileRepository, tileScope = testScope.backgroundScope, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt index a2127a4717ce..3972938d7b1b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt @@ -33,9 +33,9 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat import com.android.systemui.qs.tiles.impl.custom.customTileQsTileConfig +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any @@ -51,7 +51,8 @@ import org.junit.runner.RunWith class CustomTileMapperTest : SysuiTestCase() { private val uriGrantsManager: IUriGrantsManager = mock {} - private val kosmos = testKosmos().apply { tileSpec = TileSpec.Companion.create(TEST_COMPONENT) } + private val kosmos = + testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) } private val underTest by lazy { CustomTileMapper( context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) }, @@ -202,7 +203,7 @@ class CustomTileMapperTest : SysuiTestCase() { ) = CustomTileDataModel( UserHandle.of(1), - tileSpec.componentName, + customTileSpec.componentName, Tile().apply { state = tileState label = "test label" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt index c709f16c3213..72e5766e409a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt @@ -44,9 +44,9 @@ import com.android.systemui.qs.tiles.base.actions.pendingIntentInputs import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.custom.qsTileLogger -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any @@ -68,7 +68,7 @@ class CustomTileUserActionInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().apply { componentName = TEST_COMPONENT - tileSpec = TileSpec.create(componentName) + customTileSpec = TileSpec.create(componentName) testCase = this@CustomTileUserActionInteractorTest } @@ -79,7 +79,7 @@ class CustomTileUserActionInteractorTest : SysuiTestCase() { mock { whenever(packageManager).thenReturn(packageManagerFacade.packageManager) }, - tileSpec = tileSpec, + tileSpec = customTileSpec, qsTileLogger = qsTileLogger, windowManager = windowManagerFacade.windowManager, displayTracker = mock {}, @@ -227,7 +227,7 @@ class CustomTileUserActionInteractorTest : SysuiTestCase() { private fun pendingIntent(): PendingIntent = mock { whenever(isActivity).thenReturn(true) } private fun Kosmos.customTileModel( - componentName: ComponentName = tileSpec.componentName, + componentName: ComponentName = customTileSpec.componentName, activityLaunchForClick: PendingIntent? = null, tileState: Int = 111, ) = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt new file mode 100644 index 000000000000..a0aa2d4a9a6c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.domain.interactor + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.UserHandle +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.NightDisplayRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.user.utils.UserScopedService +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.fakeGlobalSettings +import com.android.systemui.util.settings.fakeSettings +import com.android.systemui.util.time.DateFormatUtil +import com.android.systemui.utils.leaks.FakeLocationController +import com.google.common.truth.Truth.assertThat +import java.time.LocalTime +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NightDisplayTileDataInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testUser = UserHandle.of(1)!! + private val testStartTime = LocalTime.MIDNIGHT + private val testEndTime = LocalTime.NOON + private val colorDisplayManager = + mock<ColorDisplayManager> { + whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED) + whenever(isNightDisplayActivated).thenReturn(false) + whenever(nightDisplayCustomStartTime).thenReturn(testStartTime) + whenever(nightDisplayCustomEndTime).thenReturn(testEndTime) + } + private val locationController = FakeLocationController(LeakCheck()) + private val nightDisplayListener = mock<NightDisplayListener>() + private val listenerBuilder = + mock<NightDisplayListenerModule.Builder> { + whenever(setUser(anyInt())).thenReturn(this) + whenever(build()).thenReturn(nightDisplayListener) + } + private val globalSettings = kosmos.fakeGlobalSettings + private val secureSettings = kosmos.fakeSettings + private val dateFormatUtil = mock<DateFormatUtil> { whenever(is24HourFormat).thenReturn(false) } + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val userScopedColorDisplayManager = + mock<UserScopedService<ColorDisplayManager>> { + whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager) + } + private val nightDisplayRepository = + NightDisplayRepository( + testDispatcher, + scope.backgroundScope, + globalSettings, + secureSettings, + listenerBuilder, + userScopedColorDisplayManager, + locationController, + ) + + private val underTest: NightDisplayTileDataInteractor = + NightDisplayTileDataInteractor(context, dateFormatUtil, nightDisplayRepository) + + @Test + fun availability_matchesColorDisplayManager() = runTest { + val availability by collectLastValue(underTest.availability(testUser)) + + val expectedAvailability = ColorDisplayManager.isNightDisplayAvailable(context) + assertThat(availability).isEqualTo(expectedAvailability) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..adc8bcba5a5c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.domain.interactor + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.UserHandle +import android.provider.Settings +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.NightDisplayRepository +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.intentInputs +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.custom.qsTileLogger +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.user.utils.UserScopedService +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.fakeGlobalSettings +import com.android.systemui.util.settings.fakeSettings +import com.android.systemui.utils.leaks.FakeLocationController +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NightDisplayTileUserActionInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler() + private val testUser = UserHandle.of(1) + private val colorDisplayManager = + mock<ColorDisplayManager> { + whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED) + whenever(isNightDisplayActivated).thenReturn(false) + } + private val locationController = FakeLocationController(LeakCheck()) + private val nightDisplayListener = mock<NightDisplayListener>() + private val listenerBuilder = + mock<NightDisplayListenerModule.Builder> { + whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this) + whenever(build()).thenReturn(nightDisplayListener) + } + private val globalSettings = kosmos.fakeGlobalSettings + private val secureSettings = kosmos.fakeSettings + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val userScopedColorDisplayManager = + mock<UserScopedService<ColorDisplayManager>> { + whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager) + } + private val nightDisplayRepository = + NightDisplayRepository( + testDispatcher, + scope.backgroundScope, + globalSettings, + secureSettings, + listenerBuilder, + userScopedColorDisplayManager, + locationController, + ) + + private val underTest = + NightDisplayTileUserActionInteractor( + nightDisplayRepository, + qsTileIntentUserActionHandler, + kosmos.qsTileLogger + ) + + @Test + fun handleClick_inactive_activates() = + scope.runTest { + val startingModel = NightDisplayTileModel.AutoModeOff(false, false) + + underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser)) + + verify(colorDisplayManager).setNightDisplayActivated(true) + } + + @Test + fun handleClick_active_disables() = + scope.runTest { + val startingModel = NightDisplayTileModel.AutoModeOff(true, false) + + underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser)) + + verify(colorDisplayManager).setNightDisplayActivated(false) + } + + @Test + fun handleClick_whenAutoModeTwilight_flipsState() = + scope.runTest { + val originalState = true + val startingModel = NightDisplayTileModel.AutoModeTwilight(originalState, false, false) + + underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser)) + + verify(colorDisplayManager).setNightDisplayActivated(!originalState) + } + + @Test + fun handleClick_whenAutoModeCustom_flipsState() = + scope.runTest { + val originalState = true + val startingModel = + NightDisplayTileModel.AutoModeCustom(originalState, false, null, null, false) + + underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser)) + + verify(colorDisplayManager).setNightDisplayActivated(!originalState) + } + + @Test + fun handleLongClickWhenEnabled() = + scope.runTest { + val enabledState = true + + underTest.handleInput( + QSTileInputTestKtx.longClick( + NightDisplayTileModel.AutoModeOff(enabledState, false), + testUser + ) + ) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } + + @Test + fun handleLongClickWhenDisabled() = + scope.runTest { + val enabledState = false + + underTest.handleInput( + QSTileInputTestKtx.longClick( + NightDisplayTileModel.AutoModeOff(enabledState, false), + testUser + ) + ) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt new file mode 100644 index 000000000000..5d2e7013c2f4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.ui + +import android.graphics.drawable.TestStubDrawable +import android.service.quicksettings.Tile +import android.text.TextUtils +import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.qs.tiles.impl.night.qsNightDisplayTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.util.mockito.mock +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NightDisplayTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val config = kosmos.qsNightDisplayTileConfig + + private val testStartTime = LocalTime.MIDNIGHT + private val testEndTime = LocalTime.NOON + + private lateinit var mapper: NightDisplayTileMapper + + @Before + fun setup() { + mapper = + NightDisplayTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_nightlight_icon_on, TestStubDrawable()) + addOverride(R.drawable.qs_nightlight_icon_off, TestStubDrawable()) + } + .resources, + context.theme, + mock<QSTileLogger>(), + ) + } + + @Test + fun disabledModel_whenAutoModeOff() { + val inputModel = NightDisplayTileModel.AutoModeOff(false, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE] + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + /** Force enable does not change the mode by itself. */ + @Test + fun disabledModel_whenAutoModeOff_whenForceEnable() { + val inputModel = NightDisplayTileModel.AutoModeOff(false, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE] + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_whenAutoModeOff() { + val inputModel = NightDisplayTileModel.AutoModeOff(true, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE] + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_forceAutoMode_whenAutoModeOff() { + val inputModel = NightDisplayTileModel.AutoModeOff(true, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE] + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_autoModeTwilight_locationOff() { + val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = createNightDisplayTileState(QSTileState.ActivationState.ACTIVE, null) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_autoModeTwilight_locationOn() { + val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.getString(R.string.quick_settings_night_secondary_label_until_sunrise) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun disabledModel_autoModeTwilight_locationOn() { + val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun disabledModel_autoModeTwilight_locationOff() { + val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = createNightDisplayTileState(QSTileState.ActivationState.INACTIVE, null) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun disabledModel_autoModeCustom_24Hour() { + val inputModel = + NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.getString( + R.string.quick_settings_night_secondary_label_on_at, + formatter24Hour.format(testStartTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun disabledModel_autoModeCustom_12Hour() { + val inputModel = + NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.getString( + R.string.quick_settings_night_secondary_label_on_at, + formatter12Hour.format(testStartTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + /** Should have the same outcome as [disabledModel_autoModeCustom_12Hour] */ + @Test + fun disabledModel_autoModeCustom_12Hour_isEnrolledForcedAutoMode() { + val inputModel = + NightDisplayTileModel.AutoModeCustom(false, true, testStartTime, null, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.getString( + R.string.quick_settings_night_secondary_label_on_at, + formatter12Hour.format(testStartTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_autoModeCustom_24Hour() { + val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.getString( + R.string.quick_settings_secondary_label_until, + formatter24Hour.format(testEndTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_autoModeCustom_12Hour() { + val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.getString( + R.string.quick_settings_secondary_label_until, + formatter12Hour.format(testEndTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + /** Should have the same state as [enabledModel_autoModeCustom_24Hour] */ + @Test + fun enabledModel_autoModeCustom_24Hour_forceEnabled() { + val inputModel = NightDisplayTileModel.AutoModeCustom(true, true, null, testEndTime, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.getString( + R.string.quick_settings_secondary_label_until, + formatter24Hour.format(testEndTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createNightDisplayTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String? + ): QSTileState { + val label = context.getString(R.string.quick_settings_night_display_label) + + val contentDescription = + if (TextUtils.isEmpty(secondaryLabel)) label + else TextUtils.concat(label, ", ", secondaryLabel) + return QSTileState( + { + Icon.Loaded( + context.getDrawable( + if (activationState == QSTileState.ActivationState.ACTIVE) + R.drawable.qs_nightlight_icon_on + else R.drawable.qs_nightlight_icon_off + )!!, + null + ) + }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + contentDescription, + null, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } + + private companion object { + val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a") + val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 179ba42cc772..0b55befce932 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -35,7 +35,10 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor +import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter @@ -100,7 +103,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { footerActionsViewModelFactory = footerActionsViewModelFactory, footerActionsController = footerActionsController, sceneBackInteractor = sceneBackInteractor, - mediaDataManager = mediaDataManager, + mediaCarouselInteractor = kosmos.mediaCarouselInteractor, ) } @@ -236,20 +239,34 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { } @Test - fun hasMedia_mediaVisible() { + fun addAndRemoveMedia_mediaVisibilityIsUpdated() = testScope.runTest { - whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true) + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) + val isMediaVisible by collectLastValue(underTest.isMediaVisible) + val userMedia = MediaData(active = true) - assertThat(underTest.isMediaVisible()).isTrue() + assertThat(isMediaVisible).isFalse() + + kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + + assertThat(isMediaVisible).isTrue() + + kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId) + + assertThat(isMediaVisible).isFalse() } - } @Test - fun doesNotHaveMedia_mediaNotVisible() { + fun addInactiveMedia_mediaVisibilityIsUpdated() = testScope.runTest { - whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false) + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) + val isMediaVisible by collectLastValue(underTest.isMediaVisible) + val userMedia = MediaData(active = false) + + assertThat(isMediaVisible).isFalse() - assertThat(underTest.isMediaVisible()).isFalse() + kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + + assertThat(isMediaVisible).isTrue() } - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 4cb8bf84967b..9e7e766cb820 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -52,6 +52,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor @@ -209,7 +210,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { qsSceneAdapter = qsFlexiglassAdapter, notifications = kosmos.notificationsPlaceholderViewModel, brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel, - mediaDataManager = mediaDataManager, + mediaCarouselInteractor = kosmos.mediaCarouselInteractor, shadeInteractor = kosmos.shadeInteractor, footerActionsController = kosmos.footerActionsController, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/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/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 93465c804d0e..677477df8ea1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -437,12 +437,12 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() assertThat( sysUiState.flags and - QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0 + QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L ) .isTrue() assertThat( sysUiState.flags and - QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0 + QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L ) .isFalse() @@ -450,12 +450,12 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() assertThat( sysUiState.flags and - QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0 + QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L ) .isFalse() assertThat( sysUiState.flags and - QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0 + QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L ) .isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt new file mode 100644 index 000000000000..545a0c70719f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.ui.viewmodel + +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +@EnableSceneContainer +class GoneSceneViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val shadeRepository by lazy { kosmos.shadeRepository } + private lateinit var underTest: GoneSceneViewModel + + @Before + fun setUp() { + underTest = + GoneSceneViewModel( + applicationScope = testScope.backgroundScope, + shadeInteractor = kosmos.shadeInteractor, + ) + } + + @Test + fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + shadeRepository.setShadeMode(ShadeMode.Split) + runCurrent() + + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey) + .isEqualTo(ToSplitShade) + } + + @Test + fun downTransitionKey_splitShadeDisabled_isNull() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + shadeRepository.setShadeMode(ShadeMode.Single) + runCurrent() + + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt index e11a8f1e0ba9..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) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt index aa0ca186c1ee..78c4def5689b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt @@ -60,7 +60,7 @@ import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class ShadeInteractorImplTest(flags: FlagsParameterization?) : SysuiTestCase() { +class ShadeInteractorImplTest(flags: FlagsParameterization) : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope val configurationRepository by lazy { kosmos.fakeConfigurationRepository } @@ -85,7 +85,7 @@ class ShadeInteractorImplTest(flags: FlagsParameterization?) : SysuiTestCase() { } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt index 44c9695d8b03..cecc70cae60d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt @@ -60,7 +60,7 @@ import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() { +class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val shadeInteractor by lazy { kosmos.shadeInteractor } @@ -80,7 +80,7 @@ class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() { } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 5312ad809a72..482dc5d992f0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -32,13 +32,17 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor +import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.qs.footerActionsController import com.android.systemui.qs.footerActionsViewModelFactory import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -49,7 +53,6 @@ import com.android.systemui.testKosmos import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.Locale import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -93,7 +96,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { qsSceneAdapter = qsSceneAdapter, notifications = kosmos.notificationsPlaceholderViewModel, brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel, - mediaDataManager = mediaDataManager, + mediaCarouselInteractor = kosmos.mediaCarouselInteractor, shadeInteractor = kosmos.shadeInteractor, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, footerActionsController = kosmos.footerActionsController, @@ -159,6 +162,27 @@ class ShadeSceneViewModelTest : SysuiTestCase() { } @Test + fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + shadeRepository.setShadeMode(ShadeMode.Split) + runCurrent() + + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey) + .isEqualTo(ToSplitShade) + } + + @Test + fun upTransitionKey_splitShadeDisable_isNull() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + shadeRepository.setShadeMode(ShadeMode.Single) + runCurrent() + + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull() + } + + @Test fun isClickable_deviceUnlocked_false() = testScope.runTest { val isClickable by collectLastValue(underTest.isClickable) @@ -200,19 +224,20 @@ class ShadeSceneViewModelTest : SysuiTestCase() { } @Test - fun hasActiveMedia_mediaVisible() = + fun addAndRemoveMedia_mediaVisibilityisUpdated() = testScope.runTest { - whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true) + val isMediaVisible by collectLastValue(underTest.isMediaVisible) + val userMedia = MediaData(active = true) - assertThat(underTest.isMediaVisible()).isTrue() - } + assertThat(isMediaVisible).isFalse() - @Test - fun doesNotHaveActiveMedia_mediaNotVisible() = - testScope.runTest { - whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false) + kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + + assertThat(isMediaVisible).isTrue() + + kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId) - assertThat(underTest.isMediaVisible()).isFalse() + assertThat(isMediaVisible).isFalse() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt new file mode 100644 index 000000000000..8cb811de851b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.service.notification.NotificationListenerService +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.dumpManager +import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.mockNotifCollection +import com.android.systemui.statusbar.notification.collection.notifPipeline +import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider +import com.android.systemui.testKosmos +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationMediaManagerTest : SysuiTestCase() { + + private val KEY = "KEY" + + private val kosmos = testKosmos() + private val visibilityProvider = kosmos.notificationVisibilityProvider + private val notifPipeline = kosmos.notifPipeline + private val notifCollection = kosmos.mockNotifCollection + private val dumpManager = kosmos.dumpManager + private val mediaDataManager = mock<MediaDataManager>() + private val backgroundExecutor = FakeExecutor(FakeSystemClock()) + + private var listenerCaptor = argumentCaptor<MediaDataManager.Listener>() + + private lateinit var notificationMediaManager: NotificationMediaManager + + @Before + fun setup() { + notificationMediaManager = + NotificationMediaManager( + context, + visibilityProvider, + notifPipeline, + notifCollection, + mediaDataManager, + dumpManager, + backgroundExecutor, + ) + + verify(mediaDataManager).addListener(listenerCaptor.capture()) + } + + @Test + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + fun mediaDataRemoved_userInitiated_dismissNotif() { + val notifEntryCaptor = argumentCaptor<NotificationEntry>() + val notifEntry = mock<NotificationEntry>() + whenever(notifEntry.key).thenReturn(KEY) + whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking()) + whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry)) + + listenerCaptor.lastValue.onMediaDataRemoved(KEY, true) + + verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any()) + assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY) + } + + @Test + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + fun mediaDataRemoved_notUserInitiated_doesNotDismissNotif() { + listenerCaptor.lastValue.onMediaDataRemoved(KEY, false) + + verify(notifCollection, never()).dismissNotification(any(), any()) + } + + @Test + @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + fun mediaDataRemoved_notUserInitiated_flagOff_dismissNotif() { + val notifEntryCaptor = argumentCaptor<NotificationEntry>() + val notifEntry = mock<NotificationEntry>() + whenever(notifEntry.key).thenReturn(KEY) + whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking()) + whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry)) + + listenerCaptor.lastValue.onMediaDataRemoved(KEY, false) + + verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any()) + assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index d353a625b890..f06e04b70809 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -33,7 +33,10 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.parameterizeSceneContainerFlag import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -65,11 +68,11 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper -class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTestCase() { +class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val mockDarkAnimator = mock<ObjectAnimator>() private lateinit var underTest: StatusBarStateControllerImpl @@ -84,7 +87,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTes } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -98,6 +101,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTes uiEventLogger, { kosmos.interactionJankMonitor }, JavaAdapter(testScope.backgroundScope), + { kosmos.keyguardTransitionInteractor }, { kosmos.shadeInteractor }, { kosmos.deviceUnlockedInteractor }, { kosmos.sceneInteractor }, @@ -330,4 +334,25 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTes assertThat(currentScene).isEqualTo(Scenes.QuickSettings) assertThat(statusBarState).isEqualTo(StatusBarState.SHADE) } + + @Test + fun leaveOpenOnKeyguard_whenGone_isFalse() = + testScope.runTest { + underTest.start() + underTest.setLeaveOpenOnKeyguardHide(true) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope = testScope, + ) + assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(true) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, + ) + assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(false) + } } diff --git a/packages/SystemUI/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/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index cbbc4d897048..9367a93a2890 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -59,7 +59,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization?) : +class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { companion object { @@ -71,7 +71,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } private val kosmos = testKosmos() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index 7ac549a7b551..cc5df74e5e6e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -20,12 +20,13 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import android.app.NotificationManager.Policy import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags +import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState @@ -33,7 +34,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R -import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository @@ -56,11 +57,13 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @EnableFlags(FooterViewRefactor.FLAG_NAME) -class NotificationListViewModelTest : SysuiTestCase() { +class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } @@ -72,16 +75,30 @@ class NotificationListViewModelTest : SysuiTestCase() { private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository private val fakePowerRepository = kosmos.fakePowerRepository private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository - private val fakeShadeRepository = kosmos.fakeShadeRepository private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository private val headsUpRepository = kosmos.headsUpNotificationRepository private val zenModeRepository = kosmos.zenModeRepository - val underTest = kosmos.notificationListViewModel + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + + private lateinit var underTest: NotificationListViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Before fun setUp() { MockitoAnnotations.initMocks(this) + underTest = kosmos.notificationListViewModel } @Test @@ -163,7 +180,7 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) // AND quick settings are expanded - fakeShadeRepository.legacyQsFullscreen.value = true + shadeTestUtil.setQsFullscreen(true) runCurrent() // THEN empty shade is not visible @@ -178,9 +195,10 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) // AND quick settings are expanded - fakeShadeRepository.setQsExpansion(1f) - // AND split shade is enabled + shadeTestUtil.setQsExpansion(1f) + // AND split shade is expanded overrideResource(R.bool.config_use_split_notification_shade, true) + shadeTestUtil.setShadeExpansion(1f) fakeConfigurationController.notifyConfigurationChanged() runCurrent() @@ -290,7 +308,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer is visible @@ -306,7 +324,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open on lockscreen fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer is visible @@ -337,7 +355,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND user is not set up fakeUserSetupRepository.setUserSetUp(false) runCurrent() @@ -355,7 +373,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND device is starting to go to sleep fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP) runCurrent() @@ -373,10 +391,10 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND quick settings are expanded - fakeShadeRepository.setQsExpansion(1f) - fakeShadeRepository.legacyQsFullscreen.value = true + shadeTestUtil.setQsExpansion(1f) + shadeTestUtil.setQsFullscreen(true) runCurrent() // THEN footer is not visible @@ -390,11 +408,11 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) + // AND quick settings are expanded + shadeTestUtil.setQsExpansion(1f) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) - // AND quick settings are expanded - fakeShadeRepository.setQsExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND split shade is enabled overrideResource(R.bool.config_use_split_notification_shade, true) fakeConfigurationController.notifyConfigurationChanged() @@ -413,7 +431,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND remote input is active fakeRemoteInputRepository.isRemoteInputActive.value = true runCurrent() @@ -431,7 +449,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open and fully expanded fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer visibility animates @@ -447,7 +465,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND we are on the keyguard fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer visibility does not animate @@ -461,7 +479,7 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN shade is closed fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(0f) + shadeTestUtil.setShadeExpansion(0f) runCurrent() // THEN footer is hidden @@ -475,7 +493,7 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer is hidden @@ -489,8 +507,8 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN QS partially open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setQsExpansion(0.5f) - fakeShadeRepository.setLegacyShadeExpansion(0.5f) + shadeTestUtil.setQsExpansion(0.5f) + shadeTestUtil.setShadeExpansion(0.5f) runCurrent() // THEN footer is hidden @@ -588,7 +606,7 @@ class NotificationListViewModelTest : SysuiTestCase() { testScope.runTest { val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled) - fakeShadeRepository.setQsExpansion(0.0f) + shadeTestUtil.setQsExpansion(0.0f) fakeKeyguardRepository.setKeyguardShowing(false) runCurrent() @@ -601,7 +619,7 @@ class NotificationListViewModelTest : SysuiTestCase() { testScope.runTest { val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled) - fakeShadeRepository.setQsExpansion(0.0f) + shadeTestUtil.setQsExpansion(0.0f) fakeKeyguardRepository.setKeyguardShowing(true) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/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 2cd295c6045d..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 @@ -75,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 @@ -89,7 +90,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } val aodBurnInViewModel = mock(AodBurnInViewModel::class.java) @@ -107,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 @@ -660,9 +668,6 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) - ) assertThat(maxNotifications).isEqualTo(10) @@ -691,9 +696,6 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) - ) assertThat(maxNotifications).isEqualTo(10) @@ -728,9 +730,6 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : 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) @@ -823,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/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/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt index 8ce50379fe5c..63f19fbdfed9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt @@ -249,22 +249,40 @@ class AvalancheControllerTest : SysuiTestCase() { @Test - fun testDelete_showingEntryKeyBecomesPreviousHunKey() { + fun testDelete_deleteSecondToLastEntry_showingEntryKeyBecomesPreviousHunKey() { mAvalancheController.previousHunKey = "" // Entry is showing - val showingEntry = createHeadsUpEntry(id = 0) - mAvalancheController.headsUpEntryShowing = showingEntry + val firstEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = firstEntry // There's another entry waiting to show next - val nextEntry = createHeadsUpEntry(id = 1) - mAvalancheController.addToNext(nextEntry, runnableMock!!) + val secondEntry = createHeadsUpEntry(id = 1) + mAvalancheController.addToNext(secondEntry, runnableMock!!) // Delete - mAvalancheController.delete(showingEntry, runnableMock, "testLabel") + mAvalancheController.delete(firstEntry, runnableMock, "testLabel") + + // Next entry is shown + assertThat(mAvalancheController.previousHunKey).isEqualTo(firstEntry.mEntry!!.key) + } + + @Test + fun testDelete_deleteLastEntry_previousHunKeyCleared() { + mAvalancheController.previousHunKey = "key" + + // Nothing waiting to show + mAvalancheController.clearNext() + + // One entry is showing + val showingEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = showingEntry + + // Delete + mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel") // Next entry is shown - assertThat(mAvalancheController.previousHunKey).isEqualTo(showingEntry.mEntry!!.key) + assertThat(mAvalancheController.previousHunKey).isEqualTo(""); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/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/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/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt index 6256eece65dd..ab184abdc963 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt @@ -22,14 +22,14 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope -import com.android.systemui.volume.panel.availableCriteria -import com.android.systemui.volume.panel.criteriaByKey -import com.android.systemui.volume.panel.defaultCriteria +import com.android.systemui.volume.panel.domain.availableCriteria +import com.android.systemui.volume.panel.domain.defaultCriteria import com.android.systemui.volume.panel.domain.model.ComponentModel -import com.android.systemui.volume.panel.enabledComponents +import com.android.systemui.volume.panel.domain.unavailableCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey -import com.android.systemui.volume.panel.unavailableCriteria +import com.android.systemui.volume.panel.ui.composable.enabledComponents import com.google.common.truth.Truth.assertThat +import javax.inject.Provider import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -47,7 +47,7 @@ class ComponentsInteractorImplTest : SysuiTestCase() { with(kosmos) { ComponentsInteractorImpl( enabledComponents, - defaultCriteria, + { defaultCriteria }, testScope.backgroundScope, criteriaByKey, ) @@ -66,9 +66,9 @@ class ComponentsInteractorImplTest : SysuiTestCase() { ) criteriaByKey = mapOf( - BOTTOM_BAR to availableCriteria, - COMPONENT_1 to unavailableCriteria, - COMPONENT_2 to availableCriteria, + BOTTOM_BAR to Provider { availableCriteria }, + COMPONENT_1 to Provider { unavailableCriteria }, + COMPONENT_2 to Provider { availableCriteria }, ) initUnderTest() @@ -96,8 +96,8 @@ class ComponentsInteractorImplTest : SysuiTestCase() { ) criteriaByKey = mapOf( - BOTTOM_BAR to availableCriteria, - COMPONENT_2 to availableCriteria, + BOTTOM_BAR to Provider { availableCriteria }, + COMPONENT_2 to Provider { availableCriteria }, ) defaultCriteria = unavailableCriteria initUnderTest() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt index 3dbf23e3dc58..e3dc55203286 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt @@ -20,9 +20,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.testKosmos -import com.android.systemui.volume.panel.componentByKey -import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider import com.google.common.truth.Truth import org.junit.Test import org.junit.runner.RunWith diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt index 82ce6d785e90..b37184dc941c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt @@ -20,8 +20,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.testKosmos -import com.android.systemui.volume.panel.mockVolumePanelUiComponent import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponent import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager import com.google.common.truth.Truth diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt index 4e0685556145..f6ada4c166dc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt @@ -28,15 +28,15 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.policy.fakeConfigurationController import com.android.systemui.testKosmos -import com.android.systemui.volume.panel.componentByKey -import com.android.systemui.volume.panel.componentsLayoutManager -import com.android.systemui.volume.panel.criteriaByKey -import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider +import com.android.systemui.volume.panel.domain.interactor.criteriaByKey +import com.android.systemui.volume.panel.domain.unavailableCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider +import com.android.systemui.volume.panel.ui.composable.componentByKey import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager -import com.android.systemui.volume.panel.unavailableCriteria -import com.android.systemui.volume.panel.volumePanelViewModel +import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager import com.google.common.truth.Truth.assertThat +import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -95,7 +95,7 @@ class VolumePanelViewModelTest : SysuiTestCase() { COMPONENT_2 to mockVolumePanelUiComponentProvider, BOTTOM_BAR to mockVolumePanelUiComponentProvider, ) - criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria) + criteriaByKey = mapOf(COMPONENT_2 to Provider { unavailableCriteria }) }) { testScope.runTest { val componentsLayout by collectLastValue(underTest.componentsLayout) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt index 79bf5f19997b..629c96c0ef44 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -109,6 +109,12 @@ data class ClockMessageBuffers( val largeClockMessageBuffer: MessageBuffer, ) +data class AodClockBurnInModel( + val scale: Float, + val translationX: Float, + val translationY: Float, +) + /** Specifies layout information for the */ interface ClockFaceLayout { /** All clock views to add to the root constraint layout before applying constraints. */ @@ -118,6 +124,8 @@ interface ClockFaceLayout { fun applyConstraints(constraints: ConstraintSet): ConstraintSet fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet + + fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) } /** A ClockFaceLayout that applies the default lockscreen layout to a single view */ @@ -137,6 +145,10 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout { override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet { return constraints } + + override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) { + // Default clock doesn't need detailed control of view + } } /** Events that should call when various rendering parameters change */ diff --git a/packages/SystemUI/res-keyguard/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-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml index b4b2a1935393..efc750b53cc3 100644 --- a/packages/SystemUI/res-keyguard/values-hr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml @@ -61,7 +61,7 @@ <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Lice nije prepoznato"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Pokušajte ponovno ili unesite PIN"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Pokušajte ponovno ili unesite zaporku"</string> - <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Pokušajte ponovno ili izradite uzorak"</string> + <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Pokušajte ponovno ili nacrtajte uzorak"</string> <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"PIN je obavezan nakon previše pokušaja"</string> <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"Zaporka je obavezna nakon previše pokušaja"</string> <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"Uzorak je obavezan nakon previše pokušaja"</string> diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml index c4fc5f7e0802..2bace4a702eb 100644 --- a/packages/SystemUI/res-keyguard/values-my/strings.xml +++ b/packages/SystemUI/res-keyguard/values-my/strings.xml @@ -116,7 +116,7 @@ <string name="kg_prompt_added_security_password" msgid="6053156069765029006">"ထပ်ဆောင်း လုံခြုံရေးအတွက် စကားဝှက် လိုအပ်သည်"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"စက်ပစ္စည်းကို စီမံခန့်ခွဲသူက လော့ခ်ချထားပါသည်"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"စက်ပစ္စည်းကို ကိုယ်တိုင်ကိုယ်ကျ လော့ခ်ချထားခဲ့သည်"</string> - <string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိ"</string> + <string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိပါ"</string> <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"‘မျက်နှာပြ လော့ခ်ဖွင့်ခြင်း’ သုံးရန် ‘ဆက်တင်များ’ တွင်ကင်မရာသုံးခွင့်ကိုဖွင့်ပါ"</string> <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{ဆင်းမ်ပင်နံပါတ် ထည့်သွင်းပါ။ သင့်စက်ကို လော့ခ်ဖွင့်ရန် မိုဘိုင်းဖုန်းကုမ္ပဏီသို့ မဆက်သွယ်မီ # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။}other{ဆင်းမ်ပင်နံပါတ် ထည့်သွင်းပါ။ သင့်တွင် # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။}}"</string> <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{ဆင်းမ်ကတ်သည် ယခု ပိတ်သွားပါပြီ။ ရှေ့ဆက်ရန် PUK ကုဒ်ကို ထည့်ပါ။ ဆင်းမ်ကတ် အပြီးပိတ်မသွားမီ သင့်တွင် # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။ အသေးစိတ်အတွက် မိုဘိုင်းဖုန်းကုမ္ပဏီကို ဆက်သွယ်ပါ။}other{ဆင်းမ်ကတ်သည် ယခု ပိတ်သွားပါပြီ။ ရှေ့ဆက်ရန် PUK ကုဒ်ကို ထည့်ပါ။ ဆင်းမ်ကတ် အပြီးပိတ်မသွားမီ သင့်တွင် # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။ အသေးစိတ်အတွက် မိုဘိုင်းဖုန်းကုမ္ပဏီကို ဆက်သွယ်ပါ။}}"</string> diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml index 2bfdef6ca182..224f1aeef05d 100644 --- a/packages/SystemUI/res-keyguard/values-ne/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml @@ -58,7 +58,7 @@ <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"PIN मिलेन। फेरि प्रयास गर्नुहोस्।"</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"वा फिंगरप्रिन्ट प्रयोग गरी अनलक गर्नुहोस्"</string> <string name="kg_fp_not_recognized" msgid="5183108260932029241">"फिंगरप्रिन्ट मिलेन"</string> - <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"अनुहार पहिचान गर्न सकिएन"</string> + <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"अनुहार मिलेन"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"फेरि प्रयास गर्नुहोस् वा PIN हाल्नुहोस्"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"फेरि प्रयास गर्नुहोस् वा पासवर्ड हाल्नुहोस्"</string> <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"फेरि प्रयास गर्नुहोस् वा प्याटर्न हाल्नुहोस्"</string> diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml index 9b0647afb614..4dffd971dd27 100644 --- a/packages/SystemUI/res-keyguard/values-sk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml @@ -57,7 +57,7 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"Nesprávny kód PIN"</string> <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Nesprávny kód PIN. Zopakujte."</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Alebo odomknite odtlačkom prsta"</string> - <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Nerozpoz. odtlačok prsta"</string> + <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Odtlačok prsta nebol rozpoznaný"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Tvár nebola rozpoznaná"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Skúste to znova alebo zadajte PIN"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Skúste to znova alebo zadajte heslo"</string> diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml index 60f60c4c1879..b73372f72929 100644 --- a/packages/SystemUI/res-keyguard/values-ta/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml @@ -57,7 +57,7 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"தவறான பின்"</string> <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"தவறு. மீண்டும் முயலவும்."</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"இல்லையெனில் கைரேகை மூலம் அன்லாக் செய்யவும்"</string> - <string name="kg_fp_not_recognized" msgid="5183108260932029241">"கைரேகை அடையாளம் இல்லை"</string> + <string name="kg_fp_not_recognized" msgid="5183108260932029241">"கைரேகையை அடையாளம் காண முடியவில்லை"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"முகம் கண்டறிய முடியவில்லை"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"மீண்டும் முயலவும் அல்லது பின்னை உள்ளிடவும்"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"மீண்டும் முயலவும் அல்லது கடவுச்சொல்லை உள்ளிடவும்"</string> diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml index fa2a234d4368..fcb3a3ec8edc 100644 --- a/packages/SystemUI/res-keyguard/values-ur/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml @@ -116,7 +116,7 @@ <string name="kg_prompt_added_security_password" msgid="6053156069765029006">"اضافی سیکیورٹی کیلئے پاس ورڈ درکار ہے"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"آلہ منتظم کی جانب سے مقفل ہے"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"آلہ کو دستی طور پر مقفل کیا گیا تھا"</string> - <string name="kg_face_not_recognized" msgid="7903950626744419160">"تسلیم شدہ نہیں ہے"</string> + <string name="kg_face_not_recognized" msgid="7903950626744419160">"شناخت نہیں ہو سکی"</string> <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"فیس اَنلاک استعمال کرنے کیلئے، ترتیبات میں کیمرا تک رسائی کو آن کریں"</string> <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{SIM کا PIN درج کریں۔ اس سے پہلے کہ آپ اپنا آلہ غیر مقفل کرنے کیلئے لازمی طور پر اپنے کیریئر سے رابطہ کریں آپ کے پاس # کوشش بچی ہے۔}other{SIM کا PIN درج کریں۔ آپ کے پاس # کوششیں بچی ہیں۔}}"</string> <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس # کوشش بچی ہے۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔}other{SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس # کوششیں بچی ہیں۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔}}"</string> diff --git a/packages/SystemUI/res/drawable/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/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..a30a12221105 100644 --- a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml +++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml @@ -13,12 +13,16 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > - <!-- Since this layer list has just one layer, we can remove it and replace with the inner - layer drawable. However this should only be done when the flag +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/qs_tile_ripple_color"> + <!-- We don't really use the ripple effect here, but changing it to LayerDrawable causes + performance regression, see: b/339412453. + Since this ripple has just one layer inside, we can try to remove that extra "background" + layer. However this should only be done when the flag com.android.systemui.qs_tile_focus_state has completed all its stages and this drawable fully replaces the previous one to ensure consistency with code sections searching for - specific ids in drawable hierarchy --> + specific ids in drawable hierarchy + --> <item android:id="@id/background"> <layer-list> @@ -48,4 +52,4 @@ </item> </layer-list> </item> -</layer-list>
\ No newline at end of file +</ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml index f644584f747a..5755dcd70a82 100644 --- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml @@ -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" 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..05f6faea464e 100644 --- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml @@ -229,6 +229,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_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml index d51fe5849133..fa4d9a868fd7 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml @@ -222,6 +222,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 index 33ad2cd3a30f..521369e56652 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay2.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay2.xml @@ -31,7 +31,7 @@ android:layout_width="0dp" android:elevation="4dp" android:background="@drawable/shelf_action_chip_container_background" - android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing" android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/actions_container" diff --git a/packages/SystemUI/res/layout/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 76f7f3b2c759..49d3a8ec8ad8 100644 --- a/packages/SystemUI/res/layout/screenshot_shelf.xml +++ b/packages/SystemUI/res/layout/screenshot_shelf.xml @@ -33,8 +33,7 @@ android:layout_width="wrap_content" android:elevation="4dp" android:background="@drawable/shelf_action_chip_container_background" - android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal" - android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin" + android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toTopOf="@id/guideline" > @@ -61,7 +60,7 @@ android:id="@+id/screenshot_preview_border" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing" android:layout_marginTop="@dimen/overlay_border_width_neg" android:layout_marginEnd="@dimen/overlay_border_width_neg" android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin" @@ -136,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" @@ -153,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" @@ -171,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 ac523c17df25..cc8f9c2ec305 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Langdruk om legstukke te pasmaak"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pasmaak legstukke"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-ikoon vir gedeaktiveerde legstuk"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Wysig legstuk"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwyder"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Voeg legstuk by"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Pasmaak legstukke"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Legstukke op sluitskerm"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"kies legstuk"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"verwyder legstuk"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"plaas gekose legstuk"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Voer uitsetinstellings in"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volumeglyers is uitgevou"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volumeglyers is ingevou"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"demp %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ontdemp %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> speel tans op"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Oudio sal speel op"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Bel met"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 1236c976c57d..b00dc78d4de4 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ምግብሮችን ለማበጀት በረጅሙ ይጫኑ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ምግብሮችን አብጅ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"የመተግበሪያ አዶ ለተሰናከለ ምግብር"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ምግብርን አርትዕ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"አስወግድ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ምግብር አክል"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ምግብሮችን አብጅ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ምግብሮች በማያ ገጽ ቁልፍ ላይ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ምግብር ይምረጡ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ምግብር አስወግድ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"በቦታ የተመረጠ ምግብር"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"የውጽዓት ቅንብሮችን ያስገቡ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"የድምጽ ተንሸራታቾች ተዘርግቷል"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"የድምጽ ተንሸራታቾች ተሰብስቧል"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ላይ ድምፀ-ከል አድርግ"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"የ%s ድምፀ-ከል አንሳ"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> እየተጫወተ ያለው በ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ኦዲዮ ይጫወታል በ"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"በጥሪ ላይ"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 2eed2aa5b04e..d4981549654f 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -162,7 +162,7 @@ <string name="biometric_dialog_face_icon_description_confirmed" msgid="7918067993953940778">"تمّ التأكيد."</string> <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"يمكنك النقر على \"تأكيد\" لإكمال المهمة."</string> <string name="biometric_dialog_tap_confirm_with_face" msgid="2378151312221818694">"تم فتح قفل جهازك عند تقريبه من وجهك."</string> - <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"تم فتح قفل جهازك عند تقريبه من وجهك. اضغط للمتابعة."</string> + <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"تم فتح الجهاز بالتعرّف على وجهك. اضغط للمتابعة."</string> <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"تم التعرّف على الوجه. اضغط للمتابعة."</string> <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"تم التعرّف على الوجه. للمتابعة، اضغط على رمز فتح القفل."</string> <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"مصادقة"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"اضغط مع الاستمرار لتخصيص التطبيقات المصغّرة."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"تخصيص التطبيقات المصغَّرة"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"رمز التطبيق المصغّر غير المفعّل"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"تعديل التطبيق المصغَّر"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"إزالة"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"إضافة تطبيق مصغّر"</string> @@ -629,8 +631,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"الدخول إلى إعدادات إخراج الصوت"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"تم توسيع أشرطة تمرير الصوت"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"تم تصغير أشرطة تمرير الصوت"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"كتم صوت \"%s\""</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"إعادة صوت \"%s\""</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"تشغيل <xliff:g id="LABEL">%s</xliff:g> على"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"سيتم تشغيل الصوت على"</string> <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 16cd64b5d419..daefd834d85d 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ৱিজেট কাষ্টমাইজ কৰিবলৈ দীঘলীয়াকৈ টিপক"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ৱিজেট কাষ্টমাইজ কৰক"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"অক্ষম কৰা ৱিজেটৰ বাবে এপৰ চিহ্ন"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ৱিজেট সম্পাদনা কৰক"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"আঁতৰাওক"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ৱিজেট যোগ দিয়ক"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ৱিজেট কাষ্টমাইজ কৰক"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"লক স্ক্ৰীনত ৱিজেট"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ৱিজেট বাছনি কৰক"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ৱিজেট আঁতৰাওক"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"বাছনি কৰা ৱিজেটটো ৰাখক"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"আউটপুট ছেটিং খোলক"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ভলিউমৰ শ্লাইডাৰ বিস্তাৰ কৰা আছে"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ভলিউমৰ শ্লাইডাৰ সংকোচন কৰা আছে"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s মিউট কৰক"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s আনমিউট কৰক"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"ইয়াত <xliff:g id="LABEL">%s</xliff:g> প্লে’ হৈ আছে"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"অডিঅ’ ইয়াত প্লে’ হ’ব"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"কল চলি আছে"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 69cec1c6d115..399e621f4fc7 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Basıb saxlayaraq vidcetləri fərdiləşdirin"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidcetləri fərdiləşdirin"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Deaktiv edilmiş vidcet üçün tətbiq ikonası"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Vidceti redaktə edin"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Silin"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidcet əlavə edin"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Vidcetləri fərdiləşdirin"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Kilid ekranındakı vidcetlər"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vidcet seçin"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"vidceti silin"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"seçilmiş vidceti yerləşdirin"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Çıxış ayarlarını daxil edin"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Səs slayderləri genişləndirildi"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Səs slayderləri yığcamlaşdırıldı"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s seçimini səssiz edin"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s seçimini səssiz rejimdən çıxarın"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> tətbiqində oxudulur"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio oxudulacaq"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Zəng edilir"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index c2cc34afa758..1605666006c9 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugi pritisak za prilagođavanje vidžeta"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi vidžete"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućen vidžet"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Izmeni vidžet"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj vidžet"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagodite vidžete"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Vidžeti na zaključanom ekranu"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"izaberite vidžet"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"uklonite vidžet"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavite izabrani vidžet"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Unesite podešavanja izlaznog signala"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Klizači za jačinu zvuka su prošireni"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Klizači za jačinu zvuka su skupljeni"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"isključite zvuk za: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"uključite zvuk za: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> se pušta na"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk se pušta na"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Poziv na uređaju"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Tjuner za korisnički interfejs sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Statusna traka"</string> <string name="demo_mode" msgid="263484519766901593">"Režim demonstracije za korisnički interfejs sistema"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index f956c0fc20a0..89a4701a3ba3 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -97,8 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Левая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Правая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Захавана ў праграму \"<xliff:g id="APP">%1$s</xliff:g>\" (у працоўным профілі)"</string> - <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) --> - <skip /> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Захавана ў праграму \"<xliff:g id="APP">%1$s</xliff:g>\" (у прыватным профілі)"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Праграма \"<xliff:g id="APPNAME">%1$s</xliff:g>\" выявіла гэты здымак экрана."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> і іншыя адкрытыя праграмы выявілі гэты здымак экрана."</string> @@ -448,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Доўга націскайце, каб наладзіць віджэты"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Наладзіць віджэты"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок праграмы для адключанага віджэта"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Змяніць віджэт"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Выдаліць"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Дадаць віджэт"</string> @@ -461,12 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Закрыць віджэты на экране блакіроўкі"</string> <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Наладзіць віджэты"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Віджэты на экране блакіроўкі"</string> - <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) --> - <skip /> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"выбраць віджэт"</string> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"выдаліць віджэт"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"размясціць выбраны віджэт"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string> @@ -631,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Перайсці да налад вываду"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Меню з паўзункамі гучнасці разгорнута"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Меню з паўзункамі гучнасці згорнута"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"выключыць гук (%s)"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"уключыць гук (%s)"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> прайграецца тут:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аўдыявыхад:"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ідзе выклік"</string> @@ -663,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Спадарожнікавая сувязь, дрэннае падключэнне"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спадарожнікавая сувязь, добрае падключэнне"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Спадарожнікавая сувязь, падключэнне даступнае"</string> - <!-- no translation found for satellite_connected_carrier_text (118524195198532589) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Экстраннае спадарожнікавае падключэнне"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Працоўны профіль"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Цікава для некаторых, але не для ўсіх"</string> <string name="tuner_warning" msgid="1861736288458481650">"Наладка сістэмнага інтэрфейсу карыстальніка дае вам дадатковыя спосабы наладжвання і дапасоўвання карыстальніцкага інтэрфейсу Android. Гэтыя эксперыментальныя функцыі могуць змяніцца, перастаць працаваць або знікнуць у будучых версіях. Карыстайцеся з асцярожнасцю."</string> @@ -869,8 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Дадаць плітку"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Перамясціць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Дадаць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string> - <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) --> - <skip /> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Няправільнае месца."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Пазіцыя <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Плітка дададзена"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Плітка выдалена"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index c9d0cd220b82..bdd9fd45e0b9 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Натиснете продължително за персонализ. на приспос."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Персонализиране на приспособленията"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона на приложение за деактивирано приспособление"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Редактиране на приспособлението"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Премахване"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавяне на приспособление"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Персонализиране на приспособленията"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Приспособления на заключения екран"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"избиране на приспособление"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"премахване на приспособлението"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"поставяне на избраното приспособление"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Отваряне на изходните настройки"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Плъзгачите за силата на звука са разгънати"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Плъзгачите за силата на звука са свити"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"спиране на звука на %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"включване на звука на %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Възпроизвеждане на <xliff:g id="LABEL">%s</xliff:g> на"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудиото ще се възпроизвежда на"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Активно обаждане"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index e2b4a6ddefe7..2c47b129b632 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"উইজেট কাস্টমাইজ করতে বেশিক্ষণ প্রেস করুন"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"উইজেট কাস্টমাইজ করুন"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"বন্ধ করা উইজেটের জন্য অ্যাপের আইকন"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"উইজেট এডিট করুন"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"সরান"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"উইজেট যোগ করুন"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"উইজেট কাস্টমাইজ করুন"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"লক স্ক্রিনে উইজেট"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"উইজেট বেছে নিন"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"উইজেট সরান"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"বেছে নেওয়া উইজেটটি রাখুন"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"আউটপুট সেটিংস লিখুন"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ভলিউম স্লাইডার বড় করা হয়েছে"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ভলিউম স্লাইডার আড়াল করা হয়েছে"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s মিউট করুন"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s আনমিউট করুন"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>-এ প্লে করা হচ্ছে"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"অডিও এতে প্লে করা হবে"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"কল চালু আছে"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"সিস্টেম UI টিউনার"</string> <string name="status_bar" msgid="4357390266055077437">"স্ট্যাটাস বার"</string> <string name="demo_mode" msgid="263484519766901593">"সিস্টেম UI ডেমো মোড"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 108bed8df6d1..8621b43bdafd 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -153,7 +153,7 @@ <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošalji"</string> <string name="cancel" msgid="1089011503403416730">"Otkaži"</string> <string name="biometric_dialog_logo" msgid="7681107853070774595">"Logotip aplikacije"</string> - <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Potvrdite"</string> + <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Potvrdi"</string> <string name="biometric_dialog_try_again" msgid="8575345628117768844">"Pokušaj ponovo"</string> <string name="biometric_dialog_empty_space_description" msgid="3330555462071453396">"Dodirnite da otkažete autentifikaciju"</string> <string name="biometric_dialog_face_icon_description_idle" msgid="4351777022315116816">"Pokušajte ponovo"</string> @@ -267,7 +267,7 @@ <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string> <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Nema dostupnih uparenih uređaja"</string> <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Dodirnite da povežete ili prekinete povezanost uređaja"</string> - <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Upari novi uređaj"</string> + <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Uparite novi uređaj"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Prikaži sve"</string> <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pritisnite i zadržite da prilagodite vidžete"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodite vidžete"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni vidžet"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Uredite vidžet"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Uklanjanje"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajte vidžet"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagođavanje vidžeta"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Vidžeti na zaključanom ekranu"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"odabir vidžeta"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"uklanjanje vidžeta"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavljanje odabranog vidžeta"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Unos postavki izlaza"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Klizači jačine zvuka su prošireni"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Klizači jačine zvuka su suženi"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"isključivanje parametra %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"uključivanje parametra %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Reproducira se <xliff:g id="LABEL">%s</xliff:g> na"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Reprodukcija zvuka na"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Poziv putem"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Podešavač za korisnički interfejs sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Statusna traka"</string> <string name="demo_mode" msgid="263484519766901593">"Demo način rada Sistemskog UI-ja"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 48fadbc5e8c7..6ebfd86b85ba 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén premut per personalitzar els widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalitza els widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona de l\'aplicació per a widget desactivat"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edita el widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Suprimeix"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Afegeix un widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalitza els widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets a la pantalla de bloqueig"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecciona el widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"suprimeix el widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"col·loca el widget seleccionat"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introdueix opcions de configuració de sortida"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Els controls lliscants de volum s\'han desplegat"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Els controls lliscants de volum s\'han replegat"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silencia %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"deixa de silenciar %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"S\'està reproduint <xliff:g id="LABEL">%s</xliff:g> a"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Es reproduirà a"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Trucant des de"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Personalitzador d\'interfície d\'usuari"</string> <string name="status_bar" msgid="4357390266055077437">"Barra d\'estat"</string> <string name="demo_mode" msgid="263484519766901593">"Mode de demostració de la IU del sistema"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index c90a0230aa31..7d2202da95c9 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -269,15 +269,15 @@ <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Klepnutím zařízení připojíte nebo odpojíte"</string> <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Spárovat nové zařízení"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Zobrazit vše"</string> - <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použít Bluetooth"</string> + <string name="turn_on_bluetooth" msgid="5681370462180289071">"Používat Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Připojeno"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Sdílení zvuku"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uloženo"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojit"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovat"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Zítra znovu automaticky zapnout"</string> - <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funkce jako Quick Share a Najdi moje zařízení využívají Bluetooth"</string> - <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth se zapne zítra ráno"</string> + <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Bluetooth využívají funkce jako Quick Share a Najdi moje zařízení."</string> + <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth se zapne zítra ráno."</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Sdílení zvuku"</string> <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Sdílení zvuku"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dlouhým stisknutím můžete přizpůsobit widgety"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Přizpůsobit widgety"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikace s deaktivovaným widgetem"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Upravit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstranit"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Přidat widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Přizpůsobit widgety"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgety na obrazovce uzamčení"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vybrat widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"odstranit widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"umístit vybraný widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Otevřít nastavení výstupu"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Posuvníky hlasitosti jsou rozbalené"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Posuvníky hlasitosti jsou sbalené"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"ztlumíte %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"zapnete zvuk %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Přehrávání <xliff:g id="LABEL">%s</xliff:g> přes"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk se bude přehrávat přes"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Volání na zařízení"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Nástroj na ladění uživatelského rozhraní systému"</string> <string name="status_bar" msgid="4357390266055077437">"Stavový řádek"</string> <string name="demo_mode" msgid="263484519766901593">"Ukázkový režim uživatelského rozhraní systému"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 036f855b7a8f..ec03938ad7e2 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Hold fingeren nede for at tilpasse widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpas widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktiveret widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Rediger widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tilføj widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tilpas widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets på låseskærmen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vælg widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"fjern widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"placer valgt widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Angiv indstillinger for output"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Lydstyrkeskydere er udvidet"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Lydstyrkeskydere er skjult"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"slå lyden fra for %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"slå lyden til for %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Afspiller <xliff:g id="LABEL">%s</xliff:g> på"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lyden afspilles på"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ringer på"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 42efdc22f1db..d38ad3ae6a83 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Lange drücken, um Widgets anzupassen"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets anpassen"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-Symbol für deaktiviertes Widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Widget bearbeiten"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Entfernen"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget hinzufügen"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Widgets anpassen"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets auf dem Sperrbildschirm"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"Widget auswählen"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"Widget entfernen"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ausgewähltes Widget in den Bearbeitungsmodus versetzen"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ausgabeeinstellungen angeben"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Lautstärkeregler maximiert"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Lautstärkeregler minimiert"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s stummzuschalten"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"Stummschaltung von %s aufzuheben"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Wiedergabe von <xliff:g id="LABEL">%s</xliff:g> über"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audiowiedergabe über"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Anruf auf"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Statusleiste"</string> <string name="demo_mode" msgid="263484519766901593">"Demomodus der System-UI"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 72ab8848c6ad..c6dad44b10e9 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Παρατεταμένο πάτημα για προσαρμογή γραφ. στοιχείων"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Προσαρμογή γραφικών στοιχείων"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Εικονίδιο εφαρμογής για απενεργοποιημένο γραφικό στοιχείο"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Επεξεργασία γραφικού στοιχείου"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Κατάργηση"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Προσθήκη γραφικού στοιχείου"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Προσαρμογή γραφικών στοιχείων"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Γραφικά στοιχεία στην οθόνη κλειδώματος"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"επιλογή γραφικού στοιχείου"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"κατάργηση γραφικού στοιχείου"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"τοποθέτηση επιλεγμένου γραφικού στοιχείου"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Εισαγωγή ρυθμίσεων εξόδου"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Τα ρυθμιστικά έντασης ήχου αναπτύχθηκαν"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Τα ρυθμιστικά έντασης ήχου συμπτύχθηκαν"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"σίγαση %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"κατάργηση σίγασης %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Αναπαραγωγή <xliff:g id="LABEL">%s</xliff:g> σε"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Ο ήχος θα παίξει σε"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ενεργή κλήση σε"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Γραμμή κατάστασης"</string> <string name="demo_mode" msgid="263484519766901593">"Λειτουργία επίδειξης διεπαφής χρήστη συστήματος"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index e414cd4e857a..cbb22984c351 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customise widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Status bar"</string> <string name="demo_mode" msgid="263484519766901593">"System UI demo mode"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 204f21d0c23a..fa007b67faee 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -447,6 +447,7 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customize widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customize widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <string name="icon_description_for_pending_widget" msgid="8413816401868001755">"App icon for a widget being installed"</string> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -627,8 +628,10 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <string name="volume_panel_hint_mute" msgid="2153922288568199077">"Mute %s"</string> + <string name="volume_panel_hint_unmute" msgid="4831850937582282340">"Unmute %s"</string> + <string name="volume_panel_hint_muted" msgid="1124844870181285320">"muted"</string> + <string name="volume_panel_hint_vibrate" msgid="4136223145435914132">"vibrate"</string> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index e414cd4e857a..cbb22984c351 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customise widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Status bar"</string> <string name="demo_mode" msgid="263484519766901593">"System UI demo mode"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index e414cd4e857a..cbb22984c351 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customise widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Status bar"</string> <string name="demo_mode" msgid="263484519766901593">"System UI demo mode"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index ee28a624b462..248360bac608 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -447,6 +447,7 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customize widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customize widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <string name="icon_description_for_pending_widget" msgid="8413816401868001755">"App icon for a widget being installed"</string> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -627,8 +628,10 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <string name="volume_panel_hint_mute" msgid="2153922288568199077">"Mute %s"</string> + <string name="volume_panel_hint_unmute" msgid="4831850937582282340">"Unmute %s"</string> + <string name="volume_panel_hint_muted" msgid="1124844870181285320">"muted"</string> + <string name="volume_panel_hint_vibrate" msgid="4136223145435914132">"vibrate"</string> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 42424efdbeb6..28230f1d573d 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén presionado para personalizar los widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícono de la app de widget inhabilitado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modificar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Agregar widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets en la pantalla de bloqueo"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"Seleccionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"quitar widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"colocar widget seleccionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string> @@ -619,7 +619,7 @@ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Audio espacial"</string> <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Desactivar"</string> <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fijar"</string> - <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Seg. de cabeza"</string> + <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Monitoreo de cabeza"</string> <string name="volume_ringer_change" msgid="3574969197796055532">"Presiona para cambiar el modo de timbre"</string> <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string> <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"dejar de silenciar"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ingresar configuración de salida"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes del volumen expandidos"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes del volumen colapsados"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silenciar %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"activar sonido %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Reproduciendo <xliff:g id="LABEL">%s</xliff:g> en"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Se reproducirá en"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Llamando en"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador de IU del sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string> <string name="demo_mode" msgid="263484519766901593">"Modo demostración de la IU del sistema"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 77989e4ac7d3..fd2ac8582ae4 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén pulsado para personalizar los widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icono de la aplicación de widget inhabilitado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Añadir widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets en la pantalla de bloqueo"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"seleccionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"eliminar widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"colocar widget seleccionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introducir los ajustes de salida"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes de volumen desplegados"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes de volumen contraídos"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silenciar %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"dejar de silenciar %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Reproduciendo <xliff:g id="LABEL">%s</xliff:g> en"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Se reproducirá en"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Llamando desde"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Configurador de UI del sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string> <string name="demo_mode" msgid="263484519766901593">"Modo Demo de UI del sistema"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index a3663a0bd958..7f97176012d7 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vajutage pikalt vidinate kohandamiseks"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Kohanda vidinaid"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Keelatud vidina rakenduseikoon"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Muuda vidinat"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Eemalda"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisa vidin"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Kohanda vidinaid"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Lukustuskuva vidinad"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vidina valimine"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"eemaldage vidin"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"asetage valitud vidin"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Sisestage väljundseaded"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Helitugevuse liugurid laiendatud"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Helitugevuse liugurid ahendatud"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"vaigistab %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"tühistab %s vaigistuse"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Esitamine jätkub seadmes <xliff:g id="LABEL">%s</xliff:g>"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Heli esitatakse seadmes"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Helistamine seadmes"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Süsteemi kasutajaliidese tuuner"</string> <string name="status_bar" msgid="4357390266055077437">"Olekuriba"</string> <string name="demo_mode" msgid="263484519766901593">"Süsteemi kasutajaliidese demorežiim"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 23de72ea0527..685b5921bcd0 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widgetak pertsonalizatzeko, sakatu luze"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pertsonalizatu widgetak"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Desgaitutako widgetaren aplikazio-ikonoa"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editatu widgeta"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Kendu"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Gehitu widget bat"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Pertsonalizatu widgetak"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Pantaila blokeatuko widgetak"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"hautatu widget bat"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"kendu widgeta"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"kokatu hautatutako widgeta"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ireki emaitzaren ezarpenak"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Bolumenaren botoi lerrakorrak zabalduta daude"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Bolumenaren botoi lerrakorrak tolestuta daude"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desaktibatu honen audioa: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"aktibatu honen audioa: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> hemen erreproduzitzen:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audioak abian jarraituko du hemen:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Honen bidez deitzen"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sistemaren erabiltzaile-interfazearen konfiguratzailea"</string> <string name="status_bar" msgid="4357390266055077437">"Egoera-barra"</string> <string name="demo_mode" msgid="263484519766901593">"Sistemaren erabiltzaile-interfazearen demo modua"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 9feeaeeec31a..d21fdd330c11 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"برای سفارشیسازی ابزارکها، فشار طولانی دهید"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"سفارشیسازی ابزارکها"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"نماد برنامه برای ابزارک غیرفعال"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ویرایش ابزارک"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"برداشتن"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"افزودن ابزارک"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"سفارشیسازی ابزارکها"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ابزارکها در صفحه قفل"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"انتخاب ابزارک"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"برداشتن ابزارک"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"جایگذاری ابزارک انتخابشده"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایینپر"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامهها و دادههای این جلسه حذف خواهد شد."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"تنظیمات خروجی را وارد کنید"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"لغزندههای صدا ازهم باز شدند"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"لغزندههای صدا جمع شدند"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"بیصدا کردن %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"باصدا کردن %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"درحال پخش <xliff:g id="LABEL">%s</xliff:g> در"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"صدا در این دستگاه پخش میشود:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"تماس برقرار است"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"تنظیمکننده واسط کاربری سیستم"</string> <string name="status_bar" msgid="4357390266055077437">"نوار وضعیت"</string> <string name="demo_mode" msgid="263484519766901593">"حالت نمایشی واسط کاربری سیستم"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 68319fd8b4b6..8910bf320593 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Yksilöi widgetit pitkällä painalluksella"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Muokkaa widgettejä"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Käytöstä poistetun widgetin sovelluskuvake"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Muokkaa widgetiä"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Poista"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisää widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Muokkaa widgetejä"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgetit lukitusnäytöllä"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"valitse widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"poista widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"aseta valittu widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Lisää tuloasetukset"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Äänenvoimakkuuden liukusäätimet laajennettu"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Äänenvoimakkuuden liukusäätimet tiivistetty"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mykistä: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"poista mykistys: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Toistetaan: <xliff:g id="LABEL">%s</xliff:g>"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audiota toistetaan laitteella"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Puhelu kesken:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Tilapalkki"</string> <string name="demo_mode" msgid="263484519766901593">"Käyttöliittymän esittelytila"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index fd6ec7a371b8..df5f59f80dd1 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Maintenez le doigt pour personnaliser les widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'application pour un widget désactivé"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Retirer"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personnaliser les widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets sur l\'écran de verrouillage"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"sélectionner le widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"retirer le widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"placer le widget sélectionné"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Entrer les paramètres de sortie"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Curseurs de volume développés"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Curseurs de volume réduits"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"Désactivez le son de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"Réactivez le son de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Lecture de <xliff:g id="LABEL">%s</xliff:g> sur"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lecture audio sur"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Appel en cours"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Barre d\'état"</string> <string name="demo_mode" msgid="263484519766901593">"Mode Démo de l\'interface système"</string> @@ -1012,10 +1017,10 @@ <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Touchez pour afficher le bouton d\'accessibilité"</string> <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Le raccourci <xliff:g id="FEATURE_NAME">%s</xliff:g> a été retiré"</string> <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# raccourci retiré}one{# raccourci retiré}many{# de raccourcis retirés}other{# raccourcis retirés}}"</string> - <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer dans coin sup. gauche"</string> - <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer dans coin sup. droit"</string> - <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer dans coin inf. gauche"</string> - <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer dans coin inf. droit"</string> + <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer en haut à gauche"</string> + <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer en haut à droite"</string> + <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer en bas à gauche"</string> + <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer en bas à droite"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Rapprocher du bord et masquer"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Éloigner du bord et afficher"</string> <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Retirer"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 47f469ff4605..b02d547e08a8 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Appuyez de manière prolongée pour personnaliser les widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'appli du widget désactivé"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Supprimer"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personnaliser les widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets sur l\'écran de verrouillage"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"sélectionner un widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"supprimer le widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"positionner le widget sélectionné"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Accéder aux paramètres de sortie"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Curseurs de volume développés"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Curseurs de volume réduits"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"couper le son de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"réactiver le son de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Diffusion de <xliff:g id="LABEL">%s</xliff:g> sur"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lecture audio sur"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Appel défini sur"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index e7037b39fe33..8e2a0115b448 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pulsación longa para personalizar os widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona da aplicación de widget desactivado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engadir widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar os widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets na pantalla de bloqueo"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"seleccionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"quitar o widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"colocar o widget seleccionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introducir a configuración de saída"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controis desprazables de volume despregados"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controis desprazables de volume contraídos"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silenciar %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"activar o son de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Reproducindo <xliff:g id="LABEL">%s</xliff:g> en"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Reproducirase en"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chamada en curso"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Configurador da IU do sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string> <string name="demo_mode" msgid="263484519766901593">"Modo de demostración da IU do sistema"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index b3c8e1eb5a88..c0f6be96115c 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"વિજેટ કસ્ટમાઇઝ કરવા માટે થોડીવાર દબાવી રાખો"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"વિજેટ કસ્ટમાઇઝ કરો"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"બંધ કરેલા વિજેટ માટેની ઍપનું આઇકન"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"વિજેટમાં ફેરફાર કરો"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"કાઢી નાખો"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"વિજેટ ઉમેરો"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"વિજેટ કસ્ટમાઇઝ કરો"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"લૉક સ્ક્રીન પર વિજેટ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"વિજેટ પસંદ કરો"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"વિજેટ કાઢી નાખો"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"પસંદ કરેલું વિજેટ મૂકો"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"આઉટપુટના સેટિંગ દાખલ કરો"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"વૉલ્યૂમના સ્લાઇડર મોટા કરવામાં આવ્યા"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"વૉલ્યૂમના સ્લાઇડર નાના કરવામાં આવ્યા"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sને મ્યૂટ કરો"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%sને અનમ્યૂટ કરો"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> વગાડી રહ્યાં છીએ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ઑડિયો આની પર વાગશે"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"કૉલ ચાલુ છે"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 28825845bb08..4cf74b5f0a08 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट पसंद के मुताबिक बनाने के लिए उसे दबाकर रखें"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट अपनी पसंद के मुताबिक बनाएं"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद किए गए विजेट के लिए ऐप्लिकेशन आइकॉन"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"विजेट में बदलाव करें"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाएं"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोड़ें"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"विजेट अपनी पसंद के मुताबिक बनाएं"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"लॉक स्क्रीन पर विजेट"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"विजेट चुनें"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"विजेट हटाएं"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"चुने गए विजेट के लिए जगह चुनें"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string> @@ -562,7 +562,7 @@ <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"इस डिवाइस का प्रबंधन आपके अभिभावक करते हैं. अभिभावक आपके डिवाइस से जुड़ी जानकारी देख सकते हैं. साथ ही, इसे प्रबंधित कर सकते हैं. इनमें आपके इस्तेमाल किए गए ऐप्लिकेशन, जगह की जानकारी, और डिवाइस के इस्तेमाल में बिताए गए समय जैसी जानकारी शामिल है."</string> <string name="legacy_vpn_name" msgid="4174223520162559145">"वीपीएन"</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"TrustAgent की वजह से अनलॉक रखा गया है"</string> - <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"कई बार पुष्टि करने की कोशिश की वजह से, डिवाइस लॉक है"</string> + <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"कई बार पुष्टि करने की कोशिशों की वजह से, डिवाइस लॉक हो गया है"</string> <string name="keyguard_indication_after_adaptive_auth_lock" msgid="2323400645470712787">"डिवाइस लॉक हो गया है\nपुष्टि नहीं की जा सकी"</string> <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string> <string name="accessibility_volume_settings" msgid="1458961116951564784">"साउंड सेटिंग"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"आउटपुट की सेटिंग डालें"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"आवाज़ के स्लाइडर को बड़ा किया गया"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"आवाज़ के स्लाइडर को छोटा किया गया"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s को म्यूट करें"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s को अनम्यूट करें"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> इस पर चल रहा है"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ऑडियो इस पर चलेगा"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"कॉल चालू है"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टम यूज़र इंटरफ़ेस (यूआई) ट्यूनर"</string> <string name="status_bar" msgid="4357390266055077437">"स्टेटस बार"</string> <string name="demo_mode" msgid="263484519766901593">"सिस्टम यूज़र इंटरफ़ेस (यूआई) डेमो मोड"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 560938bb882b..b211c9437539 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -369,8 +369,8 @@ <string name="quick_settings_contrast_medium" msgid="5158352575583902566">"Srednji"</string> <string name="quick_settings_contrast_high" msgid="656049259587494499">"Visoki"</string> <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Slušna pomagala"</string> - <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Slušni uređaji"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Uparivanje novog uređaja"</string> + <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Slušna pomagala"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Uparite novi uređaj"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknite da biste uparili novi uređaj"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Ažuriranje unaprijed definiranih postavki nije uspjelo"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Želite li deblokirati mikrofon uređaja?"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugo pritisnite za prilagodbu widgeta"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi widgete"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Uredi widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagodi widgete"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgeti na zaključanom zaslonu"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"odaberi widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ukloni widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavi odabrani widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Unesite postavke izlaza"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Proširivanje klizača za glasnoću"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Sažimanje klizača za glasnoću"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"isključili zvuk za sljedeće: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"uključili zvuk za sljedeće: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> se reproducira na"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk će se reproducirati na"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Pozivanje na uređaju"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Ugađanje korisničkog sučelja sustava"</string> <string name="status_bar" msgid="4357390266055077437">"Traka statusa"</string> <string name="demo_mode" msgid="263484519766901593">"Demo način korisničkog sučelja sustava"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 23166431bb28..e736537cc125 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nyomja meg hosszan a modulok személyre szabásához"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Modulok személyre szabása"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Letiltott modul alkalmazásikonja"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modul szerkesztése"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Eltávolítás"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Modul hozzáadása"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Modulok személyre szabása"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Modulok a lezárási képernyőn"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"modul kiválasztása"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"modul törlése"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"kijelölt modul áthelyezése"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Kimenet beállításainak megadása"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Hangerő-szabályozók kibontva"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Hangerő-szabályozók összecsukva"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s némítása"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s némításának feloldása"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> lejátszása itt:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Hang lejátszása itt:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Hívás folyamatban itt:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Kezelőfelület-hangoló"</string> <string name="status_bar" msgid="4357390266055077437">"Állapotsor"</string> <string name="demo_mode" msgid="263484519766901593">"A rendszer kezelőfelületének demómódja"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 3d70b70556ab..6a905c598e00 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Երկար սեղմեք՝ վիջեթները հարմարեցնելու համար"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Հարմարեցնել վիջեթները"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Հավելվածի պատկերակ անջատված վիջեթի համար"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Փոփոխել վիջեթը"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Հեռացնել"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ավելացնել վիջեթ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Հարմարեցնել վիջեթները"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Վիջեթներ կողպէկրանին"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ընտրել վիջեթ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"հեռացնել վիջեթը"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"տեղադրել ընտրված վիջեթը"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Բացել նվագարկման կարգավորումները"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Ձայնի ուժգնության սահիչները ծավալված են"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Ձայնի ուժգնության սահիչները ծալված են"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"անջատել ձայնը (%s)"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"միացնել ձայնը (%s)"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>. նվագարկվում է"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Աուդիոն կնվագարկի"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Զանգն ընթացքում է"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Համակարգի ՕՄ-ի կարգավորիչ"</string> <string name="status_bar" msgid="4357390266055077437">"Կարգավիճակի գոտի"</string> <string name="demo_mode" msgid="263484519766901593">"Համակարգի միջերեսի ցուցադրական ռեժիմ"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index bc7bb0fcd6a4..23d83974493c 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon aplikasi untuk widget yang dinonaktifkan"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Hapus"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string> @@ -629,12 +631,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Masukkan setelan perangkat output"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Penggeser volume diluaskan"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Penggeser volume diciutkan"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"membisukan %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"membunyikan %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Memutar <xliff:g id="LABEL">%s</xliff:g> di"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio akan diputar di"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Menelepon di"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Penyetel Antarmuka Pengguna Sistem"</string> <string name="status_bar" msgid="4357390266055077437">"Bilah status"</string> <string name="demo_mode" msgid="263484519766901593">"Mode demo UI sistem"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index b30c89845138..2c77eb1f0345 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Haltu inni til að sérsníða græjur"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sérsníða græjur"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Forritstákn fyrir græju sem slökkt er á"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Breyta græju"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjarlægja"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Bæta græju við"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Sérsníða græjur"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Græjur á lásskjá"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"velja græju"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"fjarlægja græju"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"koma valinni græju fyrir"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Færa inn stillingar úttaks"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Stækkaðir hljóðstyrkssleðar"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Minnkaðir hljóðstyrkssleðar"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"þagga %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"kveikja á hljóði %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Í spilun í <xliff:g id="LABEL">%s</xliff:g>"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Hljóð heldur áfram að spilast"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Símtal í gangi"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Fínstillingar kerfisviðmóts"</string> <string name="status_bar" msgid="4357390266055077437">"Stöðustika"</string> <string name="demo_mode" msgid="263484519766901593">"Prufustilling kerfisviðmóts"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 210209958ae9..056c2867388a 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Premi a lungo per personalizzare i widget"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizza widget"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona dell\'app per widget disattivati"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modifica widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Rimuovi"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Aggiungi widget"</string> @@ -629,12 +631,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Inserisci impostazioni di uscita"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Cursori volume espansi"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Cursori volume compressi"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"disattivare audio di %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"riattivare audio di %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> in riproduzione su"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio riprodotto su:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chiamata in corso"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Ottimizzatore UI di sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Barra di stato"</string> <string name="demo_mode" msgid="263484519766901593">"Modalità demo dell\'interfaccia utente di sistema"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 5be4172db9d1..6670b097ac53 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -269,7 +269,7 @@ <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"אפשר להקיש כדי להתחבר למכשיר או להתנתק ממנו"</string> <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"התאמה של מכשיר חדש"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"הצגת הכול"</string> - <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth"</string> + <string name="turn_on_bluetooth" msgid="5681370462180289071">"שימוש ב-Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"מחובר"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"שיתוף אודיו"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"נשמר"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"לוחצים לחיצה ארוכה כדי להתאים אישית את הווידג\'טים"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"התאמה אישית של ווידג\'טים"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"סמל האפליקציה לווידג\'ט שהושבת"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"עריכת הווידג\'ט"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"הסרה"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"הוספת ווידג\'ט"</string> @@ -629,12 +631,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"הזנה של הגדרות הפלט"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"פסי ההזזה של עוצמת הקול במצב מורחב"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"פסי ההזזה של עוצמת הקול במצב מכווץ"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"השתקה של %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ביטול ההשתקה של %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"הפעלה של <xliff:g id="LABEL">%s</xliff:g> במכשיר"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"האודיו יופעל במכשיר"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"מתבצעת שיחה במכשיר"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"שורת סטטוס"</string> <string name="demo_mode" msgid="263484519766901593">"מצב הדגמה בממשק המשתמש של המערכת"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 62a3a4324658..e3c0dbcf9992 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -272,7 +272,7 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth を使用"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"接続しました"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音声の共有"</string> - <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存しました"</string> + <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存済み"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"接続を解除"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"有効化"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明日自動的に ON に戻す"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長押ししてウィジェットをカスタマイズ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ウィジェットのカスタマイズ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"無効なウィジェットのアプリアイコン"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ウィジェットを編集"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"削除"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ウィジェットを追加"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ウィジェットのカスタマイズ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ロック画面のウィジェット"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ウィジェットを選択"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ウィジェットを削除"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"選択したウィジェットを配置"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"出力の設定を入力してください"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"音量スライダーを開きました"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"音量スライダーを閉じました"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s をミュート"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s のミュートを解除"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> の再生先:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"音声の再生先"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"通話中"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"システムUI調整ツール"</string> <string name="status_bar" msgid="4357390266055077437">"ステータスバー"</string> <string name="demo_mode" msgid="263484519766901593">"システム UI デモモード"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 2a4f5d68c1f7..0251ac0e9d7c 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ხანგრძლივად დააჭირეთ ვიჯეტების მოსარგებად"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ვიჯეტების მორგება"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"აპის ხატულა გათიშული ვიჯეტისთვის"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ვიჯეტის რედაქტირება"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ამოშლა"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ვიჯეტის დამატება"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ვიჯეტების მორგება"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ვიჯეტები ჩაკეტილ ეკრანზე"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ვიჯეტის არჩევა"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ვიჯეტის ამოშლა"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"არჩეული ვიჯეტის განთავსება"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"აუდიოს გამოსვლის პარამეტრების გახსნა"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ხმის სლაიდერების გაფართოება"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ხმის სლაიდერების ჩაკეცვა"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s-ის დადუმება"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s-ის დადუმების მოხსნა"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"უკრავს <xliff:g id="LABEL">%s</xliff:g>:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"აუდიო დაიკვრება"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"მიმდინარეობს ზარი"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 398677ee6396..9c96a4eb6b60 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерді бейімдеу үшін ұзақ басып тұрыңыз."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерді реттеу"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өшірілген виджеттің қолданба белгішесі"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Виджетті өзгерту"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Өшіру"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет қосу"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Виджеттерді бейімдеу"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Құлыптаулы экрандағы виджеттер"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виджет таңдау"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"виджетті өшіру"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"таңдалған виджетті орналастыру"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Шығыс параметрлерін енгізу"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Дыбыс деңгейінің жүгірткі реттегіштері жайылды."</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Дыбыс деңгейінің жүгірткі реттегіштері жиылды."</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s дыбысын өшіру"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s дыбысын қосу"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ойнатылатын құрылғы:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудио ойнатылатын құрылғы:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Қоңырау шалып жатыр"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Жүйелік пайдаланушылық интерфейс тюнері"</string> <string name="status_bar" msgid="4357390266055077437">"Күйін көрсету жолағы"</string> <string name="demo_mode" msgid="263484519766901593">"Жүйе интерфейсінің демо режимі"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index e32da00ef6a9..dc9a747a70b4 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ចុចឱ្យយូរ ដើម្បីប្ដូរធាតុក្រាហ្វិកតាមបំណង"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ប្ដូរធាតុក្រាហ្វិកតាមបំណង"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"រូបកម្មវិធីសម្រាប់ធាតុក្រាហ្វិកដែលបានបិទ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"កែធាតុក្រាហ្វិក"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ដកចេញ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"បញ្ចូលធាតុក្រាហ្វិក"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ប្ដូរធាតុក្រាហ្វិកតាមបំណង"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ធាតុក្រាហ្វិកនៅលើអេក្រង់ចាក់សោ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ជ្រើសរើសធាតុក្រាហ្វិក"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ដកធាតុក្រាហ្វិកចេញ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ដាក់ធាតុក្រាហ្វិកដែលបានជ្រើសរើស"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរអ្នកប្រើ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយទាញចុះ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យទាំងអស់ក្នុងវគ្គនេះនឹងត្រូវលុប។"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ចូលការកំណត់ឧបករណ៍មេឌៀ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"បានពង្រីកគ្រាប់រំកិលកម្រិតសំឡេង"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"បានបង្រួមគ្រាប់រំកិលកម្រិតសំឡេង"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"បិទសំឡេង %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"បើកសំឡេង %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"កំពុងចាក់ <xliff:g id="LABEL">%s</xliff:g> នៅលើ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"សំឡេងនឹងលេងនៅលើ"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"កំពុងនិយាយទូរសព្ទ"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"កម្មវិធីសម្រួល UI ប្រព័ន្ធ"</string> <string name="status_bar" msgid="4357390266055077437">"របារស្ថានភាព"</string> <string name="demo_mode" msgid="263484519766901593">"មុខងារសាកល្បង UI ប្រព័ន្ធ"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index e217f8e20470..46a679cff801 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -368,7 +368,7 @@ <string name="quick_settings_contrast_standard" msgid="2538227821968061832">"ಪ್ರಮಾಣಿತ"</string> <string name="quick_settings_contrast_medium" msgid="5158352575583902566">"ಮಧ್ಯಮ"</string> <string name="quick_settings_contrast_high" msgid="656049259587494499">"ಹೆಚ್ಚು"</string> - <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ಹಿಯರಿಂಗ್ ಸಾಧನಗಳು"</string> + <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ಶ್ರವಣ ಸಾಧನಗಳು"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"ಹಿಯರಿಂಗ್ ಸಾಧನಗಳು"</string> <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ಹೊಸ ಸಾಧನವನ್ನು ಪೇರ್ ಮಾಡಿ"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ಹೊಸ ಸಾಧನವನ್ನು ಜೋಡಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ವಿಜೆಟ್ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ದೀರ್ಘಕಾಲ ಒತ್ತಿರಿ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ವಿಜೆಟ್ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾದ ವಿಜೆಟ್ಗಾಗಿ ಆ್ಯಪ್ ಐಕಾನ್"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ವಿಜೆಟ್ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ತೆಗೆದುಹಾಕಿ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ವಿಜೆಟ್ ಅನ್ನು ಸೇರಿಸಿ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ವಿಜೆಟ್ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ಲಾಕ್ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ವಿಜೆಟ್ಗಳು"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ವಿಜೆಟ್ ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ವಿಜೆಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಿ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ಆಯ್ಕೆಮಾಡಿದ ವಿಜೆಟ್ ಅನ್ನು ಇರಿಸಿ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್ಡೌನ್ ಮೆನು"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಶನ್ನಲ್ಲಿನ ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string> @@ -594,9 +594,9 @@ <string name="screen_pinning_negative" msgid="6882816864569211666">"ಬೇಡ"</string> <string name="screen_pinning_start" msgid="7483998671383371313">"ಆ್ಯಪ್ ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string> <string name="screen_pinning_exit" msgid="4553787518387346893">"ಆ್ಯಪ್ ಅನ್ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string> - <string name="stream_voice_call" msgid="7468348170702375660">"ಕರೆಮಾಡಿ"</string> + <string name="stream_voice_call" msgid="7468348170702375660">"ಕರೆ ಮಾಡಿ"</string> <string name="stream_system" msgid="7663148785370565134">"ಸಿಸ್ಟಂ"</string> - <string name="stream_ring" msgid="7550670036738697526">"ರಿಂಗ್"</string> + <string name="stream_ring" msgid="7550670036738697526">"ರಿಂಗ್ ಮಾಡಿ"</string> <string name="stream_music" msgid="2188224742361847580">"ಮಾಧ್ಯಮ"</string> <string name="stream_alarm" msgid="16058075093011694">"ಅಲಾರಮ್"</string> <string name="stream_notification" msgid="7930294049046243939">"ನೋಟಿಫಿಕೇಶನ್"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ಔಟ್ಪುಟ್ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಪ್ರವೇಶಿಸಿ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ವಾಲ್ಯೂಮ್ ಸ್ಲೈಡರ್ಗಳನ್ನು ವಿಸ್ತೃತಗೊಳಿಸಲಾಗಿದೆ"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ವಾಲ್ಯೂಮ್ ಸ್ಲೈಡರ್ಗಳನ್ನು ಕುಗ್ಗಿಸಲಾಗಿದೆ"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ಮ್ಯೂಟ್ ಮಾಡಿ"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ಅನ್ಮ್ಯೂಟ್ ಮಾಡಿ"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಆಗು..."</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ಇದರಲ್ಲಿ ಪ್ಲೇ ಆಗುತ್ತದೆ"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ಕರೆ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 3ef3dc862bfb..54dcfce6d5b8 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"위젯을 맞춤설정하려면 길게 누르기"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"위젯 맞춤설정"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"사용 중지된 위젯의 앱 아이콘"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"위젯 수정"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"삭제"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"위젯 추가"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"위젯 맞춤설정"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"잠금 화면의 위젯"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"위젯 선택"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"위젯 삭제"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"선택한 위젯 배치"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"출력 설정 열기"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"볼륨 슬라이더 펼침"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"볼륨 슬라이더 접힘"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s 음소거"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s 음소거 해제"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> 재생 위치:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"오디오 재생 위치:"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"전화 거는 중"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index a75d0d5fe2e7..a224ac78d599 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерди ыңгайлаштыруу үчүн кое бербей басып туруңуз"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерди ыңгайлаштыруу"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өчүрүлгөн виджет үчүн колдонмонун сүрөтчөсү"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Виджетти түзөтүү"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Өчүрүү"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет кошуу"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Виджеттерди ыңгайлаштыруу"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Кулпуланган экрандагы виджеттер"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виджет тандоо"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"виджетти алып салуу"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"тандалган виджетти жайгаштыруу"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Чыгаруу параметрлерин киргизүү"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Үндүн катуулугунун сыдырмалары жайып көрсөтүлдү"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Үндүн катуулугунун сыдырмалары жыйыштырылды"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s үнүн басуу"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s үнүн чыгаруу"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> аркылуу ойнотулууда"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудио кайсы жерде ойнотулат:"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Чалууда"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index eca041e8acf1..88866e37d07a 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ກົດຄ້າງໄວ້ເພື່ອປັບແຕ່ງວິດເຈັດ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ປັບແຕ່ງວິດເຈັດ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ໄອຄອນແອັບສຳລັບວິດເຈັດທີ່ຖືກປິດການນຳໃຊ້"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ແກ້ໄຂວິດເຈັດ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ລຶບອອກ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ເພີ່ມວິດເຈັດ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ປັບແຕ່ງວິດເຈັດ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ວິດເຈັດຢູ່ໜ້າຈໍລັອກ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ເລືອກວິດເຈັດ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ລຶບວິດເຈັດອອກ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ວາງວິດເຈັດທີ່ເລືອກ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯແລະຂໍ້ມູນທັງໝົດໃນເຊດຊັນນີ້ຈະຖືກລຶບອອກ."</string> @@ -629,10 +629,16 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ໃສ່ການຄັ້ງຄ່າເອົ້າພຸດ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ຂະຫຍາຍສະໄລເດີລະດັບສຽງແລ້ວ"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ຫຍໍ້ສະໄລເດີລະດັບສຽງລົງແລ້ວ"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"ປິດສຽງ %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ເຊົາປິດສຽງ %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"ກຳລັງຫຼິ້ນ <xliff:g id="LABEL">%s</xliff:g> ໃນ"</string> - <string name="media_output_title_without_playing" msgid="3825663683169305013">"ສຽງຈະຫຼິ້ນຕໍ່ໄປ"</string> + <string name="media_output_title_without_playing" msgid="3825663683169305013">"ສຽງຈະຫຼິ້ນຢູ່"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ກຳລັງໂທ"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"ແຖບສະຖານະ"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index fa5790ce8e26..46ac3044f765 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Ilgai paspauskite, kad tinkintumėte valdiklius"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tinkinti valdiklius"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Išjungto valdiklio programos piktograma"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Redaguoti valdiklį"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Pašalinti"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridėti valdiklį"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tinkinti valdiklius"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Valdikliai užrakinimo ekrane"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pasirinkite valdiklį"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"pašalinti valdiklį"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"padėti pasirinktą valdiklį"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Įveskite išvesties nustatymus"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Garsumo šliaužikliai išskleisti"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Garsumo šliaužikliai sutraukti"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"nutildyti %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"įjungti garsą %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Leidžiama „<xliff:g id="LABEL">%s</xliff:g>“"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Garsas bus leidžiamas"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Skambinama"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 9e01526dcb52..979f59e3374f 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -370,7 +370,7 @@ <string name="quick_settings_contrast_high" msgid="656049259587494499">"Augsts"</string> <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Dzirdes aparāti"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Dzirdes aparāti"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Savienojiet pārī jaunu ierīci"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Savienot pārī jaunu ierīci"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Noklikšķiniet, lai savienotu pārī jaunu ierīci"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Nevarēja atjaunināt pirmsiestatījumu"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vai atbloķēt ierīces mikrofonu?"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nospiediet un turiet, lai pielāgotu logrīkus."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pielāgot logrīkus"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Lietotnes ikona atspējotam logrīkam"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Rediģēt logrīku"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Noņemt"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pievienot logrīku"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Pielāgot logrīkus"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Logrīki bloķēšanas ekrānā"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"atlasīt logrīku"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"noņemt logrīku"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"novietot atlasīto logrīku"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Atvērt izvades iestatījumus"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Skaļuma slīdņi izvērsti"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Skaļuma slīdņi sakļauti"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"izslēgt skaņu straumei %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ieslēgt skaņu straumei %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> — atskaņošana šeit:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio tiks atskaņots šeit:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Aktīvs zvans ierīcē"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sistēmas saskarnes regulators"</string> <string name="status_bar" msgid="4357390266055077437">"Statusa josla"</string> <string name="demo_mode" msgid="263484519766901593">"Sistēmas lietotāja saskarnes demonstrācijas režīms"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index d1e26e5d8565..e305c9e5fee1 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Притиснете долго за да ги приспособите виџетите"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Приспособете ги виџетите"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона за апликација за оневозможен виџет"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Изменување виџети"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Отстранува"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додајте виџет"</string> @@ -596,14 +598,14 @@ <string name="screen_pinning_exit" msgid="4553787518387346893">"Апликацијата е откачена"</string> <string name="stream_voice_call" msgid="7468348170702375660">"Повик"</string> <string name="stream_system" msgid="7663148785370565134">"Систем"</string> - <string name="stream_ring" msgid="7550670036738697526">"Ѕвони"</string> + <string name="stream_ring" msgid="7550670036738697526">"Ѕвонење"</string> <string name="stream_music" msgid="2188224742361847580">"Аудиовизуелни содржини"</string> <string name="stream_alarm" msgid="16058075093011694">"Аларм"</string> <string name="stream_notification" msgid="7930294049046243939">"Известување"</string> <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth"</string> <string name="stream_dtmf" msgid="7322536356554673067">"Двојна повеќетонска фреквенција"</string> <string name="stream_accessibility" msgid="3873610336741987152">"Пристапност"</string> - <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ѕвони"</string> + <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ѕвонење"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибрации"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Исклучи звук"</string> <string name="media_device_cast" msgid="4786241789687569892">"Емитување"</string> @@ -629,12 +631,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Внесете ги поставките за излез"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Лизгачите за јачина на звукот се проширени"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Лизгачите за јачина на звукот се собрани"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"исклучување звук на %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"вклучување звук на %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>: пуштено на"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудиото ќе се пушти на"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Повик во тек"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Адаптер на УИ на системот"</string> <string name="status_bar" msgid="4357390266055077437">"Статусна лента"</string> <string name="demo_mode" msgid="263484519766901593">"Демо-режим на кориснички интерфејс на систем"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 79be0f17fb5a..c14cc418d5a8 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കാൻ ദീർഘനേരം അമർത്തുക"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കുക"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"പ്രവർത്തനരഹിതമാക്കിയ വിജറ്റിനുള്ള ആപ്പ് ഐക്കൺ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"വിജറ്റ് എഡിറ്റ് ചെയ്യുക"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"നീക്കം ചെയ്യുക"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"വിജറ്റ് ചേർക്കുക"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കുക"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ലോക്ക് സ്ക്രീനിൽ വിജറ്റുകൾ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"വിജറ്റ് തിരഞ്ഞെടുക്കുക"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"വിജറ്റ് നീക്കം ചെയ്യുക"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"തിരഞ്ഞെടുത്ത വിജറ്റ് നൽകുക"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ഔട്ട്പുട്ട് ക്രമീകരണം നൽകുക"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"വോളിയം സ്ലൈഡറുകൾ വികസിപ്പിച്ചു"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"വോളിയം സ്ലൈഡറുകൾ ചുരുക്കി"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s മ്യൂട്ട് ചെയ്യുക"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s അൺമ്യൂട്ട് ചെയ്യുക"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യുന്നു"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ഓഡിയോ പ്ലേ ചെയ്യും"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"കോൾ പുരോഗമിക്കുന്നു"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 93304d64e511..573c8ff7e7d6 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджетүүдийг өөрчлөхийн тулд удаан дарна уу"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджетүүдийг өөрчлөх"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Идэвхгүй болгосон виджетийн аппын дүрс тэмдэг"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Виджетийг засах"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Хасах"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет нэмэх"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Виджетийг өөрчлөх"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Түгжээтэй дэлгэц дээрх виджетүүд"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виджет сонгох"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"виджетийг хасах"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"сонгосон виджетийг байрлуулах"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Оролтын тохиргоог оруулах"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Дууны түвшний гулсуулагчдыг дэлгэсэн"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Дууны түвшний гулсуулагчдыг хураасан"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s-н дууг хаах"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s-н дууг нээх"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> тоглуулж байна"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудиог дараахад тоглуулна"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Дуудлага хийгдэж буй:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Системийн UI Тохируулагч"</string> <string name="status_bar" msgid="4357390266055077437">"Статус самбар"</string> <string name="demo_mode" msgid="263484519766901593">"Системийн UI демо горим"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 0aad76430e82..3667f1e9dcd1 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट कस्टमाइझ करण्यासाठी प्रेस करून ठेवा"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट कस्टमाइझ करा"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद केलेल्या विजेटच्या अॅपचे आयकन"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"विजेट संपादित करा"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"काढून टाका"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोडा"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"विजेट कस्टमाइझ करा"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"लॉक स्क्रीनवरील विजेट"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"विजेट निवडा"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"विजेट काढून टाका"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"निवडलेले विजेट ठेवा"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अॅप्स आणि डेटा हटवला जाईल."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"आउटपुट सेटिंग्ज एंटर करा"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"व्हॉल्यूम स्लायडर विस्तारित केले आहेत"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"व्हॉल्यूम स्लायडर कोलॅप्स केले आहेत"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s म्यूट करा"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s अनम्यूट करा"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> वर प्ले करत आहे"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"यावर ऑडिओ प्ले होईल"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"यावर कॉल करत आहे"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 60c028d40869..60aedfa449cf 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -275,7 +275,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan sambungan"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string> - <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Dihidupkan sekali lagi esok secara automatik"</string> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Dihidupkan lagi esok secara automatik"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Ciri seperti Quick Share dan Find My Device menggunakan Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth akan dihidupkan esok pagi"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Perkongsian Audio"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon apl untuk melumpuhkan widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Alih keluar"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Sesuaikan widget"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widget pada skrin kunci"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pilih widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"alih keluar widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"letakkan widget dipilih"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Masukkan tetapan output"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Peluncur kelantangan dikembangkan"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Peluncur kelantangan dikuncupkan"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"redamkan %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"nyahredamkan %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Memainkan <xliff:g id="LABEL">%s</xliff:g> pada"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio dimainkan pada"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Membuat panggilan"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index dcbf837df5ec..b7398d0efa13 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ဝိဂျက်များ စိတ်ကြိုက်လုပ်ရန် ကြာကြာနှိပ်ထားပါ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ဝိဂျက်များကို စိတ်ကြိုက်လုပ်ရန်"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ပိတ်ထားသော ဝိဂျက်အတွက် အက်ပ်သင်္ကေတ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ဝိဂျက်ပြင်ရန်"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ဖယ်ရှားရန်"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ဝိဂျက်ထည့်ရန်"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ဝိဂျက်များကို စိတ်ကြိုက်လုပ်ရန်"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"လော့ခ်မျက်နှာပြင်ရှိ ဝိဂျက်များ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ဝိဂျက် ရွေးရန်"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ဝိဂျက် ဖယ်ရှားရန်"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ရွေးချယ်ထားသော ဝိဂျက်ကို တင်ရန်"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string> @@ -619,7 +619,7 @@ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"ထောင့်စုံအော်ဒီယို"</string> <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"ပိတ်"</string> <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"ပုံသေ"</string> - <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"ခေါင်းလှုပ်ရှားမှု စောင့်ကြည့်ခြင်း"</string> + <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"ခေါင်းလှုပ်ရှားမှု"</string> <string name="volume_ringer_change" msgid="3574969197796055532">"ဖုန်းခေါ်သံမုဒ်သို့ ပြောင်းရန် တို့ပါ"</string> <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"အသံပိတ်ရန်"</string> <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"အသံဖွင့်ရန်"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"အထွက် ဆက်တင်များ ထည့်ရန်"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"အသံအတိုးအကျယ် ရွှေ့တုံးများ ပိုပြထားသည်"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"အသံအတိုးအကျယ် ရွှေ့တုံးများ လျှော့ပြထားသည်"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s အသံပိတ်ရန်"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s အသံပြန်ဖွင့်ရန်"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ဖွင့်မည့်နေရာ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"အသံဖွင့်မည့်နေရာ"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ဖုန်းဆက်နေသည်"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 2724a5174752..2b131c7e5407 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Trykk lenge for å tilpasse modulene"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpass moduler"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktivert modul"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Endre modul"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Legg til modul"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tilpass moduler"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Moduler på låseskjermen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"velg modul"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"fjern modul"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"plasser den valgte modulen"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Angi utdatainnstillinger"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Glidebrytere for volum er skjult"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Glidebrytere for volum er skjult"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"kutt lyden til %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"slå på lyden til %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Spiller av <xliff:g id="LABEL">%s</xliff:g> på"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lyden spilles av på"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Aktiv samtale på"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Statusrad"</string> <string name="demo_mode" msgid="263484519766901593">"Demomodus for systemgrensesnitt"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 1bbab4ae9736..42abd4439421 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -203,10 +203,10 @@ <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"फेस अनलक सेटअप गर्न सकिएन। फेरि प्रयास गर्न सेटिङमा जानुहोस्।"</string> <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"फिंगरप्रिन्ट सेन्सरमा छुनुहोस्"</string> <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"जारी राख्न अनलक आइकनमा थिच्नुहोस्"</string> - <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"अनुहार पहिचान गर्न सकिएन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string> + <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"अनुहार मिलेन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string> <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) --> <skip /> - <string name="keyguard_face_failed" msgid="2346762871330729634">"अनुहार पहिचान गर्न सकिएन"</string> + <string name="keyguard_face_failed" msgid="2346762871330729634">"अनुहार मिलेन"</string> <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्"</string> <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"फेस अनलक उपलब्ध छैन"</string> <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेटहरू कस्टमाइज गर्न केही बेरसम्म थिच्नुहोस्"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेटहरू कस्टमाइज गर्नुहोस्"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"अफ गरिएको विजेटको एप जनाउने आइकन"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"विजेट सम्पादन गर्नुहोस्"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाउनुहोस्"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट हाल्नुहोस्"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"विजेटहरू कस्टमाइज गर्नुहोस्"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"लक स्क्रिनमा भएका विजेटहरू"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"विजेट चयन गर्नुहोस्"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"विजेट हटाउनुहोस्"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"चयन गरिएका विजेटका लागि ठाउँ चयन गर्नुहोस्"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"आउटपुटसम्बन्धी सेटिङमा जानुहोस्"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"भोल्युम स्लाइडरहरू एक्स्पान्ड गरिएका छन्"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"भोल्युम स्लाइडरहरू कोल्याप्स गरिएका छन्"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s म्युट गर्नुहोस्"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s अनम्युट गर्नुहोस्"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> प्ले गरिँदै छ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"अडियो यसमा प्ले हुने छ"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"कल चलिरहेको छ"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 1a831ac735f2..c459b93e87cb 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Houd lang ingedrukt om widgets aan te passen"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets aanpassen"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-icoon voor uitgezette widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Widget bewerken"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwijderen"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget toevoegen"</string> @@ -619,7 +621,7 @@ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Ruimtelijke audio"</string> <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Uit"</string> <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Vast"</string> - <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Hoofdbeweging volgen"</string> + <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Hoofdtracking"</string> <string name="volume_ringer_change" msgid="3574969197796055532">"Tik om de beltoonmodus te wijzigen"</string> <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"geluid uit"</string> <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"geluid aanzetten"</string> @@ -629,8 +631,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Uitvoerinstellingen invoeren"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volumeschuifregelaars uitgevouwen"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volumeschuifregelaars samengevouwen"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"geluid van %s uitzetten"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"geluid van %s aanzetten"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> wordt afgespeeld op"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio wordt afgespeeld op"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Bellen actief"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index e64b1b47eaa8..1444e6d69bc3 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -370,7 +370,7 @@ <string name="quick_settings_contrast_high" msgid="656049259587494499">"ଅଧିକ"</string> <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ହିଅରିଂ ଡିଭାଇସଗୁଡ଼ିକ"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"ହିଅରିଂ ଡିଭାଇସଗୁଡ଼ିକ"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ନୂଆ ଡିଭାଇସ ପେୟାର କର"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ନୂଆ ଡିଭାଇସ ପେୟାର କରନ୍ତୁ"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ନୂଆ ଡିଭାଇସ ପେୟାର କରିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"ପ୍ରିସେଟକୁ ଅପଡେଟ କରାଯାଇପାରିଲା ନାହିଁ"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ଡିଭାଇସର ମାଇକ୍ରୋଫୋନକୁ ଅନବ୍ଲକ କରିବେ?"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରିବା ପାଇଁ ଅଧିକ ସମୟ ଦବାନ୍ତୁ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ଅକ୍ଷମ କରାଯାଇଥିବା ୱିଜେଟ ପାଇଁ ଆପ ଆଇକନ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ୱିଜେଟକୁ ଏଡିଟ କରନ୍ତୁ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"କାଢ଼ି ଦିଅନ୍ତୁ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ଲକ ସ୍କ୍ରିନରେ ଥିବା ୱିଜେଟଗୁଡ଼ିକ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ୱିଜେଟ ଚୟନ କରନ୍ତୁ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ୱିଜେଟକୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ଚୟନିତ ୱିଜେଟ ରଖନ୍ତୁ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍ ବଦଳାନ୍ତୁ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍ ଓ ଡାଟା ଡିଲିଟ୍ ହୋଇଯିବ।"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ଆଉଟପୁଟ ସେଟିଂସ ଲେଖନ୍ତୁ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ଭଲ୍ୟୁମ ସ୍ଲାଇଡରଗୁଡ଼ିକୁ ବିସ୍ତାର କରାଯାଇଛି"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ଭଲ୍ୟୁମ ସ୍ଲାଇଡରଗୁଡ଼ିକୁ ସଙ୍କୁଚିତ କରାଯାଇଛି"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sକୁ ମ୍ୟୁଟ କରନ୍ତୁ"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%sକୁ ଅନମ୍ୟୁଟ କରନ୍ତୁ"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>ରେ ପ୍ଲେ କରାଯାଉଛି"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ଅଡିଓ ଏଥିରେ ପ୍ଲେ ହେବ"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"କଲ ଚାଲିଛି"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"ସିଷ୍ଟମ୍ UI ଟ୍ୟୁନର୍"</string> <string name="status_bar" msgid="4357390266055077437">"ଷ୍ଟାଟସ୍ ବାର୍"</string> <string name="demo_mode" msgid="263484519766901593">"ସିଷ୍ଟମ୍ UI ଡେମୋ ମୋଡ୍"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 394d790ecbdb..23914b3f16c6 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ਵਿਜੇਟਾਂ ਨੂੰ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਦਬਾਈ ਰੱਖੋ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ਵਿਜੇਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ਬੰਦ ਵਿਜੇਟ ਲਈ ਐਪ ਪ੍ਰਤੀਕ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ਵਿਜੇਟ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ਹਟਾਓ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ਵਿਜੇਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ਲਾਕ ਸਕ੍ਰੀਨ \'ਤੇ ਵਿਜੇਟ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ਵਿਜੇਟ ਚੁਣੋ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ਵਿਜੇਟ ਹਟਾਓ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ਚੁਣੇ ਗਏ ਵਿਜੇਟ ਲਈ ਥਾਂ ਚੁਣੋ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟੇ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ।"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ਆਊਟਪੁੱਟ ਸੈਟਿੰਗਾਂ ਦਾਖਲ ਕਰੋ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ਅਵਾਜ਼ ਸਲਾਈਡਰਾਂ ਵਿਸਤਾਰ ਕੀਤਾ ਗਿਆ"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ਅਵਾਜ਼ ਸਲਾਈਡਰਾਂ ਨੂੰ ਸਮੇਟਿਆ ਗਿਆ"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ਨੂੰ ਮਿਊਟ ਕਰੋ"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ਨੂੰ ਅਣਮਿਊਟ ਕਰੋ"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ਚਲਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ਆਡੀਓ ਇਸ \'ਤੇ ਚੱਲੇਗੀ"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ਕਾਲ ਜਾਰੀ ਹੈ"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI ਟਿਊਨਰ"</string> <string name="status_bar" msgid="4357390266055077437">"ਸਥਿਤੀ ਪੱਟੀ"</string> <string name="demo_mode" msgid="263484519766901593">"ਸਿਸਟਮ UI ਡੈਮੋ ਮੋਡ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 920862837eb9..4609f08ca0e0 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Przytrzymaj, aby dostosować widżety"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Dostosuj widżety"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacji z wyłączonym widżetem"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edytuj widżet"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Usuń"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widżet"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Dostosuj widżety"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widżety na ekranie blokady"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"wybierz widżet"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"usuń widżet"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"umieść wybrany widżet"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Otwórz ustawienia sygnału wyjściowego"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Suwaki głośności są rozwinięte"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Suwaki głośności są zwinięte"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"wycisz: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"wyłącz wyciszenie: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Odtwarzam <xliff:g id="LABEL">%s</xliff:g> na"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Wyjścia dźwięku:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Dzwonię na:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Kalibrator System UI"</string> <string name="status_bar" msgid="4357390266055077437">"Pasek stanu"</string> <string name="demo_mode" msgid="263484519766901593">"Tryb demonstracyjny interfejsu"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 60472c8bcb34..425fa65b00fc 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets na tela de bloqueio"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remover widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"posicionar widget selecionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Inserir configurações de saída"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes de volume abertos"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes de volume fechados"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desativar o som de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ativar o som de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Tocando <xliff:g id="LABEL">%s</xliff:g> em"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Onde o áudio vai tocar?"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ligando"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador System UI"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de status"</string> <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da interface do sistema"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 3b9bebb784d8..44531331bd68 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha premido para personalizar os widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone da app do widget desativado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets no ecrã de bloqueio"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remover widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"posicionar widget selecionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introduzir definições de saída"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controlos de deslize do volume expandidos"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controlos de deslize do volume reduzidos"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desativar o som de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"reativar o som de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"A ouvir <xliff:g id="LABEL">%s</xliff:g> em:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Áudio ouvido em:"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chamada em curso"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 60472c8bcb34..425fa65b00fc 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets na tela de bloqueio"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remover widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"posicionar widget selecionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Inserir configurações de saída"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes de volume abertos"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes de volume fechados"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desativar o som de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ativar o som de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Tocando <xliff:g id="LABEL">%s</xliff:g> em"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Onde o áudio vai tocar?"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ligando"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador System UI"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de status"</string> <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da interface do sistema"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 84d68e70037c..596349ee57f1 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Apasă lung pentru a personaliza widgeturi"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizează widgeturile"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Pictograma aplicației pentru widgetul dezactivat"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editează widgetul"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Elimină"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adaugă un widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizează widgeturile"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgeturi pe ecranul de blocare"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selectează un widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"elimină widgetul"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"plasează widgetul selectat"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introdu setările de ieșire"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Glisoare de volum extinse"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Glisoare de volum restrânse"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"dezactivează sunetul pentru %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"activează sunetul pentru %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Se redă <xliff:g id="LABEL">%s</xliff:g> pe"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Conținutul audio se va reda pe"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Apel în curs pe"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Bară de stare"</string> <string name="demo_mode" msgid="263484519766901593">"Mod demonstrativ pentru IU sistem"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index c405469371da..6fabeae41a4a 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Нажмите и удерживайте, чтобы настроить виджеты."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Настроить виджеты"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок приложения для отключенного виджета"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Изменить виджет"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Удалить"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавить виджет"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Настроить виджеты"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Виджеты на заблокированном экране"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"выбрать виджет"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"удалить виджет"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"разместить выбранный виджет"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Перейти к настройкам вывода аудио"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Ползунки для регулировки громкости развернуты"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Ползунки для регулировки громкости свернуты"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"отключить звук: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"включить звук: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> – запущено здесь:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Проигрывание аудио:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Настройки вызова"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Строка состояния"</string> <string name="demo_mode" msgid="263484519766901593">"Интерфейс системы: деморежим"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 37bb23ac64a7..238e6c8e2b80 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"විජට් අභිරුචිකරණය කිරීමට දිගු ඔබන්න"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"විජට්ටු අභිරුචි කරන්න"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"අබල කළ විජට් සඳහා යෙදුම් නිරූපකය"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"විජට්ටු සංස්කරණ කරන්න"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ඉවත් කරන්න"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"විජට්ටුව එක් කරන්න"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"විජට්ටු අභිරුචි කරන්න"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"අගුළු තිරයෙහි විජට්"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"විජට්ටුව තෝරන්න"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"විජට්ටුව ඉවත් කරන්න"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"තෝරන ලද විජට්ටුව තබන්න"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ප්රතිදාන සැකසීම් ඇතුල් කරන්න"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"හඬ ස්ලයිඩර දිගහැර ඇත"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"හඬ ස්ලයිඩර හකුළා ඇත"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s නිහඬ කරන්න"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s නිහඬ නොකරන්න"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> වාදනය කරන්නේ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ශ්රව්ය වාදනය වනු ඇත්තේ"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ඇමතීම"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"පද්ධති UI සුසරකය"</string> <string name="status_bar" msgid="4357390266055077437">"තත්ත්ව තීරුව"</string> <string name="demo_mode" msgid="263484519766901593">"පද්ධති UI ආදර්ශන ප්රකාරය"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 675a34522086..825dba620870 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -269,7 +269,7 @@ <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Klepnutím pripojíte alebo odpojíte zariadenie"</string> <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Spárovať nové zariadenie"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Zobraziť všetko"</string> - <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použiť Bluetooth"</string> + <string name="turn_on_bluetooth" msgid="5681370462180289071">"Používať Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Pripojené"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Zdieľanie zvuku"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uložené"</string> @@ -370,7 +370,7 @@ <string name="quick_settings_contrast_high" msgid="656049259587494499">"Vysoký"</string> <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Načúvacie zariadenia"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Načúvacie zariadenia"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Párovanie nového zariadenia"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Spárovať nové zariadenie"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknutím spárujete nové zariadenie"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Predvoľbu sa nepodarilo aktualizovať"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Chcete odblokovať mikrofón zariadenia?"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Miniaplikácie prispôsobíte dlhým stlačením"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prispôsobiť miniaplikácie"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona deaktivovanej miniaplikácie"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Upraviť miniaplikáciu"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrániť"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridať miniaplikáciu"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prispôsobiť miniaplikácie"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Miniaplikácie na uzamknutej obrazovke"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vybrať miniaplikáciu"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"odstrániť miniaplikáciu"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"prepnúť vybranú miniaplikáciu"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string> @@ -619,7 +619,7 @@ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Priestorový zvuk"</string> <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Vypnuté"</string> <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Pevné"</string> - <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Sled. pohybov hlavy"</string> + <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Sled. polohy hlavy"</string> <string name="volume_ringer_change" msgid="3574969197796055532">"Režim zvonenia zmeníte klepnutím"</string> <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"vypnite zvuk"</string> <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"zapnite zvuk"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Zadať nastavenia výstupu"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Posúvače hlasitosti sú rozbalené"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Posúvače hlasitosti sú zbalené"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"vypnete zvuk %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"zapnete zvuk %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> sa prehráva v:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk sa prehrá cez"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Volanie je zapnuté"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Tuner používateľského rozhrania systému"</string> <string name="status_bar" msgid="4357390266055077437">"Stavový riadok"</string> <string name="demo_mode" msgid="263484519766901593">"Ukážka používateľského rozhrania systému"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index f97d6624c00c..82f2df73a355 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pridržite za prilagajanje pripomočkov"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagajanje pripomočkov"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogočen pripomoček"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Urejanje pripomočka"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrani"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajanje pripomočka"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagajanje pripomočkov"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Pripomočki na zaklenjenem zaslonu"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"izberite pripomoček"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"odstranitev pripomočka"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavitev izbranega pripomočka"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Vnos izhodnih nastavitev"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Razširitev drsnikov za glasnost"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Strnitev drsnikov za glasnost"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"izklop zvoka %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"vklop zvoka %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Predvajanje »<xliff:g id="LABEL">%s</xliff:g>« v"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvok bo predvajan v"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Poteka klicanje"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Uglaševalnik uporabniškega vmesnika sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Vrstica stanja"</string> <string name="demo_mode" msgid="263484519766901593">"Predstavitveni način uporabniškega vmesnika sistema"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index b638198c6d5f..af2e3c788b5b 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Shtyp gjatë për të personalizuar miniaplikacionet"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizo miniaplikacionet"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona e aplikacionit për miniaplikacionin e çaktivizuar"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modifiko miniaplikacionin"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Hiq"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Shto miniaplikacionin"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizo miniaplikacionet"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Miniaplikacionet në ekranin e kyçjes"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"zgjidh miniaplikacionin"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"hiq miniaplikacionin"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"vendos miniaplikacionin e zgjedhur"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Të gjitha aplikacionet dhe të dhënat në këtë sesion do të fshihen."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Hyr te cilësimet e daljes"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Rrëshqitësit e volumit u zgjeruan"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Rrëshqitësit e volumit u palosën"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"çaktivizo audion: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"aktivizo audion: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Po luhet <xliff:g id="LABEL">%s</xliff:g> te"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Do të luhet audio te"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Telefonatë aktive"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizuesi i Sistemit të Ndërfaqes së Përdoruesit"</string> <string name="status_bar" msgid="4357390266055077437">"Shiriti i statusit"</string> <string name="demo_mode" msgid="263484519766901593">"Modaliteti i demonstrimit i ndërfaqes së përdoruesit të sistemit"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 4cc1134a369a..42b979c0add9 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Дуги притисак за прилагођавање виџета"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Прилагоди виџете"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона апликације за онемогућен виџет"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Измени виџет"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Уклони"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додај виџет"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Прилагодите виџете"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Виџети на закључаном екрану"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"изаберите виџет"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"уклоните виџет"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"поставите изабрани виџет"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Унесите подешавања излазног сигнала"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Клизачи за јачину звука су проширени"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Клизачи за јачину звука су скупљени"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"искључите звук за: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"укључите звук за: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> се пушта на"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Звук се пушта на"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Позив на уређају"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Тјунер за кориснички интерфејс система"</string> <string name="status_bar" msgid="4357390266055077437">"Статусна трака"</string> <string name="demo_mode" msgid="263484519766901593">"Режим демонстрације за кориснички интерфејс система"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index b0ce12f8e5a2..29dca4630df8 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tryck länge för att anpassa widgetar"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Anpassa widgetar"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon för inaktiverad widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Redigera widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Ta bort"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lägg till widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Anpassa widgetar"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgetar på låsskärmen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"välj widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ta bort widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"placera vald widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ange inställningar för utdata"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volymreglagen har utökats"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volymreglagen har komprimerats"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"stäng av ljudet för %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"slå på ljudet för %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Spelar upp <xliff:g id="LABEL">%s</xliff:g> på"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Ljud spelas upp på"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Samtal på"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Inställningar för systemgränssnitt"</string> <string name="status_bar" msgid="4357390266055077437">"Statusfält"</string> <string name="demo_mode" msgid="263484519766901593">"Demoläge för systemgränssnitt"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 8dc22a785069..9d5181e21b45 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Bonyeza kwa muda mrefu uweke mapendeleo ya wijeti"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Badilisha wijeti upendavyo"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Aikoni ya programu ya wijeti iliyozimwa"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Badilisha wijeti"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Ondoa"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ongeza wijeti"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Badilisha wijeti upendavyo"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Wijeti kwenye skrini iliyofungwa"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"chagua wijeti"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ondoa wijeti"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"weka wijeti uliyochagua"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Weka mipangilio ya sauti inayotoka"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Vitelezi vya sauti vimepanuliwa"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Vitelezi vya kiwango cha sauti vimekunjwa"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"zima sauti ya %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"rejesha sauti ya %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Inacheza <xliff:g id="LABEL">%s</xliff:g> kwenye"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Sauti itacheza kwenye"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Simu inaendelea"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Kirekebishi cha kiolesura cha mfumo"</string> <string name="status_bar" msgid="4357390266055077437">"Sehemu ya kuonyesha hali"</string> <string name="demo_mode" msgid="263484519766901593">"Hali ya onyesho la kirekebishi cha kiolesura cha mfumo"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index ee86acac2d7e..a2f4ce5424a2 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -275,7 +275,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"சேமிக்கப்பட்டது"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"இணைப்பு நீக்கும்"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"செயல்படுத்தும்"</string> - <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"நாளைக்குத் தானாகவே மீண்டும் இயக்கப்படும்"</string> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"நாளைக்குத் தானாகவே மீண்டும் இயக்கப்படுதல்"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"விரைவுப் பகிர்தல், Find My Device போன்ற அம்சங்கள் புளூடூத்தைப் பயன்படுத்துகின்றன"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"நாளை காலை புளூடூத் இயக்கப்படும்"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"ஆடியோ பகிர்வு"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"விட்ஜெட்களைப் பிரத்தியேகமாக்க நீண்ட நேரம் அழுத்துக"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"விட்ஜெட்களைப் பிரத்தியேகமாக்குங்கள்"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"முடக்கப்பட்ட விட்ஜெட்டுக்கான ஆப்ஸ் ஐகான்"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"விட்ஜெட்டைத் திருத்து"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"அகற்றும்"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"விட்ஜெட்டைச் சேர்"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"விட்ஜெட்களைப் பிரத்தியேகமாக்கும்"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"பூட்டுத் திரையில் விட்ஜெட்கள்"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"விட்ஜெட்டைத் தேர்ந்தெடுக்கும்"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"விட்ஜெட்டை அகற்றும்"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"தேர்ந்தெடுத்த விட்ஜெட்டைக் காட்சிப்படுத்தும்"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"வெளியீட்டு அமைப்புகளுக்குச் செல்லும்"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ஒலியளவு ஸ்லைடர்கள் விரிவாக்கப்பட்டன"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ஒலியளவு ஸ்லைடர்கள் சுருக்கப்பட்டன"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ஐ ஒலியடக்கும்"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ஐ ஒலி இயக்கும்"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"இதில் <xliff:g id="LABEL">%s</xliff:g> பிளே ஆகிறது"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ஆடியோ இதில் பிளே ஆகும்"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"அழைப்பில் உள்ளது"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"நிலைப் பட்டி"</string> <string name="demo_mode" msgid="263484519766901593">"சிஸ்டம் பயனர் இடைமுக டெமோ பயன்முறை"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index d8622a3d9fc1..5b703a0d96fc 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"విడ్జెట్లను అనుకూలీకరించడానికి, నొక్కి, ఉంచండి"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"విడ్జెట్లను అనుకూలంగా మార్చండి"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"డిజేబుల్ చేయబడిన విడ్జెట్ కోసం యాప్ చిహ్నం"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"విడ్జెట్ను ఎడిట్ చేయండి"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"తీసివేయండి"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"విడ్జెట్ను జోడించండి"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"విడ్జెట్లను అనుకూలంగా మార్చండి"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"లాక్ స్క్రీన్లో విడ్జెట్లు"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"విడ్జెట్ను ఎంచుకోండి"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"విడ్జెట్ను తీసివేయండి"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ఎంచుకున్న విడ్జెట్ కోసం ప్లేస్ను ఎంచుకోండి"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్డౌన్ మెనూ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని యాప్లు మరియు డేటా తొలగించబడతాయి."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"అవుట్పుట్ సెట్టింగ్లను ఎంటర్ చేయండి"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"వాల్యూమ్ స్లయిడర్లు విస్తరించబడ్డాయి"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"వాల్యూమ్ స్లయిడర్లు కుదించబడ్డాయి"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sను మ్యూట్ చేయండి"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%sను అన్మ్యూట్ చేయండి"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ప్లే అయ్యే డివైజ్"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ఆడియో ప్లే డివైజ్"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"కాల్ ప్రోగ్రెస్లో ఉంది"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"సిస్టమ్ UI ట్యూనర్"</string> <string name="status_bar" msgid="4357390266055077437">"స్టేటస్ బార్"</string> <string name="demo_mode" msgid="263484519766901593">"సిస్టమ్ UI డెమో మోడ్"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 9f09b9ccdfe4..73ca20004444 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"กดค้างเพื่อปรับแต่งวิดเจ็ต"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ปรับแต่งวิดเจ็ต"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ไอคอนแอปสำหรับวิดเจ็ตที่ปิดใช้อยู่"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"แก้ไขวิดเจ็ต"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"นำออก"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"เพิ่มวิดเจ็ต"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ปรับแต่งวิดเจ็ต"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"วิดเจ็ตในหน้าจอล็อก"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"เลือกวิดเจ็ต"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"นำวิดเจ็ตออก"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"จัดวางวิดเจ็ตที่เลือก"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"เข้าสู่การตั้งค่าเอาต์พุต"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ขยายแถบเลื่อนระดับเสียงแล้ว"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ยุบแถบเลื่อนระดับเสียงแล้ว"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"ปิดเสียง%s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"เปิดเสียง%s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"กำลังเล่น <xliff:g id="LABEL">%s</xliff:g> ใน"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"เสียงจะเล่นต่อใน"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"กำลังโทรติดต่อ"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 9122627107f4..0c616fa91dc1 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pindutin nang matagal para i-customize ang mga widget"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"I-customize ang mga widget"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icon ng app para sa na-disable na widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"I-edit ang widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Alisin"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Magdagdag ng widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"I-customize ang mga widget"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Mga widget sa lock screen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pumili ng widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"alisin ang widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ilagay ang napiling widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Pumunta sa mga setting ng output"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Na-expand ang mga slider ng volume"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Na-collapse ang mga slider ng volume"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"i-mute ang %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"i-unmute ang %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Nagpe-play ang <xliff:g id="LABEL">%s</xliff:g> sa"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"I-play ang audio sa"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Tumatawag sa"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 1f7a8e0b4ca4..6723c4226f04 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widget\'ları özelleştirmek için uzun basın"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widget\'ları özelleştir"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Devre dışı bırakılan widget\'ın uygulama simgesi"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Widget\'ı düzenle"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Kaldır"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget ekle"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Widget\'ları özelleştir"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Kilit ekranındaki widget\'lar"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"widget seçin"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"widget\'ı kaldır"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"seçilen widget\'ı yerleştir"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Çıkış ayarlarını gir"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Ses seviyesi kaydırma çubukları genişletildi"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Ses seviyesi kaydırma çubukları daraltıldı"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s sesini kapatma"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s sesini açma"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> şurada çalacak:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Ses şurada çalacak:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Şu cihaz aranıyor:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sistem Arayüzü Ayarlayıcısı"</string> <string name="status_bar" msgid="4357390266055077437">"Durum çubuğu"</string> <string name="demo_mode" msgid="263484519766901593">"Sistem kullanıcı arayüzü demo modu"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index e9b45bf14e99..2659f9fedfee 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Утримуйте, щоб налаштувати віджети"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Налаштувати віджети"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок додатка для вимкненого віджета"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Редагувати віджет"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Видалити"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додати віджет"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Налаштувати віджети"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Віджети на заблокованому екрані"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виберіть віджет"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"видалити віджет"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"розмістити вибраний віджет"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Відкрити налаштування відтворення"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Повзунки гучності розгорнуто"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Повзунки гучності згорнуто"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"вимкнути звук %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"увімкнути звук %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Де відтворюється <xliff:g id="LABEL">%s</xliff:g>:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Де гратиме аудіо:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Триває виклик"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Рядок стану"</string> <string name="demo_mode" msgid="263484519766901593">"Демо-режим інтерфейсу системи"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index f9fc9f0de389..2a7fda1791e5 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ویجٹس کو حسب ضرورت بنانے کے لیے لانگ پریس کریں"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ویجیٹس کو حسب ضرورت بنائیں"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"غیر فعال ویجیٹ کے لئے ایپ آئیکن"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ویجیٹ میں ترمیم کریں"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ہٹائیں"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ویجیٹ شامل کریں"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ویجیٹس کو حسب ضرورت بنائیں"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"مقفل اسکرین پر ویجیٹس"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ویجیٹ منتخب کریں"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ویجیٹ ہٹائیں"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"منتخب ویجیٹ رکھیں"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"آؤٹ پٹ کی ترتیبات درج کریں"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"والیوم سلائیڈرز کو پھیلا دیا گیا"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"والیوم سلائیڈرز سکیڑا گیا"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s خاموش کریں"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s غیر خاموش کریں"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> پر چل رہی ہے"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"آڈیو اس پر چلے گی"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"کال کی جا رہی ہے"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"سسٹم UI ٹیونر"</string> <string name="status_bar" msgid="4357390266055077437">"اسٹیٹس بار"</string> <string name="demo_mode" msgid="263484519766901593">"سسٹم UI ڈیمو موڈ"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 60baeec4ed84..0d7709c51775 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vidjetlarni sozlash uchun bosib turing"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidjetlarni moslashtirish"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Faolsizlantirilgan vidjet uchun ilova belgisi"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Vidjetni tahrirlash"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Olib tashlash"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidjet kiritish"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Vidjetlarni moslash"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Ekran qulfidagi vidjetlar"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vidjet tanlash"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"vidjetni olib tashlash"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"tanlangan vidjetni joylash"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Chiqarish sozlamalarini kiritish"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Tovush slayderlari yoyilgan"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Tovush slayderlari yigʻilgan"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sni ovozsiz qilish"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ovozini chiqarish"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>da ijro etilmoqda"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio ijro etiladi"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chaqiruv yoniq"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 9656eb3cf81e..2ddcd6d32f44 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nhấn và giữ để tuỳ chỉnh tiện ích"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tuỳ chỉnh tiện ích"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Biểu tượng ứng dụng của tiện ích đã bị vô hiệu hoá"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Chỉnh sửa tiện ích"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Xoá"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Thêm tiện ích"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tuỳ chỉnh tiện ích"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Các tiện ích trên màn hình khoá"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"chọn tiện ích"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"xoá tiện ích"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"đặt tiện ích đã chọn vào vị trí"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Mở phần cài đặt thiết bị đầu ra"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Đã mở rộng thanh trượt âm lượng"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Đã thu gọn thanh trượt âm lượng"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"tắt tiếng %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"bật tiếng %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Đang phát <xliff:g id="LABEL">%s</xliff:g> trên"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Âm thanh sẽ phát trên"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Đang gọi điện"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Bộ điều hướng giao diện người dùng hệ thống"</string> <string name="status_bar" msgid="4357390266055077437">"Thanh trạng thái"</string> <string name="demo_mode" msgid="263484519766901593">"Chế độ thử nghiệm giao diện người dùng hệ thống"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 6f1a0bd0bc7b..64e37ce4b88d 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"长按即可自定义微件"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自定义微件"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用微件的应用图标"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"修改微件"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"添加微件"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"自定义微件"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"锁定屏幕上的微件"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"选择微件"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"移除微件"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"放置所选微件"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"进入输出设置"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"音量滑块已展开"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"音量滑块已收起"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"静音“%s”"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"取消静音“%s”"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>播放位置:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"音频播放位置:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"正在通话"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"系统界面调节工具"</string> <string name="status_bar" msgid="4357390266055077437">"状态栏"</string> <string name="demo_mode" msgid="263484519766901593">"系统界面演示模式"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 47d0692c3d84..714e479cb515 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用小工具的應用程式圖示"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"自訂小工具"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"上鎖畫面上的小工具"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"揀小工具"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"移除小工具"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"放置所選小工具"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"輸入輸出設定"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"打開咗音量滑桿"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"閂埋咗音量滑桿"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"將%s設定為靜音"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"取消%s的靜音設定"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"正在播放「<xliff:g id="LABEL">%s</xliff:g>」的裝置:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"音訊播放媒體"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"通話中"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"系統使用者介面調諧器"</string> <string name="status_bar" msgid="4357390266055077437">"狀態列"</string> <string name="demo_mode" msgid="263484519766901593">"系統使用者介面示範模式"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index b3ed7a051bc0..495a055c3e70 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"所停用小工具的應用程式圖示"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"自訂小工具"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"螢幕鎖定畫面上的小工具"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"選取小工具"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"移除小工具"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"放置所選小工具"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會刪除。"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"進入輸出設定"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"音量滑桿已展開"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"音量滑桿已收合"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"將%s設為靜音"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"將%s取消靜音"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"正在播放「<xliff:g id="LABEL">%s</xliff:g>」的裝置:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"音訊播放位置"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"通話中"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"系統使用者介面調整精靈"</string> <string name="status_bar" msgid="4357390266055077437">"狀態列"</string> <string name="demo_mode" msgid="263484519766901593">"系統 UI 展示模式"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 6e9e08b63095..f5ee5a0d60b1 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Cindezela isikhathi eside ukuze wenze ngokwezifiso amawijethi"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Yenza ngokwezifiso amawijethi"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Isithonjana se-app sewijethi evaliwe"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Hlela amawijethi"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Susa"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engeza iwijethi"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Yenza ngokwezifiso amawijethi"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Amawijethi ekukhiyeni isikrini"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"khetha iwijethi"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"susa iwijethi"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"beka iwijethi ekhethiwe"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Faka amasethingi wokuphumayo"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Izilayidi zevolumu zinwetshiwe"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Izilayidi zevolumu zigoqiwe"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"thulisa i-%s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"susa ukuthula kwe-%s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Idlala ku-<xliff:g id="LABEL">%s</xliff:g>"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Umsindo uzodlala"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ifonela kokuthi"</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a1daebd7513e..9cd7d16664ad 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -285,6 +285,9 @@ the amount by the view is positioned above the screen before the animation starts. --> <dimen name="heads_up_appear_y_above_screen">32dp</dimen> + <!-- padding between the old and new heads up notifications for the hun cycling animation --> + <dimen name="heads_up_cycling_padding">8dp</dimen> + <!-- padding between the heads up and the statusbar --> <dimen name="heads_up_status_bar_padding">8dp</dimen> @@ -446,6 +449,8 @@ <dimen name="overlay_preview_container_margin">8dp</dimen> <dimen name="overlay_action_container_margin_horizontal">8dp</dimen> <dimen name="overlay_action_container_margin_bottom">6dp</dimen> + <!-- minimum distance to the left, right or bottom edges. --> + <dimen name="overlay_action_container_minimum_edge_spacing">12dp</dimen> <dimen name="overlay_bg_protection_height">242dp</dimen> <dimen name="overlay_action_container_corner_radius">20dp</dimen> <dimen name="overlay_action_container_padding_vertical">8dp</dimen> @@ -909,8 +914,8 @@ <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> + <dimen name="communal_widget_picker_desired_width">360dp</dimen> + <dimen name="communal_widget_picker_desired_height">240dp</dimen> <!-- The width/height of the unlock icon view on keyguard. --> <dimen name="keyguard_lock_height">42dp</dimen> @@ -1708,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> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 45bcd829336f..aecc9066b552 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -384,6 +384,8 @@ <!-- Content description for the app logo icon on biometric prompt. [CHAR LIMIT=NONE] --> <string name="biometric_dialog_logo">App logo</string> + <!-- List of packages for which we want to show overridden logo. For example, an app overrides its launcher logo, if it's in this array, biometric dialog shows the overridden logo; otherwise biometric dialog still shows the default application info icon. [CHAR LIMIT=NONE] --> + <string-array name="biometric_dialog_package_names_for_logo_with_overrides" /> <!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] --> <string name="biometric_dialog_confirm">Confirm</string> <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] --> @@ -1158,6 +1160,8 @@ <string name="button_to_configure_widgets_text">Customize widgets</string> <!-- Description for the App icon of disabled widget. [CHAR LIMIT=NONE] --> <string name="icon_description_for_disabled_widget">App icon for disabled widget</string> + <!-- Description for the App icon of a package that is currently being installed. [CHAR LIMIT=NONE] --> + <string name="icon_description_for_pending_widget">App icon for a widget being installed</string> <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] --> <string name="edit_widget">Edit widget</string> <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] --> @@ -1634,9 +1638,15 @@ <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] --> - <string name="volume_panel_hint_mute">mute %s</string> + <string name="volume_panel_hint_mute">Mute %s</string> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] --> - <string name="volume_panel_hint_unmute">unmute %s</string> + <string name="volume_panel_hint_unmute">Unmute %s</string> + + <!-- Hint for accessibility. This is announced when the stream is muted [CHAR_LIMIT=NONE] --> + <string name="volume_panel_hint_muted">muted</string> + + <!-- Hint for accessibility. This is announced when ring mode is set to Vibrate. [CHAR_LIMIT=NONE] --> + <string name="volume_panel_hint_vibrate">vibrate</string> <!-- Title with application label for media output settings when there is media playing. [CHAR LIMIT=20] --> <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt index 422f02f0a7c9..8979ef1aa160 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt @@ -16,7 +16,6 @@ package com.android.systemui.biometrics import android.Manifest -import android.annotation.IntDef import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX @@ -39,14 +38,9 @@ import android.view.WindowMetrics import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityManager import com.android.internal.widget.LockPatternUtils -import java.lang.annotation.Retention -import java.lang.annotation.RetentionPolicy +import com.android.systemui.biometrics.shared.model.PromptKind object Utils { - const val CREDENTIAL_PIN = 1 - const val CREDENTIAL_PATTERN = 2 - const val CREDENTIAL_PASSWORD = 3 - /** Base set of layout flags for fingerprint overlay widgets. */ const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS = (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or @@ -91,17 +85,16 @@ object Utils { (promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0 @JvmStatic - @CredentialType - fun getCredentialType(utils: LockPatternUtils, userId: Int): Int = + fun getCredentialType(utils: LockPatternUtils, userId: Int): PromptKind = when (utils.getKeyguardStoredPasswordQuality(userId)) { - PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN + PASSWORD_QUALITY_SOMETHING -> PromptKind.Pattern PASSWORD_QUALITY_NUMERIC, - PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN + PASSWORD_QUALITY_NUMERIC_COMPLEX -> PromptKind.Pin PASSWORD_QUALITY_ALPHABETIC, PASSWORD_QUALITY_ALPHANUMERIC, PASSWORD_QUALITY_COMPLEX, - PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD - else -> CREDENTIAL_PASSWORD + PASSWORD_QUALITY_MANAGED -> PromptKind.Password + else -> PromptKind.Password } @JvmStatic @@ -129,8 +122,4 @@ object Utils { return windowMetrics?.windowInsets?.getInsets(WindowInsets.Type.navigationBars()) ?: Insets.NONE } - - @Retention(RetentionPolicy.SOURCE) - @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD) - annotation class CredentialType } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt index a97e2dc42607..b99c51489521 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,21 @@ package com.android.systemui.biometrics.shared.model -import com.android.systemui.biometrics.Utils - -// TODO(b/251476085): this should eventually replace Utils.CredentialType -/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */ sealed interface PromptKind { + object None : PromptKind + data class Biometric( + /** The available modalities for the authentication on the prompt. */ val activeModalities: BiometricModalities = BiometricModalities(), + // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of + // simply depending on rotations. + val showTwoPane: Boolean = false, ) : PromptKind object Pin : PromptKind object Pattern : PromptKind object Password : PromptKind + + fun isBiometric() = this is Biometric + fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/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/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 69aa90996946..b4377eae4d1f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -22,7 +22,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static com.android.systemui.shared.Flags.shadeAllowBackGesture; -import android.annotation.IntDef; +import android.annotation.LongDef; import android.content.Context; import android.content.res.Resources; import android.view.ViewConfiguration; @@ -51,92 +51,94 @@ public class QuickStepContract { // Overview is disabled, either because the device is in lock task mode, or because the device // policy has disabled the feature - public static final int SYSUI_STATE_SCREEN_PINNING = 1 << 0; + public static final long SYSUI_STATE_SCREEN_PINNING = 1L << 0; // The navigation bar is hidden due to immersive mode - public static final int SYSUI_STATE_NAV_BAR_HIDDEN = 1 << 1; + public static final long SYSUI_STATE_NAV_BAR_HIDDEN = 1L << 1; // The notification panel is expanded and interactive (either locked or unlocked), and the // quick settings is not expanded - public static final int SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1 << 2; + public static final long SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1L << 2; // The keyguard bouncer is showing - public static final int SYSUI_STATE_BOUNCER_SHOWING = 1 << 3; + public static final long SYSUI_STATE_BOUNCER_SHOWING = 1L << 3; // The navigation bar a11y button should be shown - public static final int SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1 << 4; + public static final long SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1L << 4; // The navigation bar a11y button shortcut is available - public static final int SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1 << 5; + public static final long SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1L << 5; // The keyguard is showing and not occluded - public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1 << 6; + public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1L << 6; // The recents feature is disabled (either by SUW/SysUI/device policy) - public static final int SYSUI_STATE_OVERVIEW_DISABLED = 1 << 7; + public static final long SYSUI_STATE_OVERVIEW_DISABLED = 1L << 7; // The home feature is disabled (either by SUW/SysUI/device policy) - public static final int SYSUI_STATE_HOME_DISABLED = 1 << 8; + public static final long SYSUI_STATE_HOME_DISABLED = 1L << 8; // The keyguard is showing, but occluded - public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1 << 9; + public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1L << 9; // The search feature is disabled (either by SUW/SysUI/device policy) - public static final int SYSUI_STATE_SEARCH_DISABLED = 1 << 10; + public static final long SYSUI_STATE_SEARCH_DISABLED = 1L << 10; // The notification panel is expanded and interactive (either locked or unlocked), and quick // settings is expanded. - public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11; + public static final long SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1L << 11; // Winscope tracing is enabled - public static final int SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1 << 12; + public static final long SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1L << 12; // The Assistant gesture should be constrained. It is up to the launcher implementation to // decide how to constrain it - public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13; + public static final long SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1L << 13; // The bubble stack is expanded. This means that the home gesture should be ignored, since a // swipe up is an attempt to close the bubble stack, but that the back gesture should remain // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble // stack. - public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14; + public static final long SYSUI_STATE_BUBBLES_EXPANDED = 1L << 14; // A SysUI dialog is showing. - public static final int SYSUI_STATE_DIALOG_SHOWING = 1 << 15; + public static final long SYSUI_STATE_DIALOG_SHOWING = 1L << 15; // The one-handed mode is active - public static final int SYSUI_STATE_ONE_HANDED_ACTIVE = 1 << 16; + public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16; // Allow system gesture no matter the system bar(s) is visible or not - public static final int SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1 << 17; + public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17; // The IME is showing - public static final int SYSUI_STATE_IME_SHOWING = 1 << 18; + public static final long SYSUI_STATE_IME_SHOWING = 1L << 18; // The window magnification is overlapped with system gesture insets at the bottom. - public static final int SYSUI_STATE_MAGNIFICATION_OVERLAP = 1 << 19; + public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19; // ImeSwitcher is showing - public static final int SYSUI_STATE_IME_SWITCHER_SHOWING = 1 << 20; + public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20; // Device dozing/AOD state - public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21; + public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21; // The home feature is disabled (either by SUW/SysUI/device policy) - public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22; + public static final long SYSUI_STATE_BACK_DISABLED = 1L << 22; // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it. - public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23; + public static final long SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1L << 23; // The voice interaction session window is showing - public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25; + public static final long SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1L << 25; // Freeform windows are showing in desktop mode - public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26; + public static final long SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1L << 26; // Device dreaming state - public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27; + public static final long SYSUI_STATE_DEVICE_DREAMING = 1L << 27; // Whether the device is currently awake (as opposed to asleep, see WakefulnessLifecycle). // Note that the device is awake on while waking up on, but not while going to sleep. - public static final int SYSUI_STATE_AWAKE = 1 << 28; + public static final long SYSUI_STATE_AWAKE = 1L << 28; // Whether the device is currently transitioning between awake/asleep indicated by // SYSUI_STATE_AWAKE. - public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29; + public static final long SYSUI_STATE_WAKEFULNESS_TRANSITION = 1L << 29; // The notification panel expansion fraction is > 0 - public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30; + public static final long SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1L << 30; // When keyguard will be dismissed but didn't start animation yet - public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1 << 31; + public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1L << 31; + // Physical keyboard shortcuts helper is showing + public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32; // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants - public static final int SYSUI_STATE_WAKEFULNESS_MASK = + public static final long SYSUI_STATE_WAKEFULNESS_MASK = SYSUI_STATE_AWAKE | SYSUI_STATE_WAKEFULNESS_TRANSITION; // Mirroring the WakefulnessLifecycle#Wakefulness states - public static final int WAKEFULNESS_ASLEEP = 0; - public static final int WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE; - public static final int WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION; - public static final int WAKEFULNESS_WAKING = + public static final long WAKEFULNESS_ASLEEP = 0; + public static final long WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE; + public static final long WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION; + public static final long WAKEFULNESS_WAKING = SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE; // Whether the back gesture is allowed (or ignored) by the Shade public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = shadeAllowBackGesture(); @Retention(RetentionPolicy.SOURCE) - @IntDef({SYSUI_STATE_SCREEN_PINNING, + @LongDef({SYSUI_STATE_SCREEN_PINNING, SYSUI_STATE_NAV_BAR_HIDDEN, SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED, SYSUI_STATE_QUICK_SETTINGS_EXPANDED, @@ -167,10 +169,11 @@ public class QuickStepContract { SYSUI_STATE_WAKEFULNESS_TRANSITION, SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY, + SYSUI_STATE_SHORTCUT_HELPER_SHOWING, }) public @interface SystemUiStateFlags {} - public static String getSystemUiStateString(int flags) { + public static String getSystemUiStateString(long flags) { StringJoiner str = new StringJoiner("|"); if ((flags & SYSUI_STATE_SCREEN_PINNING) != 0) { str.add("screen_pinned"); @@ -265,6 +268,9 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) != 0) { str.add("keygrd_going_away"); } + if ((flags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0) { + str.add("shortcut_helper_showing"); + } return str.toString(); } @@ -285,13 +291,13 @@ public class QuickStepContract { * Returns whether the specified sysui state is such that the assistant gesture should be * disabled. */ - public static boolean isAssistantGestureDisabled(int sysuiStateFlags) { + public static boolean isAssistantGestureDisabled(long sysuiStateFlags) { if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN; } // Disable when in quick settings, screen pinning, immersive, the bouncer is showing, // or search is disabled - int disableFlags = SYSUI_STATE_SCREEN_PINNING + long disableFlags = SYSUI_STATE_SCREEN_PINNING | SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_BOUNCER_SHOWING | SYSUI_STATE_SEARCH_DISABLED @@ -313,7 +319,7 @@ public class QuickStepContract { * Returns whether the specified sysui state is such that the back gesture should be * disabled. */ - public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) { + public static boolean isBackGestureDisabled(long sysuiStateFlags, boolean forTrackpad) { // Always allow when the bouncer/global actions/voice session is showing (even on top of // the keyguard) if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0 @@ -328,9 +334,9 @@ public class QuickStepContract { return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0; } - private static int getBackGestureDisabledMask(boolean forTrackpad) { + private static long getBackGestureDisabledMask(boolean forTrackpad) { // Disable when in immersive, or the notifications are interactive - int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; + long disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; if (!forTrackpad) { disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN; } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 460779c73cda..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 905a98c2e181..42838aeddd6b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -326,7 +326,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardTransitionInteractor.startDismissKeyguardTransition(); + mKeyguardTransitionInteractor.startDismissKeyguardTransition( + "KeyguardSecurityContainerController#finish"); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java index 177d933f2d9f..35c202437ed7 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java @@ -30,6 +30,8 @@ import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.SparseArray; import android.view.Display; import android.view.SurfaceControl; @@ -41,6 +43,8 @@ import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.window.InputTransferToken; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.CoreStartable; @@ -69,6 +73,9 @@ import javax.inject.Inject; public class Magnification implements CoreStartable, CommandQueue.Callbacks { private static final String TAG = "Magnification"; + @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300; + private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1; + private final ModeSwitchesController mModeSwitchesController; private final Context mContext; private final Handler mHandler; @@ -209,8 +216,26 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { SysUiState sysUiState, OverviewProxyService overviewProxyService, SecureSettings secureSettings, DisplayTracker displayTracker, DisplayManager displayManager, AccessibilityLogger a11yLogger) { + this(context, mainHandler.getLooper(), executor, commandQueue, + modeSwitchesController, sysUiState, overviewProxyService, secureSettings, + displayTracker, displayManager, a11yLogger); + } + + @VisibleForTesting + public Magnification(Context context, Looper looper, @Main Executor executor, + CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, + SysUiState sysUiState, OverviewProxyService overviewProxyService, + SecureSettings secureSettings, DisplayTracker displayTracker, + DisplayManager displayManager, AccessibilityLogger a11yLogger) { mContext = context; - mHandler = mainHandler; + mHandler = new Handler(looper) { + @Override + public void handleMessage(@NonNull Message msg) { + if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) { + showMagnificationButtonInternal(msg.arg1, msg.arg2); + } + } + }; mExecutor = executor; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mCommandQueue = commandQueue; @@ -350,6 +375,21 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { @MainThread void showMagnificationButton(int displayId, int magnificationMode) { + if (Flags.delayShowMagnificationButton()) { + if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) { + return; + } + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode), + DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS); + } else { + showMagnificationButtonInternal(displayId, magnificationMode); + } + } + + @MainThread + private void showMagnificationButtonInternal(int displayId, int magnificationMode) { // not to show mode switch button if settings panel is already showing to // prevent settings panel be covered by the button. if (isMagnificationSettingsPanelShowing(displayId)) { @@ -360,6 +400,9 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { @MainThread void removeMagnificationButton(int displayId) { + if (Flags.delayShowMagnificationButton()) { + mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL); + } mModeSwitchesController.removeButton(displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt new file mode 100644 index 000000000000..8f071e4bc874 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.model + +import java.time.LocalTime + +sealed interface NightDisplayChangeEvent { + data class OnAutoModeChanged(val autoMode: Int) : NightDisplayChangeEvent + data class OnActivatedChanged(val isActivated: Boolean) : NightDisplayChangeEvent + data class OnCustomStartTimeChanged(val startTime: LocalTime?) : NightDisplayChangeEvent + data class OnCustomEndTimeChanged(val endTime: LocalTime?) : NightDisplayChangeEvent + data class OnForceAutoModeChanged(val shouldForceAutoMode: Boolean) : NightDisplayChangeEvent + data class OnLocationEnabledChanged(val locationEnabled: Boolean) : NightDisplayChangeEvent +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt new file mode 100644 index 000000000000..196876e541b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.model + +import java.time.LocalTime + +/** models the state of NightDisplayRepository */ +data class NightDisplayState( + val autoMode: Int = 0, + val isActivated: Boolean = true, + val startTime: LocalTime? = null, + val endTime: LocalTime? = null, + val shouldForceAutoMode: Boolean = false, + val locationEnabled: Boolean = false, +) diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt index ae9f57f1f1b0..6032f0b7b618 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt @@ -18,7 +18,7 @@ package com.android.systemui.accessibility.data.repository import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import dagger.Module import dagger.Provides import kotlinx.coroutines.channels.awaitClose @@ -29,6 +29,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged interface AccessibilityRepository { /** @see [AccessibilityManager.isTouchExplorationEnabled] */ val isTouchExplorationEnabled: Flow<Boolean> + /** @see [AccessibilityManager.isEnabled] */ + val isEnabled: Flow<Boolean> companion object { operator fun invoke(a11yManager: AccessibilityManager): AccessibilityRepository = @@ -47,6 +49,15 @@ private class AccessibilityRepositoryImpl( awaitClose { manager.removeTouchExplorationStateChangeListener(listener) } } .distinctUntilChanged() + + override val isEnabled: Flow<Boolean> = + conflatedCallbackFlow { + val listener = AccessibilityManager.AccessibilityStateChangeListener(::trySend) + manager.addAccessibilityStateChangeListener(listener) + trySend(manager.isEnabled) + awaitClose { manager.removeAccessibilityStateChangeListener(listener) } + } + .distinctUntilChanged() } @Module diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt new file mode 100644 index 000000000000..bf44fabc31c4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.accessibility.data.model.NightDisplayChangeEvent +import com.android.systemui.accessibility.data.model.NightDisplayState +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.policy.LocationController +import com.android.systemui.user.utils.UserScopedService +import com.android.systemui.util.kotlin.isLocationEnabledFlow +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import java.time.LocalTime +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +class NightDisplayRepository +@Inject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, + private val globalSettings: GlobalSettings, + private val secureSettings: SecureSettings, + private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder, + private val colorDisplayManagerUserScopedService: UserScopedService<ColorDisplayManager>, + private val locationController: LocationController, +) { + private val stateFlowUserMap = mutableMapOf<Int, Flow<NightDisplayState>>() + + fun nightDisplayState(user: UserHandle): Flow<NightDisplayState> = + stateFlowUserMap.getOrPut(user.identifier) { + return merge( + colorDisplayManagerChangeEventFlow(user), + shouldForceAutoMode(user).map { + NightDisplayChangeEvent.OnForceAutoModeChanged(it) + }, + locationController.isLocationEnabledFlow().map { + NightDisplayChangeEvent.OnLocationEnabledChanged(it) + } + ) + .scan(initialState(user)) { state, event -> + when (event) { + is NightDisplayChangeEvent.OnActivatedChanged -> + state.copy(isActivated = event.isActivated) + is NightDisplayChangeEvent.OnAutoModeChanged -> + state.copy(autoMode = event.autoMode) + is NightDisplayChangeEvent.OnCustomStartTimeChanged -> + state.copy(startTime = event.startTime) + is NightDisplayChangeEvent.OnCustomEndTimeChanged -> + state.copy(endTime = event.endTime) + is NightDisplayChangeEvent.OnForceAutoModeChanged -> + state.copy(shouldForceAutoMode = event.shouldForceAutoMode) + is NightDisplayChangeEvent.OnLocationEnabledChanged -> + state.copy(locationEnabled = event.locationEnabled) + } + } + .conflate() + .onStart { emit(initialState(user)) } + .flowOn(bgCoroutineContext) + .stateIn(scope, SharingStarted.WhileSubscribed(), NightDisplayState()) + } + + /** Track changes in night display enabled state and its auto mode */ + private fun colorDisplayManagerChangeEventFlow(user: UserHandle) = callbackFlow { + val nightDisplayListener = nightDisplayListenerBuilder.setUser(user.identifier).build() + val nightDisplayCallback = + object : NightDisplayListener.Callback { + override fun onActivated(activated: Boolean) { + trySend(NightDisplayChangeEvent.OnActivatedChanged(activated)) + } + + override fun onAutoModeChanged(autoMode: Int) { + trySend(NightDisplayChangeEvent.OnAutoModeChanged(autoMode)) + } + + override fun onCustomStartTimeChanged(startTime: LocalTime?) { + trySend(NightDisplayChangeEvent.OnCustomStartTimeChanged(startTime)) + } + + override fun onCustomEndTimeChanged(endTime: LocalTime?) { + trySend(NightDisplayChangeEvent.OnCustomEndTimeChanged(endTime)) + } + } + nightDisplayListener.setCallback(nightDisplayCallback) + awaitClose { nightDisplayListener.setCallback(null) } + } + + /** @return true when the option to force auto mode is available and a value has not been set */ + private fun shouldForceAutoMode(userHandle: UserHandle): Flow<Boolean> = + combine(isForceAutoModeAvailable, isDisplayAutoModeRawNotSet(userHandle)) { + isForceAutoModeAvailable, + isDisplayAutoModeRawNotSet, + -> + isForceAutoModeAvailable && isDisplayAutoModeRawNotSet + } + + private val isForceAutoModeAvailable: Flow<Boolean> = + globalSettings + .observerFlow(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) + .onStart { emit(Unit) } + .map { + globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) == + NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE + } + .distinctUntilChanged() + + /** Inspired by [ColorDisplayService.getNightDisplayAutoModeRawInternal] */ + private fun isDisplayAutoModeRawNotSet(userHandle: UserHandle): Flow<Boolean> = + if (userHandle.identifier == UserHandle.USER_NULL) { + flowOf(IS_AUTO_MODE_RAW_NOT_SET_DEFAULT) + } else { + secureSettings + .observerFlow(userHandle.identifier, DISPLAY_AUTO_MODE_RAW_SETTING_NAME) + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser( + DISPLAY_AUTO_MODE_RAW_SETTING_NAME, + userHandle.identifier + ) == NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET + } + } + .distinctUntilChanged() + + suspend fun setNightDisplayAutoMode(autoMode: Int, user: UserHandle) { + withContext(bgCoroutineContext) { + colorDisplayManagerUserScopedService.forUser(user).nightDisplayAutoMode = autoMode + } + } + + suspend fun setNightDisplayActivated(activated: Boolean, user: UserHandle) { + withContext(bgCoroutineContext) { + colorDisplayManagerUserScopedService.forUser(user).isNightDisplayActivated = activated + } + } + + private fun initialState(user: UserHandle): NightDisplayState { + val colorDisplayManager = colorDisplayManagerUserScopedService.forUser(user) + return NightDisplayState( + colorDisplayManager.nightDisplayAutoMode, + colorDisplayManager.isNightDisplayActivated, + colorDisplayManager.nightDisplayCustomStartTime, + colorDisplayManager.nightDisplayCustomEndTime, + globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) == + NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE && + secureSettings.getIntForUser(DISPLAY_AUTO_MODE_RAW_SETTING_NAME, user.identifier) == + NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET, + locationController.isLocationEnabled, + ) + } + + private companion object { + const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1 + const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1" + const val IS_AUTO_MODE_RAW_NOT_SET_DEFAULT = true + const val IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME = + Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE + const val DISPLAY_AUTO_MODE_RAW_SETTING_NAME = Settings.Secure.NIGHT_DISPLAY_AUTO_MODE + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt index 968ce0dc8cb0..93b624a310b4 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt @@ -28,6 +28,8 @@ constructor( private val a11yRepo: AccessibilityRepository, ) { /** @see [android.view.accessibility.AccessibilityManager.isTouchExplorationEnabled] */ - val isTouchExplorationEnabled: Flow<Boolean> - get() = a11yRepo.isTouchExplorationEnabled + val isTouchExplorationEnabled: Flow<Boolean> = a11yRepo.isTouchExplorationEnabled + + /** @see [android.view.accessibility.AccessibilityManager.isEnabled] */ + val isEnabled: Flow<Boolean> = a11yRepo.isEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt index 54dd6d00aa48..ed9597ddf559 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt @@ -41,6 +41,10 @@ import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMap import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel +import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileDataInteractor +import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.qs.tiles.impl.night.ui.NightDisplayTileMapper import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel @@ -117,6 +121,7 @@ interface QSAccessibilityModule { const val FONT_SCALING_TILE_SPEC = "font_scaling" const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness" const val ONE_HANDED_TILE_SPEC = "onehanded" + const val NIGHT_DISPLAY_TILE_SPEC = "night" @Provides @IntoMap @@ -279,5 +284,41 @@ interface QSAccessibilityModule { mapper, ) else StubQSTileViewModel + + @Provides + @IntoMap + @StringKey(NIGHT_DISPLAY_TILE_SPEC) + fun provideNightDisplayTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(NIGHT_DISPLAY_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_nightlight_icon_off, + labelRes = R.string.quick_settings_night_display_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** + * Inject NightDisplay Tile into tileViewModelMap in QSModule. The tile is hidden behind a + * flag. + */ + @Provides + @IntoMap + @StringKey(NIGHT_DISPLAY_TILE_SPEC) + fun provideNightDisplayTileViewModel( + factory: QSTileViewModelFactory.Static<NightDisplayTileModel>, + mapper: NightDisplayTileMapper, + stateInteractor: NightDisplayTileDataInteractor, + userActionInteractor: NightDisplayTileUserActionInteractor + ): QSTileViewModel = + if (Flags.qsNewTilesFuture()) + factory.create( + TileSpec.create(NIGHT_DISPLAY_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + else StubQSTileViewModel } } diff --git a/packages/SystemUI/src/com/android/systemui/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/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 5ba0b2df7664..298c0f782b79 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -73,9 +73,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.biometrics.AuthController.ScaleFactorProvider; -import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; import com.android.systemui.biometrics.shared.model.BiometricModalities; +import com.android.systemui.biometrics.shared.model.PromptKind; import com.android.systemui.biometrics.ui.BiometricPromptLayout; import com.android.systemui.biometrics.ui.CredentialView; import com.android.systemui.biometrics.ui.binder.BiometricViewBinder; @@ -149,7 +149,6 @@ public class AuthContainerView extends LinearLayout private final CoroutineScope mApplicationCoroutineScope; // TODO(b/287311775): these should be migrated out once ready - private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor; private final @NonNull Provider<PromptSelectorInteractor> mPromptSelectorInteractorProvider; // TODO(b/287311775): these should be migrated out of the view private final Provider<CredentialViewModel> mCredentialViewModelProvider; @@ -310,7 +309,6 @@ public class AuthContainerView extends LinearLayout @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, - @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractor, @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor, @NonNull PromptViewModel promptViewModel, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @@ -318,7 +316,7 @@ public class AuthContainerView extends LinearLayout @NonNull VibratorHelper vibratorHelper) { this(config, applicationCoroutineScope, fpProps, faceProps, wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, - jankMonitor, promptSelectorInteractor, promptCredentialInteractor, promptViewModel, + jankMonitor, promptSelectorInteractor, promptViewModel, credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor, vibratorHelper); } @@ -334,7 +332,6 @@ public class AuthContainerView extends LinearLayout @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider, - @NonNull Provider<PromptCredentialInteractor> credentialInteractor, @NonNull PromptViewModel promptViewModel, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @NonNull Handler mainHandler, @@ -357,13 +354,19 @@ public class AuthContainerView extends LinearLayout mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; mBiometricCallback = new BiometricCallback(); + mFpProps = fpProps; + mFaceProps = faceProps; + final BiometricModalities biometricModalities = new BiometricModalities( + Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), + Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds)); + mPromptSelectorInteractorProvider = promptSelectorInteractorProvider; - mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential( - config.mPromptInfo); + mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mEffectiveUserId, + biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName, + false /*onSwitchToCredential*/); final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - if (constraintBp() && (Utils.isBiometricAllowed(config.mPromptInfo) - || mPromptViewModel.getShowBpWithoutIconForCredential().getValue())) { + if (constraintBp() && mPromptViewModel.getPromptKind().getValue().isBiometric()) { mLayout = (ConstraintLayout) layoutInflater.inflate( R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */); } else { @@ -397,10 +400,7 @@ public class AuthContainerView extends LinearLayout mPanelController = new AuthPanelController(mContext, mPanelView); mBackgroundExecutor = bgExecutor; mInteractionJankMonitor = jankMonitor; - mPromptCredentialInteractor = credentialInteractor; mCredentialViewModelProvider = credentialViewModelProvider; - mFpProps = fpProps; - mFaceProps = faceProps; showPrompt(config, layoutInflater, promptViewModel, Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), @@ -429,11 +429,12 @@ public class AuthContainerView extends LinearLayout @Nullable FaceSensorPropertiesInternal faceProps, @NonNull VibratorHelper vibratorHelper ) { - if (Utils.isBiometricAllowed(config.mPromptInfo) - || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) { + if (mPromptViewModel.getPromptKind().getValue().isBiometric()) { addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper); - } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { - addCredentialView(true, false); + } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) { + if (constraintBp()) { + addCredentialView(true, false); + } } else { mPromptSelectorInteractorProvider.get().resetPrompt(); } @@ -444,12 +445,6 @@ public class AuthContainerView extends LinearLayout @Nullable FingerprintSensorPropertiesInternal fpProps, @Nullable FaceSensorPropertiesInternal faceProps, @NonNull VibratorHelper vibratorHelper) { - mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication( - config.mPromptInfo, - config.mUserId, - config.mOperationId, - new BiometricModalities(fpProps, faceProps), - config.mOpPackageName); if (constraintBp()) { mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null, @@ -500,34 +495,24 @@ public class AuthContainerView extends LinearLayout private void addCredentialView(boolean animatePanel, boolean animateContents) { final LayoutInflater factory = LayoutInflater.from(mContext); - @Utils.CredentialType final int credentialType = Utils.getCredentialType( - mLockPatternUtils, mEffectiveUserId); - - switch (credentialType) { - case Utils.CREDENTIAL_PATTERN: - mCredentialView = factory.inflate( - R.layout.auth_credential_pattern_view, null, false); - break; - case Utils.CREDENTIAL_PIN: - mCredentialView = factory.inflate(R.layout.auth_credential_pin_view, null, false); - break; - case Utils.CREDENTIAL_PASSWORD: - mCredentialView = factory.inflate( - R.layout.auth_credential_password_view, null, false); - break; - default: - throw new IllegalStateException("Unknown credential type: " + credentialType); + PromptKind credentialType = Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId); + final int layoutResourceId; + if (credentialType instanceof PromptKind.Pattern) { + layoutResourceId = R.layout.auth_credential_pattern_view; + } else if (credentialType instanceof PromptKind.Pin) { + layoutResourceId = R.layout.auth_credential_pin_view; + } else if (credentialType instanceof PromptKind.Password) { + layoutResourceId = R.layout.auth_credential_password_view; + } else { + throw new IllegalStateException("Unknown credential type: " + credentialType); } + mCredentialView = factory.inflate(layoutResourceId, null, false); // The background is used for detecting taps / cancelling authentication. Since the // credential view is full-screen and should not be canceled from background taps, // disable it. mBackgroundView.setOnClickListener(null); mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - - mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication( - mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId, - mConfig.mOpPackageName); final CredentialViewModel vm = mCredentialViewModelProvider.get(); vm.setAnimateContents(animateContents); ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel, @@ -562,10 +547,9 @@ public class AuthContainerView extends LinearLayout () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)); if (constraintBp()) { // Do nothing on attachment with constraintLayout - } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo) - || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) { + } else if (mPromptViewModel.getPromptKind().getValue().isBiometric()) { mBiometricScrollView.addView(mBiometricView.asView()); - } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { + } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) { addCredentialView(true /* animatePanel */, false /* animateContents */); } else { throw new IllegalStateException("Unknown configuration: " diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index ca88d40df6af..d6d40f28d288 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -69,7 +69,6 @@ import com.android.internal.os.SomeArgs; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.CoreStartable; import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; -import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; @@ -139,7 +138,6 @@ public class AuthController implements private Job mBiometricContextListenerJob = null; // TODO: these should be migrated out once ready - @NonNull private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor; @NonNull private final Provider<PromptSelectorInteractor> mPromptSelectorInteractor; @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider; @NonNull private final Provider<PromptViewModel> mPromptViewModelProvider; @@ -735,7 +733,6 @@ public class AuthController implements @NonNull LockPatternUtils lockPatternUtils, @NonNull Lazy<UdfpsLogger> udfpsLogger, @NonNull Lazy<LogContextInteractor> logContextInteractor, - @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractorProvider, @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @NonNull Provider<PromptViewModel> promptViewModelProvider, @@ -768,7 +765,6 @@ public class AuthController implements mLogContextInteractor = logContextInteractor; mPromptSelectorInteractor = promptSelectorInteractorProvider; - mPromptCredentialInteractor = promptCredentialInteractorProvider; mPromptViewModelProvider = promptViewModelProvider; mCredentialViewModelProvider = credentialViewModelProvider; @@ -1253,6 +1249,8 @@ public class AuthController implements } mCurrentDialog = newDialog; + // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be + // removed. if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) { cancelIfOwnerIsNotInForeground(); } else { @@ -1316,8 +1314,8 @@ public class AuthController implements config.mScaleProvider = this::getScaleFactor; return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps, wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, - mInteractionJankMonitor, mPromptCredentialInteractor, mPromptSelectorInteractor, - viewModel, mCredentialViewModelProvider, bgExecutor, mVibratorHelper); + mInteractionJankMonitor, mPromptSelectorInteractor, viewModel, + mCredentialViewModelProvider, bgExecutor, mVibratorHelper); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/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/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt index 9ad3f4313838..58b238b820c3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt @@ -16,12 +16,8 @@ package com.android.systemui.biometrics.data.repository -import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo -import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.AuthController -import com.android.systemui.biometrics.Utils -import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -56,8 +52,8 @@ interface PromptRepository { /** The gatekeeper challenge, if one is associated with this prompt. */ val challenge: StateFlow<Long?> - /** The kind of credential to use (biometric, pin, pattern, etc.). */ - val kind: StateFlow<PromptKind> + /** The kind of prompt to use (biometric, pin, pattern, etc.). */ + val promptKind: StateFlow<PromptKind> /** The package name that the prompt is called from. */ val opPackageName: StateFlow<String?> @@ -69,18 +65,6 @@ interface PromptRepository { */ val isConfirmationRequired: Flow<Boolean> - /** - * If biometric prompt without icon needs to show for displaying content prior to credential - * view. - */ - val showBpWithoutIconForCredential: StateFlow<Boolean> - - /** - * Update whether biometric prompt without icon needs to show for displaying content prior to - * credential view, which should be set before [setPrompt]. - */ - fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) - /** Update the prompt configuration, which should be set before [isShowing]. */ fun setPrompt( promptInfo: PromptInfo, @@ -125,8 +109,8 @@ constructor( private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null) override val userId = _userId.asStateFlow() - private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric()) - override val kind = _kind.asStateFlow() + private val _promptKind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.None) + override val promptKind = _promptKind.asStateFlow() private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) override val opPackageName = _opPackageName.asStateFlow() @@ -145,21 +129,6 @@ constructor( } .distinctUntilChanged() - private val _showBpWithoutIconForCredential: MutableStateFlow<Boolean> = MutableStateFlow(false) - override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow() - - override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { - val hasCredentialViewShown = kind.value !is PromptKind.Biometric - val showBpForCredential = - Flags.customBiometricPrompt() && - constraintBp() && - !Utils.isBiometricAllowed(promptInfo) && - isDeviceCredentialAllowed(promptInfo) && - promptInfo.contentView != null && - !promptInfo.isContentViewMoreOptionsButtonUsed - _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown - } - override fun setPrompt( promptInfo: PromptInfo, userId: Int, @@ -167,7 +136,7 @@ constructor( kind: PromptKind, opPackageName: String, ) { - _kind.value = kind + _promptKind.value = kind _userId.value = userId _challenge.value = gatekeeperChallenge _promptInfo.value = promptInfo @@ -178,7 +147,7 @@ constructor( _promptInfo.value = null _userId.value = null _challenge.value = null - _kind.value = PromptKind.Biometric() + _promptKind.value = PromptKind.None _opPackageName.value = null } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt index b7c0fa802db3..14ba8a22a65e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt @@ -16,10 +16,8 @@ package com.android.systemui.biometrics.domain.interactor -import android.hardware.biometrics.PromptInfo import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.data.repository.PromptRepository import com.android.systemui.biometrics.domain.model.BiometricOperationInfo import com.android.systemui.biometrics.domain.model.BiometricPromptRequest @@ -42,12 +40,6 @@ import kotlinx.coroutines.withContext * Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users * PIN, pattern, or password credential instead of a biometric. * - * This is used to cache the calling app's options that were given to the underlying authenticate - * APIs and should be set before any UI is shown to the user. - * - * There can be at most one request active at a given time. Use [resetPrompt] when no request is - * active to clear the cache. - * * Views that use any biometric should use [PromptSelectorInteractor] instead. */ class PromptCredentialInteractor @@ -74,13 +66,13 @@ constructor( biometricPromptRepository.promptInfo, biometricPromptRepository.challenge, biometricPromptRepository.userId, - biometricPromptRepository.kind - ) { promptInfo, challenge, userId, kind -> + biometricPromptRepository.promptKind + ) { promptInfo, challenge, userId, promptKind -> if (promptInfo == null || userId == null || challenge == null) { return@combine null } - when (kind) { + when (promptKind) { PromptKind.Pin -> BiometricPromptRequest.Credential.Pin( info = promptInfo, @@ -137,28 +129,6 @@ constructor( private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null) val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow() - /** Update the current request to use credential-based authentication instead of biometrics. */ - fun useCredentialsForAuthentication( - promptInfo: PromptInfo, - @Utils.CredentialType kind: Int, - userId: Int, - challenge: Long, - opPackageName: String, - ) { - biometricPromptRepository.setPrompt( - promptInfo, - userId, - challenge, - kind.asBiometricPromptCredential(), - opPackageName, - ) - } - - /** Unset the current authentication request. */ - fun resetPrompt() { - biometricPromptRepository.unsetPrompt() - } - /** * Check a credential and return the attestation token (HAT) if successful. * @@ -231,13 +201,3 @@ constructor( _verificationError.value = null } } - -// TODO(b/251476085): remove along with Utils.CredentialType -/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */ -private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind = - when (this) { - Utils.CREDENTIAL_PIN -> PromptKind.Pin - Utils.CREDENTIAL_PASSWORD -> PromptKind.Password - Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern - else -> PromptKind.Biometric() - } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index 45816c12281e..4ba780fcec69 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics.domain.interactor +import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo import com.android.internal.widget.LockPatternUtils import com.android.systemui.biometrics.Utils @@ -53,12 +54,16 @@ interface PromptSelectorInteractor { /** Static metadata about the current prompt. */ val prompt: Flow<BiometricPromptRequest.Biometric?> + /** The kind of prompt to use (biometric, pin, pattern, etc.). */ + val promptKind: StateFlow<PromptKind> + /** If using a credential is allowed. */ val isCredentialAllowed: Flow<Boolean> /** - * The kind of credential the user may use as a fallback or [PromptKind.Biometric] if unknown or - * not [isCredentialAllowed]. + * The kind of credential the user may use as a fallback or [PromptKind.None] if unknown or not + * [isCredentialAllowed]. This is separate from [promptKind], even if [promptKind] is + * [PromptKind.Biometric], [credentialKind] should still be one of pin/pattern/password. */ val credentialKind: Flow<PromptKind> @@ -71,34 +76,20 @@ interface PromptSelectorInteractor { /** Fingerprint sensor type */ val sensorType: Flow<FingerprintSensorType> - /** - * If biometric prompt without icon needs to show for displaying content prior to credential - * view. - */ - val showBpWithoutIconForCredential: StateFlow<Boolean> + /** Switch to the credential view. */ + fun onSwitchToCredential() /** - * Update whether biometric prompt without icon needs to show for displaying content prior to - * credential view, which should be set before [PromptRepository.setPrompt]. + * Update the kind of prompt (biometric prompt w/ or w/o sensor icon, pin view, pattern view, + * etc). */ - fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) - - /** Use biometrics for authentication. */ - fun useBiometricsForAuthentication( + fun setPrompt( promptInfo: PromptInfo, - userId: Int, - challenge: Long, + effectiveUserId: Int, modalities: BiometricModalities, - opPackageName: String, - ) - - /** Use credential-based authentication instead of biometrics. */ - fun useCredentialsForAuthentication( - promptInfo: PromptInfo, - @Utils.CredentialType kind: Int, - userId: Int, challenge: Long, opPackageName: String, + onSwitchToCredential: Boolean, ) /** Unset the current authentication request. */ @@ -111,7 +102,7 @@ class PromptSelectorInteractorImpl constructor( fingerprintPropertyRepository: FingerprintPropertyRepository, private val promptRepository: PromptRepository, - lockPatternUtils: LockPatternUtils, + private val lockPatternUtils: LockPatternUtils, ) : PromptSelectorInteractor { override val prompt: Flow<BiometricPromptRequest.Biometric?> = @@ -119,7 +110,7 @@ constructor( promptRepository.promptInfo, promptRepository.challenge, promptRepository.userId, - promptRepository.kind, + promptRepository.promptKind, promptRepository.opPackageName, ) { promptInfo, challenge, userId, kind, opPackageName -> if ( @@ -141,6 +132,8 @@ constructor( } } + override val promptKind: StateFlow<PromptKind> = promptRepository.promptKind + override val isConfirmationRequired: Flow<Boolean> = promptRepository.isConfirmationRequired.distinctUntilChanged() @@ -152,55 +145,61 @@ constructor( override val credentialKind: Flow<PromptKind> = combine(prompt, isCredentialAllowed) { prompt, isAllowed -> if (prompt != null && isAllowed) { - when ( - getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId) - ) { - Utils.CREDENTIAL_PIN -> PromptKind.Pin - Utils.CREDENTIAL_PASSWORD -> PromptKind.Password - Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern - else -> PromptKind.Biometric() - } + getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId) } else { - PromptKind.Biometric() + PromptKind.None } } override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType - override val showBpWithoutIconForCredential = promptRepository.showBpWithoutIconForCredential - - override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { - promptRepository.setShouldShowBpWithoutIconForCredential(promptInfo) - } - - override fun useBiometricsForAuthentication( - promptInfo: PromptInfo, - userId: Int, - challenge: Long, - modalities: BiometricModalities, - opPackageName: String, - ) { - promptRepository.setPrompt( - promptInfo = promptInfo, - userId = userId, - gatekeeperChallenge = challenge, - kind = PromptKind.Biometric(modalities), - opPackageName = opPackageName, + override fun onSwitchToCredential() { + val modalities: BiometricModalities = + if (promptRepository.promptKind.value.isBiometric()) + (promptRepository.promptKind.value as PromptKind.Biometric).activeModalities + else BiometricModalities() + setPrompt( + promptRepository.promptInfo.value!!, + promptRepository.userId.value!!, + modalities, + promptRepository.challenge.value!!, + promptRepository.opPackageName.value!!, + true /*onSwitchToCredential*/ ) } - override fun useCredentialsForAuthentication( + override fun setPrompt( promptInfo: PromptInfo, - @Utils.CredentialType kind: Int, - userId: Int, + effectiveUserId: Int, + modalities: BiometricModalities, challenge: Long, opPackageName: String, + onSwitchToCredential: Boolean, ) { + val hasCredentialViewShown = promptKind.value.isCredential() + val showBpForCredential = + Flags.customBiometricPrompt() && + com.android.systemui.Flags.constraintBp() && + !Utils.isBiometricAllowed(promptInfo) && + isDeviceCredentialAllowed(promptInfo) && + promptInfo.contentView != null && + !promptInfo.isContentViewMoreOptionsButtonUsed + val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown + var kind: PromptKind = PromptKind.None + if (onSwitchToCredential) { + kind = getCredentialType(lockPatternUtils, effectiveUserId) + } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) { + // TODO(b/330908557): check to show one pane or two pane + kind = PromptKind.Biometric(modalities) + } else if (isDeviceCredentialAllowed(promptInfo)) { + kind = getCredentialType(lockPatternUtils, effectiveUserId) + } + promptRepository.setPrompt( promptInfo = promptInfo, - userId = userId, + userId = effectiveUserId, gatekeeperChallenge = challenge, - kind = kind.asBiometricPromptCredential(), + kind = kind, opPackageName = opPackageName, ) } @@ -209,13 +208,3 @@ constructor( promptRepository.unsetPrompt() } } - -// TODO(b/251476085): remove along with Utils.CredentialType -/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */ -private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind = - when (this) { - Utils.CREDENTIAL_PIN -> PromptKind.Pin - Utils.CREDENTIAL_PASSWORD -> PromptKind.Password - Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern - else -> PromptKind.Biometric() - } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt index 6f079e2d3b3a..4f96c1e03d11 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt @@ -1,5 +1,6 @@ package com.android.systemui.biometrics.domain.model +import android.content.ComponentName import android.graphics.Bitmap import android.hardware.biometrics.PromptContentView import android.hardware.biometrics.PromptInfo @@ -43,6 +44,9 @@ sealed class BiometricPromptRequest( val logoBitmap: Bitmap? = info.logoBitmap val logoDescription: String? = info.logoDescription val negativeButtonText: String = info.negativeButtonText?.toString() ?: "" + val componentNameForConfirmDeviceCredentialActivity: ComponentName? = + info.componentNameForConfirmDeviceCredentialActivity + val allowBackgroundAuthentication = info.isAllowBackgroundAuthentication } /** Prompt using a credential (pin, pattern, password). */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index da5695163cb4..65c5b6b3859c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -230,7 +230,7 @@ object BiometricViewBinder { } lifecycleScope.launch { - viewModel.showBpWithoutIconForCredential.collect { showWithoutIcon -> + viewModel.hideSensorIcon.collect { showWithoutIcon -> if (!showWithoutIcon) { PromptIconViewBinder.bind( iconView, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index d1ad783c7cc8..f0969eda4029 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -55,6 +55,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import kotlin.math.abs import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** Helper for [BiometricViewBinder] to handle resize transitions. */ @@ -169,14 +170,14 @@ object BiometricViewSizeBinder { val flipConstraintSet = ConstraintSet() view.doOnLayout { - fun setVisibilities(size: PromptSize) { + fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) { viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) } largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) largeConstraintSet.setVisibility(R.id.indicator, View.GONE) largeConstraintSet.setVisibility(R.id.scrollView, View.GONE) - if (viewModel.showBpWithoutIconForCredential.value) { + if (hideSensorIcon) { smallConstraintSet.setVisibility(iconHolderView.id, View.GONE) smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) smallConstraintSet.setVisibility(R.id.indicator, View.GONE) @@ -362,12 +363,16 @@ object BiometricViewSizeBinder { } } } + lifecycleScope.launch { + combine(viewModel.hideSensorIcon, viewModel.size, ::Pair).collect { + (hideSensorIcon, size) -> + setVisibilities(hideSensorIcon, size) + } + } lifecycleScope.launch { combine(viewModel.position, viewModel.size, ::Pair).collect { (position, size) -> - setVisibilities(size) - if (position.isLeft) { if (size.isSmall) { flipConstraintSet.clone(smallConstraintSet) @@ -481,7 +486,7 @@ object BiometricViewSizeBinder { v.showContentOrHide(forceHide = size.isSmall) } - if (viewModel.showBpWithoutIconForCredential.value) { + if (viewModel.hideSensorIcon.first()) { iconHolderView.visibility = View.GONE } @@ -492,10 +497,6 @@ object BiometricViewSizeBinder { viewsToFadeInOnSizeChange.forEach { it.alpha = 0f } } - // TODO(b/302735104): Fix wrong height due to the delay of - // PromptContentView. addOnLayoutChangeListener() will cause crash - // when showing credential view, since |PromptIconViewModel| won't - // release the flow. // propagate size changes to legacy panel controller and animate // transitions view.doOnLayout { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 2104f3e1fba6..156ec6b975a5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -16,7 +16,10 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.app.ActivityTaskManager +import android.content.ComponentName import android.content.Context +import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.Rect @@ -26,18 +29,22 @@ import android.hardware.biometrics.BiometricFingerprintConstants import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.biometrics.PromptContentView +import android.os.UserHandle import android.util.Log import android.util.RotationUtils import android.view.HapticFeedbackConstants import android.view.MotionEvent +import com.android.launcher3.icons.IconProvider import com.android.systemui.Flags.bpTalkback import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.Utils.isSystem import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor +import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.DisplayRotation @@ -67,11 +74,13 @@ class PromptViewModel @Inject constructor( displayStateInteractor: DisplayStateInteractor, - promptSelectorInteractor: PromptSelectorInteractor, + private val promptSelectorInteractor: PromptSelectorInteractor, @Application private val context: Context, private val udfpsOverlayInteractor: UdfpsOverlayInteractor, private val biometricStatusInteractor: BiometricStatusInteractor, - private val udfpsUtils: UdfpsUtils + private val udfpsUtils: UdfpsUtils, + private val iconProvider: IconProvider, + private val activityTaskManager: ActivityTaskManager, ) { /** The set of modalities available for this prompt */ val modalities: Flow<BiometricModalities> = @@ -195,8 +204,11 @@ constructor( /** The kind of credential the user has. */ val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind - val showBpWithoutIconForCredential: StateFlow<Boolean> = - promptSelectorInteractor.showBpWithoutIconForCredential + /** The kind of prompt to use (biometric, pin, pattern, etc.). */ + val promptKind: StateFlow<PromptKind> = promptSelectorInteractor.promptKind + + /** Whether the sensor icon on biometric prompt ui should be hidden. */ + val hideSensorIcon: Flow<Boolean> = modalities.map { it.isEmpty }.distinctUntilChanged() /** The label to use for the cancel button. */ val negativeButtonText: Flow<String> = @@ -496,14 +508,7 @@ constructor( !(customBiometricPrompt() && constraintBp()) || it == null -> null it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme) it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap) - else -> - try { - val info = context.getApplicationInfo(it.opPackageName) - context.packageManager.getApplicationIcon(info) - } catch (e: Exception) { - Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e) - null - } + else -> context.getUserBadgedIcon(it, iconProvider, activityTaskManager) } } .distinctUntilChanged() @@ -514,15 +519,8 @@ constructor( .map { when { !(customBiometricPrompt() && constraintBp()) || it == null -> "" - it.logoDescription != null -> it.logoDescription - else -> - try { - val info = context.getApplicationInfo(it.opPackageName) - context.packageManager.getApplicationLabel(info).toString() - } catch (e: Exception) { - Log.w(TAG, "Cannot find name for package " + it.opPackageName, e) - "" - } + !it.logoDescription.isNullOrEmpty() -> it.logoDescription + else -> context.getUserBadgedLabel(it, activityTaskManager) } } .distinctUntilChanged() @@ -896,6 +894,7 @@ constructor( */ fun onSwitchToCredential() { _forceLargeSize.value = true + promptSelectorInteractor.onSwitchToCredential() } private fun vibrateOnSuccess() { @@ -922,15 +921,109 @@ constructor( } companion object { - private const val TAG = "PromptViewModel" + const val TAG = "PromptViewModel" } } -private fun Context.getApplicationInfo(packageName: String): ApplicationInfo = - packageManager.getApplicationInfo( - packageName, - PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER - ) +private fun Context.getUserBadgedIcon( + prompt: BiometricPromptRequest.Biometric, + iconProvider: IconProvider, + activityTaskManager: ActivityTaskManager +): Drawable? { + var icon: Drawable? = null + val componentName = prompt.getComponentNameForLogo(activityTaskManager) + if (componentName != null && shouldShowLogoWithOverrides(componentName)) { + val activityInfo = getActivityInfo(componentName) + icon = if (activityInfo == null) null else iconProvider.getIcon(activityInfo) + } + if (icon == null) { + val appInfo = prompt.getApplicationInfoForLogo(this, componentName) + if (appInfo == null) { + Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName") + return null + } else { + icon = packageManager.getApplicationIcon(appInfo) + } + } + return packageManager.getUserBadgedIcon(icon, UserHandle.of(prompt.userInfo.userId)) +} + +private fun Context.getUserBadgedLabel( + prompt: BiometricPromptRequest.Biometric, + activityTaskManager: ActivityTaskManager +): String { + val componentName = prompt.getComponentNameForLogo(activityTaskManager) + val appInfo = prompt.getApplicationInfoForLogo(this, componentName) + return if (appInfo == null || packageManager.getApplicationLabel(appInfo).isNullOrEmpty()) { + Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName") + "" + } else { + packageManager + .getUserBadgedLabel(packageManager.getApplicationLabel(appInfo), UserHandle.of(userId)) + .toString() + } +} + +private fun BiometricPromptRequest.Biometric.getComponentNameForLogo( + activityTaskManager: ActivityTaskManager +): ComponentName? { + val topActivity: ComponentName? = activityTaskManager.getTasks(1).firstOrNull()?.topActivity + return when { + componentNameForConfirmDeviceCredentialActivity != null -> + componentNameForConfirmDeviceCredentialActivity + topActivity?.packageName.contentEquals(opPackageName) -> topActivity + else -> { + Log.w(PromptViewModel.TAG, "Top activity $topActivity is not the client $opPackageName") + null + } + } +} + +private fun BiometricPromptRequest.Biometric.getApplicationInfoForLogo( + context: Context, + componentNameForLogo: ComponentName? +): ApplicationInfo? { + val packageName = + when { + componentNameForLogo != null -> componentNameForLogo.packageName + // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be + // removed. + // This is being consistent with the check in [AuthController.showDialog()]. + allowBackgroundAuthentication || isSystem(context, opPackageName) -> opPackageName + else -> null + } + return if (packageName == null) { + Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName") + null + } else { + context.getApplicationInfo(packageName) + } +} + +private fun Context.shouldShowLogoWithOverrides(componentName: ComponentName): Boolean { + return resources + .getStringArray(R.array.biometric_dialog_package_names_for_logo_with_overrides) + .find { componentName.packageName.contentEquals(it) } != null +} + +private fun Context.getActivityInfo(componentName: ComponentName): ActivityInfo? = + try { + packageManager.getActivityInfo(componentName, 0) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(PromptViewModel.TAG, "Cannot find activity info for $opPackageName", e) + null + } + +private fun Context.getApplicationInfo(packageName: String): ApplicationInfo? = + try { + packageManager.getApplicationInfo( + packageName, + PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER + ) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e) + null + } /** How the fingerprint sensor was started for the prompt. */ enum class FingerprintStartMode { diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt index dd8c0df387dc..f0230beaa967 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt @@ -204,11 +204,7 @@ internal constructor( isEnabled: Boolean, @StringRes infoResId: Int ) { - getAutoOnToggle(dialog).apply { - isChecked = isEnabled - setEnabled(true) - alpha = ENABLED_ALPHA - } + getAutoOnToggle(dialog).isChecked = isEnabled getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId) } @@ -236,12 +232,8 @@ internal constructor( } getAutoOnToggleView(dialog).visibility = initialUiProperties.autoOnToggleVisibility - getAutoOnToggle(dialog).setOnCheckedChangeListener { view, isChecked -> + getAutoOnToggle(dialog).setOnCheckedChangeListener { _, isChecked -> mutableBluetoothAutoOnToggle.value = isChecked - view.apply { - isEnabled = false - alpha = DISABLED_ALPHA - } uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUTO_ON_TOGGLE_CLICKED) } } diff --git a/packages/SystemUI/src/com/android/systemui/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 b42a903878a7..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); @@ -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"); @@ -302,7 +330,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onTouchEvent(MotionEvent ev) { logDebug("REAL: onTouchEvent(" + MotionEvent.actionToString(ev.getActionMasked()) + ")"); - if (!mKeyguardStateController.isShowing()) { + if (!isKeyguardShowing()) { avoidGesture(); return; } @@ -399,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() { @@ -446,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/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java index 8efc66de24cd..ba236ba016ff 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java @@ -66,11 +66,11 @@ 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 java.util.ArrayList; - import kotlin.Unit; import kotlin.jvm.functions.Function0; +import java.util.ArrayList; + /** * Handles the visual elements and animations for the clipboard overlay. */ @@ -109,6 +109,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { private View mDismissButton; private LinearLayout mActionContainer; private ClipboardOverlayCallbacks mClipboardCallbacks; + private ActionButtonViewBinder mActionButtonViewBinder = new ActionButtonViewBinder(); public ClipboardOverlayView(Context context) { this(context, null); @@ -152,14 +153,15 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { private void bindDefaultActionChips() { if (screenshotShelfUi2()) { - ActionButtonViewBinder.INSTANCE.bind(mRemoteCopyChip, + 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)), + mContext.getString(R.string.clipboard_send_nearby_description), + true), new Function0<>() { @Override public Unit invoke() { @@ -169,12 +171,14 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { return null; } })); - ActionButtonViewBinder.INSTANCE.bind(mShareChip, + 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)), + null, + mContext.getString(com.android.internal.R.string.share), + true), new Function0<>() { @Override public Unit invoke() { @@ -512,9 +516,9 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { private View constructShelfActionChip(RemoteAction action, Runnable onFinish) { View chip = LayoutInflater.from(mContext).inflate( R.layout.shelf_action_chip, mActionContainer, false); - ActionButtonViewBinder.INSTANCE.bind(chip, ActionButtonViewModel.Companion.withNextId( + mActionButtonViewBinder.bind(chip, ActionButtonViewModel.Companion.withNextId( new ActionButtonAppearance(action.getIcon().loadDrawable(mContext), - action.getTitle(), action.getTitle()), new Function0<>() { + action.getTitle(), action.getTitle(), false), new Function0<>() { @Override public Unit invoke() { try { diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt index 5c64dc645283..1c1642905d7d 100644 --- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.common.data.repository import android.os.UserHandle import com.android.systemui.common.shared.model.PackageChangeModel +import com.android.systemui.common.shared.model.PackageInstallSession import kotlinx.coroutines.flow.Flow interface PackageChangeRepository { @@ -28,4 +29,7 @@ interface PackageChangeRepository { * [UserHandle.USER_ALL] may be used to listen to all users. */ fun packageChanged(user: UserHandle): Flow<PackageChangeModel> + + /** Emits a list of all known install sessions associated with the primary user. */ + val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> } diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt index 712a3527b7c9..41b03f1f3de6 100644 --- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt @@ -18,6 +18,7 @@ package com.android.systemui.common.data.repository import android.os.UserHandle import com.android.systemui.common.shared.model.PackageChangeModel +import com.android.systemui.common.shared.model.PackageInstallSession import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -27,6 +28,7 @@ import kotlinx.coroutines.flow.filter class PackageChangeRepositoryImpl @Inject constructor( + packageInstallerMonitor: PackageInstallerMonitor, private val monitorFactory: PackageUpdateMonitor.Factory, ) : PackageChangeRepository { /** @@ -37,4 +39,7 @@ constructor( override fun packageChanged(user: UserHandle): Flow<PackageChangeModel> = monitor.packageChanged.filter { user == UserHandle.ALL || user == it.user } + + override val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> = + packageInstallerMonitor.installSessionsForPrimaryUser } diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt new file mode 100644 index 000000000000..46db34618c70 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.data.repository + +import android.content.pm.PackageInstaller +import android.os.Handler +import com.android.internal.annotations.GuardedBy +import com.android.systemui.common.shared.model.PackageInstallSession +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.log.dagger.PackageChangeRepoLog +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.dropWhile +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +/** Monitors package install sessions for all users. */ +@SysUISingleton +class PackageInstallerMonitor +@Inject +constructor( + @Background private val bgHandler: Handler, + @Background private val bgScope: CoroutineScope, + @PackageChangeRepoLog logBuffer: LogBuffer, + private val packageInstaller: PackageInstaller, +) : PackageInstaller.SessionCallback() { + + private val logger = Logger(logBuffer, TAG) + + @GuardedBy("sessions") private val sessions = mutableMapOf<Int, PackageInstallSession>() + + private val _installSessions = + MutableStateFlow<List<PackageInstallSession>>(emptyList()).apply { + subscriptionCount + .map { count -> count > 0 } + .distinctUntilChanged() + // Drop initial false value + .dropWhile { !it } + .onEach { isActive -> + if (isActive) { + synchronized(sessions) { + sessions.putAll( + packageInstaller.allSessions + .map { session -> session.toModel() } + .associateBy { it.sessionId } + ) + updateInstallerSessionsFlow() + } + packageInstaller.registerSessionCallback( + this@PackageInstallerMonitor, + bgHandler + ) + } else { + synchronized(sessions) { + sessions.clear() + updateInstallerSessionsFlow() + } + packageInstaller.unregisterSessionCallback(this@PackageInstallerMonitor) + } + } + .launchIn(bgScope) + } + + val installSessionsForPrimaryUser: Flow<List<PackageInstallSession>> = + _installSessions.asStateFlow() + + /** Called when a new installer session is created. */ + override fun onCreated(sessionId: Int) { + logger.i({ "session created $int1" }) { int1 = sessionId } + updateSession(sessionId) + } + + /** Called when new installer session has finished. */ + override fun onFinished(sessionId: Int, success: Boolean) { + logger.i({ "session finished $int1" }) { int1 = sessionId } + synchronized(sessions) { + sessions.remove(sessionId) + updateInstallerSessionsFlow() + } + } + + /** + * Badging details for the session changed. For example, the app icon or label has been updated. + */ + override fun onBadgingChanged(sessionId: Int) { + logger.i({ "session badging changed $int1" }) { int1 = sessionId } + updateSession(sessionId) + } + + /** + * A session is considered active when there is ongoing forward progress being made. For + * example, a package started downloading. + */ + override fun onActiveChanged(sessionId: Int, active: Boolean) { + // Active status updates are not tracked for now + } + + override fun onProgressChanged(sessionId: Int, progress: Float) { + // Progress updates are not tracked for now + } + + private fun updateSession(sessionId: Int) { + val session = packageInstaller.getSessionInfo(sessionId) + + synchronized(sessions) { + if (session == null) { + sessions.remove(sessionId) + } else { + sessions[sessionId] = session.toModel() + } + updateInstallerSessionsFlow() + } + } + + @GuardedBy("sessions") + private fun updateInstallerSessionsFlow() { + _installSessions.value = sessions.values.toList() + } + + companion object { + const val TAG = "PackageInstallerMonitor" + + private fun PackageInstaller.SessionInfo.toModel(): PackageInstallSession { + return PackageInstallSession( + sessionId = this.sessionId, + packageName = this.appPackageName, + icon = this.getAppIcon(), + user = this.user, + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt new file mode 100644 index 000000000000..7025229743b9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.shared.model + +import android.graphics.Bitmap +import android.os.UserHandle + +/** Represents a session of a package being installed on device. */ +data class PackageInstallSession( + val sessionId: Int, + val packageName: String, + val icon: Bitmap?, + val user: UserHandle, +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt index 9e7fb4e73a29..153b7aa3e522 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt @@ -20,6 +20,8 @@ import android.annotation.SuppressLint import android.app.DreamManager import com.android.systemui.CoreStartable import com.android.systemui.Flags.communalHub +import com.android.systemui.Flags.restartDreamOnUnocclude +import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -27,8 +29,10 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -43,6 +47,7 @@ constructor( private val powerInteractor: PowerInteractor, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val communalInteractor: CommunalInteractor, private val dreamManager: DreamManager, @Background private val bgScope: CoroutineScope, ) : CoreStartable { @@ -52,6 +57,19 @@ constructor( return } + // Return to dream from occluded when not already dreaming. + if (restartDreamOnUnocclude()) { + keyguardTransitionInteractor.startedKeyguardTransitionStep + .sample(keyguardInteractor.isDreaming, ::Pair) + .filter { + it.first.from == KeyguardState.OCCLUDED && + it.first.to == KeyguardState.DREAMING && + !it.second + } + .onEach { dreamManager.startDream() } + .launchIn(bgScope) + } + // Restart the dream underneath the hub in order to support the ability to swipe // away the hub to enter the dream. keyguardTransitionInteractor.finishedKeyguardState diff --git a/packages/SystemUI/src/com/android/systemui/communal/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 5091a99b2a64..06c83962df6b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -163,6 +163,13 @@ constructor( initialValue = false, ) + /** Whether to start dreaming when returning from occluded */ + val dreamFromOccluded: Flow<Boolean> = + keyguardTransitionInteractor + .transitionStepsToState(KeyguardState.OCCLUDED) + .map { it.from == KeyguardState.DREAMING } + .stateIn(scope = applicationScope, SharingStarted.Eagerly, false) + /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. * @@ -403,19 +410,30 @@ constructor( updateOnWorkProfileBroadcastReceived, ) { widgets, allowedCategories, _ -> widgets.map { widget -> - if (widget.providerInfo.widgetCategory and allowedCategories != 0) { - // At least one category this widget specified is allowed, so show it - WidgetContent.Widget( - appWidgetId = widget.appWidgetId, - providerInfo = widget.providerInfo, - appWidgetHost = appWidgetHost, - inQuietMode = isQuietModeEnabled(widget.providerInfo.profile) - ) - } else { - WidgetContent.DisabledWidget( - appWidgetId = widget.appWidgetId, - providerInfo = widget.providerInfo, - ) + when (widget) { + is CommunalWidgetContentModel.Available -> { + if (widget.providerInfo.widgetCategory and allowedCategories != 0) { + // At least one category this widget specified is allowed, so show it + WidgetContent.Widget( + appWidgetId = widget.appWidgetId, + providerInfo = widget.providerInfo, + appWidgetHost = appWidgetHost, + inQuietMode = isQuietModeEnabled(widget.providerInfo.profile) + ) + } else { + WidgetContent.DisabledWidget( + appWidgetId = widget.appWidgetId, + providerInfo = widget.providerInfo, + ) + } + } + is CommunalWidgetContentModel.Pending -> { + WidgetContent.PendingWidget( + appWidgetId = widget.appWidgetId, + packageName = widget.packageName, + icon = widget.icon, + ) + } } } } @@ -430,7 +448,15 @@ constructor( } else { // Get associated work profile for the currently selected user. val workProfile = userTracker.userProfiles.find { it.isManagedProfile } - list.filter { it.providerInfo.profile.identifier != workProfile?.id } + list.filter { model -> + val uid = + when (model) { + is CommunalWidgetContentModel.Available -> + model.providerInfo.profile.identifier + is CommunalWidgetContentModel.Pending -> model.user.identifier + } + uid != workProfile?.id + } } /** A flow of available smartspace targets. Currently only showing timers. */ @@ -513,7 +539,11 @@ constructor( ): List<CommunalWidgetContentModel> { val currentUserIds = userTracker.userProfiles.map { it.id }.toSet() return list.filter { widget -> - currentUserIds.contains(widget.providerInfo.profile?.identifier) + when (widget) { + is CommunalWidgetContentModel.Available -> + currentUserIds.contains(widget.providerInfo.profile?.identifier) + is CommunalWidgetContentModel.Pending -> true + } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/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/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt index e141dc40477c..53aecc199c4b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt @@ -17,10 +17,27 @@ package com.android.systemui.communal.shared.model import android.appwidget.AppWidgetProviderInfo +import android.graphics.Bitmap +import android.os.UserHandle /** Encapsulates data for a communal widget. */ -data class CommunalWidgetContentModel( - val appWidgetId: Int, - val providerInfo: AppWidgetProviderInfo, - val priority: Int, -) +sealed interface CommunalWidgetContentModel { + val appWidgetId: Int + val priority: Int + + /** Widget is ready to display */ + data class Available( + override val appWidgetId: Int, + val providerInfo: AppWidgetProviderInfo, + override val priority: Int, + ) : CommunalWidgetContentModel + + /** Widget is pending installation */ + data class Pending( + override val appWidgetId: Int, + override val priority: Int, + val packageName: String, + val icon: Bitmap?, + val user: UserHandle, + ) : CommunalWidgetContentModel +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index 3f92223fb57b..f6122ad48300 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -104,7 +104,12 @@ constructor( ): Boolean = withContext(backgroundDispatcher) { val widgets = communalInteractor.widgetContent.first() - val excludeList = widgets.mapTo(ArrayList()) { it.providerInfo } + val excludeList = + widgets.filterIsInstance<CommunalContentModel.WidgetContent.Widget>().mapTo( + ArrayList() + ) { + it.providerInfo + } getWidgetPickerActivityIntent(resources, packageManager, excludeList)?.let { try { activityLauncher.launch(it) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt index 337d8735ea53..9114aabae2e9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge /** View model for transitions related to the communal hub. */ @@ -49,6 +50,27 @@ constructor( communalInteractor: CommunalInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { + // Show UMO on glanceable hub immediately on transition into glanceable hub + private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> = + keyguardTransitionInteractor + .transitionStepsFromState(KeyguardState.OCCLUDED) + .filter { + it.to == KeyguardState.GLANCEABLE_HUB && + (it.transitionState == TransitionState.STARTED || + it.transitionState == TransitionState.CANCELED) + } + .map { it.transitionState == TransitionState.STARTED } + + private val showUmoFromGlanceableHubToOccluded: Flow<Boolean> = + keyguardTransitionInteractor + .transitionStepsFromState(KeyguardState.GLANCEABLE_HUB) + .filter { + it.to == KeyguardState.OCCLUDED && + (it.transitionState == TransitionState.FINISHED || + it.transitionState == TransitionState.CANCELED) + } + .map { it.transitionState != TransitionState.FINISHED } + /** * Whether UMO location should be on communal. This flow is responsive to transitions so that a * new value is emitted at the right step of a transition to/from communal hub that the location @@ -60,6 +82,8 @@ constructor( glanceableHubToLockscreenTransitionViewModel.showUmo, dreamToGlanceableHubTransitionViewModel.showUmo, glanceableHubToDreamTransitionViewModel.showUmo, + showUmoFromOccludedToGlanceableHub, + showUmoFromGlanceableHubToOccluded, ) .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 1120466c7acc..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 2ccab072d57f..301da51c8082 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt @@ -39,6 +39,7 @@ class CommunalAppWidgetHostStartable @Inject constructor( private val appWidgetHost: CommunalAppWidgetHost, + private val communalWidgetHost: CommunalWidgetHost, private val communalInteractor: CommunalInteractor, private val userTracker: UserTracker, @Background private val bgScope: CoroutineScope, @@ -70,9 +71,11 @@ constructor( // Always ensure this is called on the main/ui thread. withContext(uiDispatcher) { if (active) { + communalWidgetHost.startObservingHost() appWidgetHost.startListening() } else { appWidgetHost.stopListening() + communalWidgetHost.stopObservingHost() } } @@ -83,7 +86,15 @@ constructor( private fun validateWidgetsAndDeleteOrphaned(widgets: List<CommunalWidgetContentModel>) { val currentUserIds = userTracker.userProfiles.map { it.id }.toSet() widgets - .filter { widget -> !currentUserIds.contains(widget.providerInfo.profile?.identifier) } + .filter { widget -> + val uid = + when (widget) { + is CommunalWidgetContentModel.Available -> + widget.providerInfo.profile?.identifier + is CommunalWidgetContentModel.Pending -> widget.user.identifier + } + !currentUserIds.contains(uid) + } .onEach { widget -> communalInteractor.deleteWidget(id = widget.appWidgetId) } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/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/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt index 7c2dae34707b..060a3318abd0 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt @@ -25,10 +25,10 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl import com.android.systemui.util.kotlin.SharedPreferencesExt.observe +import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart class AuthorizedPanelsRepositoryImpl @Inject @@ -40,7 +40,7 @@ constructor( override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> { val prefs = instantiateSharedPrefs(user) - return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) } + return prefs.observe().emitOnStart().map { getAuthorizedPanelsInternal(prefs) } } override fun getAuthorizedPanels(): Set<String> { diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt index 9be049400962..691ec76d50ed 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt @@ -26,12 +26,12 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl import com.android.systemui.util.kotlin.SharedPreferencesExt.observe +import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) @SysUISingleton @@ -63,8 +63,8 @@ constructor( ): Flow<SelectedComponentRepository.SelectedComponent?> { val prefs = getSharedPreferencesForUser(userHandle.identifier) return prefs - .observe(PREF_COMPONENT) - .onStart { emit(Unit) } + .observe() + .emitOnStart() .map { getSelectedComponent(userHandle) } .flowOn(bgDispatcher) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index ef3f10fa3853..11e6f7a8c38c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -49,6 +49,7 @@ import android.content.SharedPreferences; import android.content.om.OverlayManager; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; import android.content.res.AssetManager; @@ -224,6 +225,13 @@ public class FrameworkServicesModule { @Provides @Singleton + static UserScopedService<ColorDisplayManager> provideScopedColorDisplayManager( + Context context) { + return new UserScopedServiceImpl<>(context, ColorDisplayManager.class); + } + + @Provides + @Singleton static CrossWindowBlurListeners provideCrossWindowBlurListeners() { return CrossWindowBlurListeners.getInstance(); } @@ -483,6 +491,12 @@ public class FrameworkServicesModule { @Provides @Singleton + static PackageInstaller providePackageInstaller(PackageManager packageManager) { + return packageManager.getPackageInstaller(); + } + + @Provides + @Singleton static PackageManagerWrapper providePackageManagerWrapper() { return PackageManagerWrapper.getInstance(); } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt index 30a56a21e322..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 @@ -48,6 +48,7 @@ import com.android.systemui.keyguard.data.repository.FaceDetectTableLog 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.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 @@ -302,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/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt index 6c6683a483c7..669cd94c1f21 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) 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/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt index d3f7e24bb87f..44f1c1e8305f 100644 --- 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 @@ -17,19 +17,43 @@ 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 repository: ShortcutHelperRepository) { +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 onUserLeave() { + 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/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt index 934f9ee9e90d..ef4156da4f7b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt @@ -63,12 +63,13 @@ constructor( setUpSheetDismissListener() setUpDismissOnTouchOutside() observeFinishRequired() + viewModel.onViewOpened() } override fun onDestroy() { super.onDestroy() if (isFinishing) { - viewModel.onUserLeave() + viewModel.onViewClosed() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt index 7e48c6523122..c623f5c23fd9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt @@ -38,7 +38,11 @@ constructor( .distinctUntilChanged() .flowOn(backgroundDispatcher) - fun onUserLeave() { - interactor.onUserLeave() + fun onViewClosed() { + interactor.onViewClosed() + } + + fun onViewOpened() { + interactor.onViewOpened() } } 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 d6fd354d8544..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; @@ -1077,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); @@ -1165,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); } } }); @@ -1280,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); } } }); @@ -1546,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())); @@ -2124,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()); @@ -3357,7 +3388,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } catch (RemoteException e) { mSurfaceBehindRemoteAnimationRequested = false; - e.printStackTrace(); + Log.e(TAG, "Failed to report keyguardGoingAway", e); } } @@ -3440,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); } @@ -3471,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/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index e32bfcf81fe2..7655d7a89dc3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -134,7 +134,7 @@ constructor( TransitionInfo( ownerName = "", from = KeyguardState.OFF, - to = KeyguardState.LOCKSCREEN, + to = KeyguardState.OFF, animator = null ) ) @@ -247,7 +247,7 @@ constructor( state: TransitionState ) { if (updateTransitionId != transitionId) { - Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId") + Log.w(TAG, "Attempting to update with old/invalid transitionId: $transitionId") return } @@ -266,6 +266,14 @@ constructor( } override suspend fun emitInitialStepsFromOff(to: KeyguardState) { + _currentTransitionInfo.value = + TransitionInfo( + ownerName = "KeyguardTransitionRepository(boot)", + from = KeyguardState.OFF, + to = to, + animator = null + ) + emitTransition( TransitionStep( KeyguardState.OFF, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt new file mode 100644 index 000000000000..80bdc65f9b97 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.KeyguardState +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +@SysUISingleton +class LockscreenSceneTransitionRepository @Inject constructor() { + + /** + * This [KeyguardState] will indicate which sub state within KTF should be navigated to when the + * next transition into the Lockscreen scene is started. It will be consumed exactly once and + * after that the state will be set back to [DEFAULT_STATE]. + */ + val nextLockscreenTargetState: MutableStateFlow<KeyguardState> = MutableStateFlow(DEFAULT_STATE) + + companion object { + val DEFAULT_STATE = KeyguardState.LOCKSCREEN + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 5a28f7113ebd..9b07675f672c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.wm.shell.animation.Interpolators import javax.inject.Inject @@ -140,6 +141,8 @@ constructor( } private fun listenForAlternateBouncerToGone() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // Handled via #dismissAlternateBouncer. return @@ -162,6 +165,8 @@ constructor( } private fun listenForAlternateBouncerToPrimaryBouncer() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { isPrimaryBouncerShowing -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 4d737749fbc1..a306954b7d8f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion. import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -185,6 +186,7 @@ constructor( * PRIMARY_BOUNCER. */ private fun listenForAodToPrimaryBouncer() { + if (SceneContainerFlag.isEnabled) return scope.launch("$TAG#listenForAodToPrimaryBouncer") { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt index e738ea4397de..63294f7609a2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -93,6 +94,8 @@ constructor( } private fun listenForDreamingLockscreenHostedToPrimaryBouncer() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing } @@ -101,6 +104,8 @@ constructor( } private fun listenForDreamingLockscreenHostedToGone() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.biometricUnlockState .filterRelevantKeyguardStateAnd { biometricUnlockState -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index c952e0879d12..7961b45830d4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -88,6 +89,8 @@ constructor( private fun listenForDreamingToGlanceableHub() { if (!communalHub()) return + if (SceneContainerFlag.isEnabled) + return // TODO(b/336576536): Check if adaptation for scene framework is needed scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) { glanceableHubTransitions.listenForGlanceableHubTransition( transitionOwnerName = TAG, @@ -175,6 +178,8 @@ constructor( } private fun listenForDreamingToGoneWhenDismissable() { + if (SceneContainerFlag.isEnabled) + return // TODO(b/336576536): Check if adaptation for scene framework is needed scope.launch { keyguardInteractor.isAbleToDream .sampleCombine( @@ -190,6 +195,8 @@ constructor( } private fun listenForDreamingToGoneFromBiometricUnlock() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.biometricUnlockState .filterRelevantKeyguardStateAnd { biometricUnlockState -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index faab033441c1..ca6ab3ef52d8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -28,9 +28,11 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -62,6 +64,8 @@ constructor( ) { override fun start() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (!Flags.communalHub()) { return } @@ -79,6 +83,7 @@ constructor( duration = when (toState) { KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION + KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION else -> DEFAULT_DURATION }.inWholeMilliseconds } @@ -171,5 +176,6 @@ constructor( const val TAG = "FromGlanceableHubTransitionInteractor" val DEFAULT_DURATION = 1.seconds val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION + val TO_OCCLUDED_DURATION = 450.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index c2c095bb9574..2b3732f75812 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -62,6 +63,8 @@ constructor( ) { override fun start() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return listenForGoneToAodOrDozing() listenForGoneToDreaming() listenForGoneToLockscreenOrHub() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 56261e0865e1..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 e51ba8308a08..2603aab2781b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators +import com.android.systemui.Flags.restartDreamOnUnocclude import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -26,6 +27,7 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -90,17 +92,14 @@ constructor( .sample( communalInteractor.isIdleOnCommunal, communalInteractor.showCommunalFromOccluded, + communalInteractor.dreamFromOccluded, ) - .collect { (_, isIdleOnCommunal, showCommunalFromOccluded) -> - // Occlusion signals come from the framework, and should interrupt any - // existing transition - val to = - if (isIdleOnCommunal || showCommunalFromOccluded) { - KeyguardState.GLANCEABLE_HUB - } else { - KeyguardState.LOCKSCREEN - } - startTransitionTo(to) + .collect { (_, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) -> + startTransitionToLockscreenOrHub( + isIdleOnCommunal, + showCommunalFromOccluded, + dreamFromOccluded + ) } } } else { @@ -110,26 +109,42 @@ constructor( keyguardInteractor.isKeyguardShowing, communalInteractor.isIdleOnCommunal, communalInteractor.showCommunalFromOccluded, + communalInteractor.dreamFromOccluded, ) - .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) -> + .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _, _) -> !isOccluded && isShowing } - .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded) -> - // Occlusion signals come from the framework, and should interrupt any - // existing transition - val to = - if (isIdleOnCommunal || showCommunalFromOccluded) { - KeyguardState.GLANCEABLE_HUB - } else { - KeyguardState.LOCKSCREEN - } - startTransitionTo(to) + .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) + -> + startTransitionToLockscreenOrHub( + isIdleOnCommunal, + showCommunalFromOccluded, + dreamFromOccluded + ) } } } } + private suspend fun FromOccludedTransitionInteractor.startTransitionToLockscreenOrHub( + isIdleOnCommunal: Boolean, + showCommunalFromOccluded: Boolean, + dreamFromOccluded: Boolean, + ) { + if (restartDreamOnUnocclude() && dreamFromOccluded) { + startTransitionTo(KeyguardState.DREAMING) + } else if (isIdleOnCommunal || showCommunalFromOccluded) { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return + startTransitionTo(KeyguardState.GLANCEABLE_HUB) + } else { + startTransitionTo(KeyguardState.LOCKSCREEN) + } + } + private fun listenForOccludedToGone() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // We don't think OCCLUDED to GONE is possible. You should always have to go via a // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard @@ -150,10 +165,6 @@ constructor( } } - fun dismissToGone() { - scope.launch { startTransitionTo(KeyguardState.GONE) } - } - private fun listenForOccludedToAsleep() { scope.launch { listenForSleepTransition() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 181a551b0537..53a0c3200f3d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample @@ -98,6 +99,8 @@ constructor( } private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { scope.launch { keyguardInteractor.primaryBouncerShowing @@ -158,10 +161,14 @@ constructor( } private fun listenForPrimaryBouncerToAsleep() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { listenForSleepTransition() } } private fun listenForPrimaryBouncerToDreamingLockscreenHosted() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.primaryBouncerShowing .sample(keyguardInteractor.isActiveDreamLockscreenHosted, ::Pair) @@ -174,6 +181,8 @@ constructor( } private fun listenForPrimaryBouncerToGone() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // This is handled in KeyguardSecurityContainerController and // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt index 197221a7b5b3..fcf67d519cae 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt @@ -25,6 +25,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject @@ -49,6 +50,8 @@ constructor( fromState: KeyguardState, toState: KeyguardState, ) { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return val toScene = if (fromState == KeyguardState.GLANCEABLE_HUB) { CommunalScenes.Blank diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/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 88367f42abec..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 @@ -63,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 @@ -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,7 +230,19 @@ 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 @@ -305,10 +318,12 @@ constructor( shadeRepository.legacyShadeExpansion.onStart { emit(0f) }, keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) }, ) { legacyShadeExpansion, goneValue -> - if (goneValue == 1f || (goneValue == 0f && legacyShadeExpansion == 0f)) { + val isLegacyShadeInResetPosition = + legacyShadeExpansion == 0f || legacyShadeExpansion == 1f + if (goneValue == 1f || (goneValue == 0f && isLegacyShadeInResetPosition)) { // Reset the translation value emit(0f) - } else if (legacyShadeExpansion > 0f && legacyShadeExpansion < 1f) { + } 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 @@ -326,7 +341,11 @@ constructor( } } } - .distinctUntilChanged() + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = 0f, + ) val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered 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..75c4d6f6fea6 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, ) { @@ -72,8 +75,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 +116,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/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/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index bb2eeb77969d..1e2db7c36432 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -16,11 +16,17 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockMode +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor +import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,6 +48,7 @@ constructor( fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor, fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor, + sceneInteractor: SceneInteractor, ) { private val defaultSurfaceBehindVisibility = transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible) @@ -103,21 +110,42 @@ constructor( * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it. */ val usingKeyguardGoingAwayAnimation: Flow<Boolean> = - combine( - transitionInteractor.isInTransitionToState(KeyguardState.GONE), - transitionInteractor.finishedKeyguardState, - surfaceBehindInteractor.isAnimatingSurface, - notificationLaunchAnimationInteractor.isLaunchAnimationRunning, - ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning -> - // Using the animation if we're animating it directly, or if the - // ActivityLaunchAnimator is in the process of animating it. - val animationsRunning = isAnimatingSurface || notifLaunchRunning - // We may still be animating the surface after the keyguard is fully GONE, since - // some animations (like the translation spring) are not tied directly to the - // transition step amount. - isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning) - } - .distinctUntilChanged() + if (SceneContainerFlag.isEnabled) { + combine( + sceneInteractor.transitionState, + surfaceBehindInteractor.isAnimatingSurface, + notificationLaunchAnimationInteractor.isLaunchAnimationRunning, + ) { transition, isAnimatingSurface, isLaunchAnimationRunning -> + // Using the animation if we're animating it directly, or if the + // ActivityLaunchAnimator is in the process of animating it. + val isAnyAnimationRunning = isAnimatingSurface || isLaunchAnimationRunning + // We may still be animating the surface after the keyguard is fully GONE, since + // some animations (like the translation spring) are not tied directly to the + // transition step amount. + transition.isTransitioning(to = Scenes.Gone) || + (isAnyAnimationRunning && + (transition.isIdle(Scenes.Gone) || + transition.isTransitioning(from = Scenes.Gone))) + } + .distinctUntilChanged() + } else { + combine( + transitionInteractor.isInTransitionToState(KeyguardState.GONE), + transitionInteractor.finishedKeyguardState, + surfaceBehindInteractor.isAnimatingSurface, + notificationLaunchAnimationInteractor.isLaunchAnimationRunning, + ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning -> + // Using the animation if we're animating it directly, or if the + // ActivityLaunchAnimator is in the process of animating it. + val animationsRunning = isAnimatingSurface || notifLaunchRunning + // We may still be animating the surface after the keyguard is fully GONE, since + // some animations (like the translation spring) are not tied directly to the + // transition step amount. + isInTransitionToGone || + (finishedState == KeyguardState.GONE && animationsRunning) + } + .distinctUntilChanged() + } /** * Whether the lockscreen is visible, from the Window Manager (WM) perspective. @@ -127,28 +155,44 @@ constructor( * want to know if the AOD/clock/notifs/etc. are visible. */ val lockscreenVisibility: Flow<Boolean> = - transitionInteractor.currentKeyguardState - .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) - .map { (currentState, startedWithPrev) -> - val startedFromStep = startedWithPrev?.previousValue - val startedStep = startedWithPrev?.newValue - val returningToGoneAfterCancellation = - startedStep?.to == KeyguardState.GONE && - startedFromStep?.transitionState == TransitionState.CANCELED && - startedFromStep.from == KeyguardState.GONE + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState + .pairwise(ObservableTransitionState.Idle(Scenes.Lockscreen)) + .map { (prevTransitionState, transitionState) -> + val isReturningToGoneAfterCancellation = + prevTransitionState.isTransitioning(from = Scenes.Gone) && + transitionState.isTransitioning(to = Scenes.Gone) + val isNotOnGone = + !transitionState.isTransitioning(from = Scenes.Gone) && + !transitionState.isIdle(Scenes.Gone) - if (!returningToGoneAfterCancellation) { - // By default, apply the lockscreen visibility of the current state. - KeyguardState.lockscreenVisibleInState(currentState) - } else { - // If we're transitioning to GONE after a prior canceled transition from GONE, - // then this is the camera launch transition from an asleep state back to GONE. - // We don't want to show the lockscreen since we're aborting the lock and going - // back to GONE. - KeyguardState.lockscreenVisibleInState(KeyguardState.GONE) + isNotOnGone && !isReturningToGoneAfterCancellation } - } - .distinctUntilChanged() + .distinctUntilChanged() + } else { + transitionInteractor.currentKeyguardState + .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) + .map { (currentState, startedWithPrev) -> + val startedFromStep = startedWithPrev?.previousValue + val startedStep = startedWithPrev?.newValue + val returningToGoneAfterCancellation = + startedStep?.to == KeyguardState.GONE && + startedFromStep?.transitionState == TransitionState.CANCELED && + startedFromStep.from == KeyguardState.GONE + + if (!returningToGoneAfterCancellation) { + // By default, apply the lockscreen visibility of the current state. + KeyguardState.lockscreenVisibleInState(currentState) + } else { + // If we're transitioning to GONE after a prior canceled transition from + // GONE, then this is the camera launch transition from an asleep state back + // to GONE. We don't want to show the lockscreen since we're aborting the + // lock and going back to GONE. + KeyguardState.lockscreenVisibleInState(KeyguardState.GONE) + } + } + .distinctUntilChanged() + } /** * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt new file mode 100644 index 000000000000..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/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/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index 4f00495819e8..1c7b4d996b36 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -22,6 +22,8 @@ import android.content.res.ColorStateList import android.util.StateSet import android.view.HapticFeedbackConstants import android.view.View +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import androidx.core.view.isInvisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle @@ -61,6 +63,7 @@ object DeviceEntryIconViewBinder { bgViewModel: DeviceEntryBackgroundViewModel, falsingManager: FalsingManager, vibratorHelper: VibratorHelper, + overrideColor: Color? = null, ) { DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode() val longPressHandlingView = view.longPressHandlingView @@ -76,7 +79,7 @@ object DeviceEntryIconViewBinder { view, HapticFeedbackConstants.CONFIRM, ) - applicationScope.launch { viewModel.onLongPress() } + applicationScope.launch { viewModel.onUserInteraction() } } } @@ -116,6 +119,17 @@ object DeviceEntryIconViewBinder { launch("$TAG#viewModel.accessibilityDelegateHint") { viewModel.accessibilityDelegateHint.collect { hint -> view.accessibilityHintType = hint + if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) { + view.setOnClickListener { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.CONFIRM, + ) + applicationScope.launch { viewModel.onUserInteraction() } + } + } else { + view.setOnClickListener(null) + } } } launch("$TAG#viewModel.useBackgroundProtection") { @@ -157,7 +171,8 @@ object DeviceEntryIconViewBinder { viewModel.type.contentDescriptionResId ) } - fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint) + fgIconView.imageTintList = + ColorStateList.valueOf(overrideColor?.toArgb() ?: viewModel.tint) fgIconView.setPadding( viewModel.padding, viewModel.padding, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt index ed5d53cc9371..c846cbe76770 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt @@ -33,14 +33,18 @@ import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.clocks.AodClockBurnInModel import com.android.systemui.plugins.clocks.ClockController +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch object KeyguardClockViewBinder { private val TAG = KeyguardClockViewBinder::class.simpleName!! // When changing to new clock, we need to remove old clock views from burnInLayer private var lastClock: ClockController? = null + @JvmStatic fun bind( clockSection: ClockSection, @@ -48,6 +52,7 @@ object KeyguardClockViewBinder { viewModel: KeyguardClockViewModel, keyguardClockInteractor: KeyguardClockInteractor, blueprintInteractor: KeyguardBlueprintInteractor, + rootViewModel: KeyguardRootViewModel, ) { keyguardRootView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { @@ -105,6 +110,28 @@ object KeyguardClockViewBinder { } } } + + launch { + if (!MigrateClocksToBlueprint.isEnabled) return@launch + combine( + rootViewModel.translationX, + rootViewModel.translationY, + rootViewModel.scale, + ::Triple + ) + .collect { (translationX, translationY, scale) -> + viewModel.currentClock.value + ?.largeClock + ?.layout + ?.applyAodBurnIn( + AodClockBurnInModel( + translationX = translationX.value!!, + translationY = translationY, + scale = scale.scale + ) + ) + } + } } } } @@ -117,17 +144,16 @@ object KeyguardClockViewBinder { ) { val burnInLayer = viewModel.burnInLayer val clockController = viewModel.currentClock.value + // Large clocks won't be added to or removed from burn in layer + // Weather large clock has customized burn in preventing mechanism + // Non-weather large clock will only scale and translate vertically clockController?.let { clock -> when (clockSize) { ClockSize.LARGE -> { clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) } - if (clock.config.useAlternateSmartspaceAODTransition) { - clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) } - } } ClockSize.SMALL -> { clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) } - clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) } } } } @@ -148,13 +174,6 @@ object KeyguardClockViewBinder { burnInLayer?.removeView(it) rootView.removeView(it) } - - // add large clock to burn in layer only when it will have same transition with other - // components in AOD otherwise, it will have a separate scale transition while other - // components only have translate transition - if (clock.config.useAlternateSmartspaceAODTransition) { - clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) } - } clock.largeClock.layout.views.forEach { rootView.removeView(it) } } lastClock = currentClock diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index bda6438c308f..39db22d5616d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -236,7 +236,8 @@ object KeyguardRootViewBinder { indicationArea, startButton, endButton, - lockIcon -> { + lockIcon, + deviceEntryIcon -> { // Do not move these views } else -> childView.translationX = px @@ -256,23 +257,6 @@ object KeyguardRootViewBinder { it.scaleX = scaleViewModel.scale it.scaleY = scaleViewModel.scale } - // Make sure to reset these views, or they will be invisible - if (childViews[burnInLayerId]?.scaleX != 1f) { - childViews[burnInLayerId]?.scaleX = 1f - childViews[burnInLayerId]?.scaleY = 1f - childViews[aodNotificationIconContainerId]?.scaleX = 1f - childViews[aodNotificationIconContainerId]?.scaleY = 1f - view.requestLayout() - } - } else { - // For weather clock, large clock should have only scale - // transition with other parts in burnInLayer - childViews[burnInLayerId]?.scaleX = scaleViewModel.scale - childViews[burnInLayerId]?.scaleY = scaleViewModel.scale - childViews[aodNotificationIconContainerId]?.scaleX = - scaleViewModel.scale - childViews[aodNotificationIconContainerId]?.scaleY = - scaleViewModel.scale } } } @@ -596,6 +580,7 @@ object KeyguardRootViewBinder { private val startButton = R.id.start_button private val endButton = R.id.end_button private val lockIcon = R.id.lock_icon_view + private val deviceEntryIcon = R.id.device_entry_icon_view private val nsslPlaceholderId = R.id.nssl_placeholder private const val ID = "occluding_app_device_entry_unlock_msg" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt index 0f63f65e4511..1f4bc61fb003 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt @@ -29,7 +29,10 @@ import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionVi import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToDozingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToLockscreenTransitionViewModel @@ -40,6 +43,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionView import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.OccludedToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel @@ -218,4 +222,28 @@ abstract class DeviceEntryIconTransitionModule { abstract fun primaryBouncerToLockscreen( impl: PrimaryBouncerToLockscreenTransitionViewModel ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun dreamingToGlanceableHub( + impl: DreamingToGlanceableHubTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun glanceableHubToDreaming( + impl: GlanceableHubToDreamingTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun glanceableHubToOccluded( + impl: GlanceableHubToOccludedTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun occludedToGlanceableHub( + impl: OccludedToGlanceableHubTransitionViewModel + ): DeviceEntryIconTransition } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt index 35b259849b78..200d30c66305 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -65,12 +65,12 @@ constructor( object : AccessibilityDelegate() { private val accessibilityAuthenticateHint = AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_LONG_CLICK, + AccessibilityNodeInfoCompat.ACTION_CLICK, resources.getString(R.string.accessibility_authenticate_hint) ) private val accessibilityEnterHint = AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_LONG_CLICK, + AccessibilityNodeInfoCompat.ACTION_CLICK, resources.getString(R.string.accessibility_enter_hint) ) override fun onInitializeAccessibilityNodeInfo( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index ef2927096c04..b367715f529e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -38,6 +38,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFaceLayout @@ -64,6 +65,7 @@ constructor( private val context: Context, val smartspaceViewModel: KeyguardSmartspaceViewModel, val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>, + private val rootViewModel: KeyguardRootViewModel, ) : KeyguardSection() { override fun addViews(constraintLayout: ConstraintLayout) {} override fun bindData(constraintLayout: ConstraintLayout) { @@ -75,7 +77,8 @@ constructor( constraintLayout, keyguardClockViewModel, clockInteractor, - blueprintInteractor.get() + blueprintInteractor.get(), + rootViewModel, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index 218967c338ea..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 @@ -57,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 @@ -76,7 +76,7 @@ class ClockSizeTransition( 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 for animation") + Log.e(TAG, "Failed to find smartspace equivalent target under $parent") return } transition.values[SMARTSPACE_BOUNDS] = targetSSView.getRect() @@ -109,14 +109,12 @@ class ClockSizeTransition( 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 @@ -221,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!! } } @@ -232,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) } } @@ -282,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!! } } @@ -291,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) + } } } @@ -339,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!! } } @@ -348,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 @@ -367,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 570f37710c24..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,11 +102,16 @@ constructor( var leaveShadeOpen: Boolean = false var willRunDismissFromKeyguard: Boolean = false val transitionAnimation = - animationFlow.setup( - duration = duration, - from = fromState, - to = GONE, - ) + animationFlow + .setup( + duration = duration, + // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene -> + // scene transition + edge = Edge.create(from = fromState, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = fromState, to = GONE), + ) return shadeInteractor.anyExpansion .map { it > 0f } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt index 87324a233cef..6f8389fc8b7c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt @@ -117,7 +117,8 @@ constructor( KeyguardState.DOZING, KeyguardState.DREAMING, KeyguardState.PRIMARY_BOUNCER, - KeyguardState.AOD -> emit(0f) + KeyguardState.AOD, + KeyguardState.UNDEFINED -> emit(0f) KeyguardState.ALTERNATE_BOUNCER, KeyguardState.LOCKSCREEN -> emit(1f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index da2fcc48a13d..ae83c9e720a3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.animation.FloatEvaluator import android.animation.IntEvaluator import com.android.keyguard.KeyguardViewController +import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -68,6 +69,7 @@ constructor( private val keyguardViewController: Lazy<KeyguardViewController>, private val deviceEntryInteractor: DeviceEntryInteractor, private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor, + private val accessibilityInteractor: AccessibilityInteractor, @Application private val scope: CoroutineScope, ) { val isUdfpsSupported: StateFlow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported @@ -146,10 +148,11 @@ constructor( KeyguardState.GLANCEABLE_HUB, KeyguardState.GONE, KeyguardState.OCCLUDED, - KeyguardState.DREAMING_LOCKSCREEN_HOSTED, -> 0f + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.UNDEFINED, -> 0f KeyguardState.AOD, KeyguardState.ALTERNATE_BOUNCER, - KeyguardState.LOCKSCREEN -> 1f + KeyguardState.LOCKSCREEN, -> 1f } } val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported @@ -232,7 +235,8 @@ constructor( } } val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged() - val isLongPressEnabled: Flow<Boolean> = + + private val isInteractive: Flow<Boolean> = combine( iconType, isUdfpsSupported, @@ -244,17 +248,24 @@ constructor( DeviceEntryIconView.IconType.NONE -> false } } - val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> = - combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled -> - if (longPressEnabled) { - deviceEntryStatus.toAccessibilityHintType() + accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled -> + if (touchExplorationEnabled) { + combine(iconType, isInteractive) { iconType, isInteractive -> + if (isInteractive) { + iconType.toAccessibilityHintType() + } else { + DeviceEntryIconView.AccessibilityHintType.NONE + } + } } else { - DeviceEntryIconView.AccessibilityHintType.NONE + flowOf(DeviceEntryIconView.AccessibilityHintType.NONE) } } - suspend fun onLongPress() { + val isLongPressEnabled: Flow<Boolean> = isInteractive + + suspend fun onUserInteraction() { if (SceneContainerFlag.isEnabled) { deviceEntryInteractor.attemptDeviceEntry() } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/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 bbcea56799ea..f405b9d5a07c 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,6 +39,7 @@ 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 @@ -115,14 +117,18 @@ constructor( private val shadeInteractor: ShadeInteractor, ) { private var burnInJob: Job? = null - private val burnInModel = MutableStateFlow(BurnInModel()) + internal val burnInModel = MutableStateFlow(BurnInModel()) 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 @@ -144,7 +150,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, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index d8b50133949d..02e48fc5a09b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -28,6 +28,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel @@ -89,10 +90,15 @@ constructor( shadeMode: ShadeMode, ): Map<UserAction, UserActionResult> { val shadeSceneKey = - if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade + UserActionResult( + toScene = + if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade, + transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }, + ) val quickSettingsIfSingleShade = - if (shadeMode is ShadeMode.Single) Scenes.QuickSettings else shadeSceneKey + if (shadeMode is ShadeMode.Single) UserActionResult(Scenes.QuickSettings) + else shadeSceneKey return mapOf( Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable }, @@ -103,7 +109,7 @@ constructor( swipeDownFromTop(pointerCount = 2) to // TODO(b/338577208): Remove 'Dual' once we add Dual Shade invocation zones. if (shadeMode is ShadeMode.Dual) { - Scenes.QuickSettingsShade + UserActionResult(Scenes.QuickSettingsShade) } else { quickSettingsIfSingleShade }, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/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 cf6a533ed76b..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,7 +17,9 @@ 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 @@ -34,8 +36,7 @@ constructor( 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> = 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/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt index 9719c029574f..0c70f10bba21 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt @@ -107,14 +107,11 @@ constructor( .thenByDescending { it.updateTime } .thenByDescending { it.notificationKey } - private val _sortedMedia: MutableStateFlow<TreeMap<MediaSortKeyModel, MediaCommonModel>> = - MutableStateFlow(TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)) - val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> = - _sortedMedia.asStateFlow() - - private val _isMediaFromRec: MutableStateFlow<Boolean> = MutableStateFlow(false) - val isMediaFromRec: StateFlow<Boolean> = _isMediaFromRec.asStateFlow() + private val _currentMedia: MutableStateFlow<List<MediaCommonModel>> = + MutableStateFlow(mutableListOf()) + val currentMedia = _currentMedia.asStateFlow() + private var sortedMedia = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) private var mediaFromRecPackageName: String? = null private var locale: Locale = applicationContext.resources.configuration.locales.get(0) @@ -186,7 +183,7 @@ constructor( fun addMediaDataLoadingState(mediaDataLoadingModel: MediaDataLoadingModel) { val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) sortedMap.putAll( - _sortedMedia.value.filter { (_, commonModel) -> + sortedMedia.filter { (_, commonModel) -> commonModel !is MediaCommonModel.MediaControl || commonModel.mediaLoadedModel.instanceId != mediaDataLoadingModel.instanceId } @@ -207,18 +204,52 @@ constructor( ) if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) { - val isMediaFromRec = isMediaFromRec(it) - - _isMediaFromRec.value = isMediaFromRec - if (isMediaFromRec) { - mediaFromRecPackageName = null + val newCommonModel = + MediaCommonModel.MediaControl( + mediaDataLoadingModel, + canBeRemoved(it), + isMediaFromRec(it) + ) + sortedMap[sortKey] = newCommonModel + + // On Addition or tapping on recommendations, we should show the new order of media. + if (mediaFromRecPackageName == it.packageName) { + if (it.isPlaying == true) { + mediaFromRecPackageName = null + _currentMedia.value = sortedMap.values.toList() + } + } else if (sortedMap.size > sortedMedia.size) { + _currentMedia.value = sortedMap.values.toList() + } else if (sortedMap.size == sortedMedia.size) { + // When loading an update for an existing media control. + val currentList = + mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) } + currentList.forEachIndexed { index, mediaCommonModel -> + if ( + mediaCommonModel is MediaCommonModel.MediaControl && + mediaCommonModel.mediaLoadedModel.instanceId == + mediaDataLoadingModel.instanceId && + mediaCommonModel != newCommonModel + ) { + // Update media model if changed. + currentList[index] = newCommonModel + } + } + _currentMedia.value = currentList } - sortedMap[sortKey] = - MediaCommonModel.MediaControl(mediaDataLoadingModel, canBeRemoved(it)) } } - _sortedMedia.value = sortedMap + sortedMedia = sortedMap + + // On removal we want to keep the order being shown to user. + if (mediaDataLoadingModel is MediaDataLoadingModel.Removed) { + _currentMedia.value = + _currentMedia.value.filter { commonModel -> + commonModel !is MediaCommonModel.MediaControl || + mediaDataLoadingModel.instanceId != commonModel.mediaLoadedModel.instanceId + } + } } fun setRecommendationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) { @@ -229,7 +260,7 @@ constructor( } val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) sortedMap.putAll( - _sortedMedia.value.filter { (_, commonModel) -> + sortedMedia.filter { (_, commonModel) -> commonModel !is MediaCommonModel.MediaRecommendations } ) @@ -240,11 +271,25 @@ constructor( isPlaying = false, active = _smartspaceMediaData.value.isActive, ) - if (smartspaceMediaLoadingModel is SmartspaceMediaLoadingModel.Loaded) { - sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) + when (smartspaceMediaLoadingModel) { + is SmartspaceMediaLoadingModel.Loaded -> + sortedMap[sortKey] = + MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) + is SmartspaceMediaLoadingModel.Removed -> + _currentMedia.value = + _currentMedia.value.filter { commonModel -> + commonModel !is MediaCommonModel.MediaRecommendations + } } - _sortedMedia.value = sortedMap + if (sortedMap.size > sortedMedia.size) { + _currentMedia.value = sortedMap.values.toList() + } + sortedMedia = sortedMap + } + + fun setOrderedMedia() { + _currentMedia.value = sortedMedia.values.toList() } fun setMediaFromRecPackageName(packageName: String) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt index c02478b02ec2..96ef7d250012 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt @@ -206,11 +206,11 @@ constructor( listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { allEntries.remove(key) userEntries.remove(key)?.let { // Only notify listeners if something actually changed - listeners.forEach { it.onMediaDataRemoved(key) } + listeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } } @@ -246,7 +246,7 @@ constructor( // Only remove media when the profile is unavailable. if (DEBUG) Log.d(TAG, "Removing $key after profile change") userEntries.remove(key, data) - listeners.forEach { listener -> listener.onMediaDataRemoved(key) } + listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) } } } } @@ -261,7 +261,7 @@ constructor( userEntries.clear() keyCopy.forEach { if (DEBUG) Log.d(TAG, "Removing $it after user change") - listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } + listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) } } allEntries.forEach { (key, data) -> diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt index 3a83115642bc..143d66b69f57 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt @@ -545,8 +545,8 @@ class LegacyMediaDataManagerImpl( * External listeners registered with [addListener] will be notified after the event propagates * through the internal listener pipeline. */ - private fun notifyMediaDataRemoved(key: String) { - internalListeners.forEach { it.onMediaDataRemoved(key) } + private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) { + internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } /** @@ -578,7 +578,7 @@ class LegacyMediaDataManagerImpl( if (it.active == !timedOut && !forceUpdate) { if (it.resumption) { if (DEBUG) Log.d(TAG, "timing out resume player $key") - dismissMediaData(key, 0L /* delay */) + dismissMediaData(key, delay = 0L, userInitiated = false) } return } @@ -627,17 +627,17 @@ class LegacyMediaDataManagerImpl( } } - private fun removeEntry(key: String, logEvent: Boolean = true) { + private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) { mediaEntries.remove(key)?.let { if (logEvent) { logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) } } - notifyMediaDataRemoved(key) + notifyMediaDataRemoved(key, userInitiated) } /** Dismiss a media entry. Returns false if the key was not found. */ - override fun dismissMediaData(key: String, delay: Long): Boolean { + override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean { val existed = mediaEntries[key] != null backgroundExecutor.execute { mediaEntries[key]?.let { mediaData -> @@ -649,7 +649,10 @@ class LegacyMediaDataManagerImpl( } } } - foregroundExecutor.executeDelayed({ removeEntry(key) }, delay) + foregroundExecutor.executeDelayed( + { removeEntry(key = key, userInitiated = userInitiated) }, + delay + ) return existed } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt index ad70db5a3300..88910f944466 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt @@ -53,8 +53,8 @@ class MediaDataCombineLatest @Inject constructor() : listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) } } - override fun onMediaDataRemoved(key: String) { - remove(key) + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { + remove(key, userInitiated) } override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { @@ -71,8 +71,8 @@ class MediaDataCombineLatest @Inject constructor() : } } - override fun onKeyRemoved(key: String) { - remove(key) + override fun onKeyRemoved(key: String, userInitiated: Boolean) { + remove(key, userInitiated) } /** @@ -92,10 +92,10 @@ class MediaDataCombineLatest @Inject constructor() : } } - private fun remove(key: String) { + private fun remove(key: String, userInitiated: Boolean) { entries.remove(key)?.let { val listenersCopy = listeners.toSet() - listenersCopy.forEach { it.onMediaDataRemoved(key) } + listenersCopy.forEach { it.onMediaDataRemoved(key, userInitiated) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt index 5432a189cf7c..8d19ce800cbe 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt @@ -213,7 +213,7 @@ constructor( listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { mediaFilterRepository.removeMediaEntry(key)?.let { mediaData -> val instanceId = mediaData.instanceId mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let { @@ -221,7 +221,7 @@ constructor( MediaDataLoadingModel.Removed(instanceId) ) // Only notify listeners if something actually changed - listeners.forEach { it.onMediaDataRemoved(key) } + listeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } } } @@ -270,7 +270,7 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(data.instanceId) ) - listeners.forEach { listener -> listener.onMediaDataRemoved(key) } + listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) } } } } @@ -288,7 +288,7 @@ constructor( MediaDataLoadingModel.Removed(instanceId) ) getKey(instanceId)?.let { - listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } + listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt index 2331aa2170ef..8099e593b33d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt @@ -60,7 +60,7 @@ interface MediaDataManager { ) /** Dismiss a media entry. Returns false if the key was not found. */ - fun dismissMediaData(key: String, delay: Long): Boolean + fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean /** * Called whenever the recommendation has been expired or removed by the user. This will remove @@ -136,7 +136,7 @@ interface MediaDataManager { ) {} /** Called whenever a previously existing Media notification was removed. */ - override fun onMediaDataRemoved(key: String) {} + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {} /** * Called whenever a previously existing Smartspace media data was removed. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index 1d7c0256b2ef..eed775242d1f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -498,8 +498,8 @@ class MediaDataProcessor( * External listeners registered with [MediaCarouselInteractor.addListener] will be notified * after the event propagates through the internal listener pipeline. */ - private fun notifyMediaDataRemoved(key: String) { - internalListeners.forEach { it.onMediaDataRemoved(key) } + private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) { + internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } /** @@ -531,7 +531,7 @@ class MediaDataProcessor( if (it.active == !timedOut && !forceUpdate) { if (it.resumption) { if (DEBUG) Log.d(TAG, "timing out resume player $key") - dismissMediaData(key, 0L /* delay */) + dismissMediaData(key, delayMs = 0L, userInitiated = false) } return } @@ -580,17 +580,17 @@ class MediaDataProcessor( } } - private fun removeEntry(key: String, logEvent: Boolean = true) { + private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) { mediaDataRepository.removeMediaEntry(key)?.let { if (logEvent) { logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) } } - notifyMediaDataRemoved(key) + notifyMediaDataRemoved(key, userInitiated) } /** Dismiss a media entry. Returns false if the key was not found. */ - fun dismissMediaData(key: String, delayMs: Long): Boolean { + fun dismissMediaData(key: String, delayMs: Long, userInitiated: Boolean): Boolean { val existed = mediaDataRepository.mediaEntries.value[key] != null backgroundExecutor.execute { mediaDataRepository.mediaEntries.value[key]?.let { mediaData -> @@ -602,16 +602,19 @@ class MediaDataProcessor( } } } - foregroundExecutor.executeDelayed({ removeEntry(key) }, delayMs) + foregroundExecutor.executeDelayed( + { removeEntry(key, userInitiated = userInitiated) }, + delayMs + ) return existed } /** Dismiss a media entry. Returns false if the corresponding key was not found. */ - fun dismissMediaData(instanceId: InstanceId, delayMs: Long): Boolean { + fun dismissMediaData(instanceId: InstanceId, delayMs: Long, userInitiated: Boolean): Boolean { val mediaEntries = mediaDataRepository.mediaEntries.value val filteredEntries = mediaEntries.filter { (_, data) -> data.instanceId == instanceId } return if (filteredEntries.isNotEmpty()) { - dismissMediaData(filteredEntries.keys.first(), delayMs) + dismissMediaData(filteredEntries.keys.first(), delayMs, userInitiated) } else { false } @@ -1579,7 +1582,7 @@ class MediaDataProcessor( ) {} /** Called whenever a previously existing Media notification was removed. */ - fun onMediaDataRemoved(key: String) {} + fun onMediaDataRemoved(key: String, userInitiated: Boolean) {} /** * Called whenever a previously existing Smartspace media data was removed. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt index 0e2814b3d91e..043fbfaa8a23 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt @@ -111,10 +111,10 @@ constructor( } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { val token = entries.remove(key) token?.stop() - token?.let { listeners.forEach { it.onKeyRemoved(key) } } + token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } } } fun dump(pw: PrintWriter) { @@ -136,7 +136,7 @@ constructor( /** Called when the route has changed for a given notification. */ fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) /** Called when the notification was removed. */ - fun onKeyRemoved(key: String) + fun onKeyRemoved(key: String, userInitiated: Boolean) } private inner class Entry( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt index b2a8f2e71cc6..b178d84c5d18 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt @@ -137,7 +137,7 @@ constructor( // farther and dismiss the media data so that media controls for the local session // don't hang around while casting. if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) { - dispatchMediaDataRemoved(key) + dispatchMediaDataRemoved(key, userInitiated = false) } } } @@ -151,11 +151,11 @@ constructor( backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { // Queue on background thread to ensure ordering of loaded and removed events is maintained. backgroundExecutor.execute { keyedTokens.remove(key) - dispatchMediaDataRemoved(key) + dispatchMediaDataRemoved(key, userInitiated) } } @@ -174,8 +174,10 @@ constructor( } } - private fun dispatchMediaDataRemoved(key: String) { - foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } } + private fun dispatchMediaDataRemoved(key: String, userInitiated: Boolean) { + foregroundExecutor.execute { + listeners.toSet().forEach { it.onMediaDataRemoved(key, userInitiated) } + } } private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt index 29f396700831..fc319036d67e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt @@ -169,7 +169,7 @@ constructor( mediaListeners[key] = PlaybackStateListener(key, data) } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { mediaListeners.remove(key)?.destroy() } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt index 33c0b195bf50..9e6230012760 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt @@ -63,7 +63,7 @@ constructor( private val mediaDeviceManager: MediaDeviceManager, private val mediaDataCombineLatest: MediaDataCombineLatest, private val mediaDataFilter: MediaDataFilterImpl, - mediaFilterRepository: MediaFilterRepository, + private val mediaFilterRepository: MediaFilterRepository, private val mediaFlags: MediaFlags, ) : MediaDataManager, CoreStartable { @@ -123,18 +123,8 @@ constructor( initialValue = false, ) - /** The most recent sorted set for user media instances */ - val sortedMedia: StateFlow<List<MediaCommonModel>> = - mediaFilterRepository.sortedMedia - .mapLatest { it.values.toList() } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = emptyList(), - ) - - /** Whether the current change in media was done by clicking on a recommendation */ - val isMediaFromRec: StateFlow<Boolean> = mediaFilterRepository.isMediaFromRec + /** The current list for user media instances */ + val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia override fun start() { if (!mediaFlags.isMediaControlsRefactorEnabled()) { @@ -215,12 +205,12 @@ constructor( ) } - override fun dismissMediaData(key: String, delay: Long): Boolean { - return mediaDataProcessor.dismissMediaData(key, delay) + override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean { + return mediaDataProcessor.dismissMediaData(key, delay, userInitiated) } fun removeMediaControl(instanceId: InstanceId, delay: Long) { - mediaDataProcessor.dismissMediaData(instanceId, delay) + mediaDataProcessor.dismissMediaData(instanceId, delay, userInitiated = false) } override fun dismissSmartspaceRecommendation(key: String, delay: Long) { @@ -251,6 +241,10 @@ constructor( override fun isRecommendationActive() = mediaDataRepository.smartspaceMediaData.value.isActive + fun reorderMedia() { + mediaFilterRepository.setOrderedMedia() + } + /** Add a listener for internal events. */ private fun addInternalListener(listener: MediaDataManager.Listener) = mediaDataProcessor.addInternalListener(listener) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt index 9f2d132fc7a9..1a0f582fb100 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, 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..229e592a6a0c 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 @@ -359,6 +366,10 @@ constructor( ) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) mediaCarousel.repeatWhenAttached { + if (mediaFlags.isMediaControlsRefactorEnabled()) { + mediaCarouselViewModel.onAttached() + mediaCarouselScrollHandler.scrollToStart() + } repeatOnLifecycle(Lifecycle.State.STARTED) { listenForAnyStateToGoneKeyguardTransition(this) listenForAnyStateToLockscreenTransition(this) @@ -383,12 +394,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 +486,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 +577,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) { @@ -630,9 +645,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 +663,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) { @@ -734,6 +753,14 @@ constructor( viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded) updateViewControllerToState(viewController, noAnimation = true) updatePageIndicator() + if ( + commonViewModel is MediaCommonViewModel.MediaControl && commonViewModel.isMediaFromRec + ) { + mediaCarouselScrollHandler.scrollToPlayer( + mediaCarouselScrollHandler.visibleMediaIndex, + destIndex = 0 + ) + } mediaCarouselScrollHandler.onPlayersChanged() mediaFrame.requiresRemeasuring = true commonViewModel.onAdded(commonViewModel) @@ -1023,7 +1050,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 +1070,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 +1530,8 @@ constructor( } } - private fun onSwipeToDismiss() { + @VisibleForTesting + fun onSwipeToDismiss() { if (mediaFlags.isMediaControlsRefactorEnabled()) { mediaCarouselViewModel.onSwipeToDismiss() return @@ -1521,6 +1550,7 @@ constructor( it.mIsImpressed = false } } + MediaPlayerData.isSwipedAway = true logger.logSwipeDismiss() mediaManager.onSwipeToDismiss() } @@ -1547,6 +1577,7 @@ constructor( "state: ${desiredHostState?.expansion}, " + "only active ${desiredHostState?.showsOnlyActiveMedia}" ) + println("isSwipedAway: ${MediaPlayerData.isSwipedAway}") } } } @@ -1577,6 +1608,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 +1617,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 +1642,9 @@ internal object MediaPlayerData { // A map that tracks order of visible media players before they get reordered. private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>() + // Whether the user swiped away the carousel since its last update + internal var isSwipedAway: Boolean = false + fun addMediaPlayer( key: String, data: MediaData, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt index ebf1c6a10703..1be25a74dbea 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt @@ -53,8 +53,16 @@ constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) { { "add player $str1, active: $bool1" } ) - fun logMediaRemoved(key: String) = - buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" }) + fun logMediaRemoved(key: String, userInitiated: Boolean) = + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = key + bool1 = userInitiated + }, + { "removing player $str1, by user $bool1" } + ) fun logRecommendationLoaded(key: String, isActive: Boolean) = buffer.log( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index e6c785ef41f0..0bc3c43993dd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -786,10 +786,11 @@ public class MediaControlPanel { if (mKey != null) { closeGuts(); if (!mMediaDataManagerLazy.get().dismissMediaData(mKey, - MediaViewController.GUTS_ANIMATION_DURATION + 100)) { + /* delay */ MediaViewController.GUTS_ANIMATION_DURATION + 100, + /* userInitiated */ true)) { Log.w(TAG, "Manager failed to dismiss media " + mKey); // Remove directly from carousel so user isn't stuck with defunct controls - mMediaCarouselController.removePlayer(mKey, false, false); + mMediaCarouselController.removePlayer(mKey, false, false, true); } } else { Log.w(TAG, "Dismiss media with null notification. Token uid=" diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/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..fd5f44594ae8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt @@ -26,21 +26,15 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.factory.Me import com.android.systemui.media.controls.shared.model.MediaCommonModel import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger -import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.util.Utils -import com.android.systemui.util.kotlin.pairwiseBy -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user inputs for media carousel */ @@ -60,45 +54,26 @@ constructor( private val mediaFlags: MediaFlags, ) { - @OptIn(ExperimentalCoroutinesApi::class) val mediaItems: StateFlow<List<MediaCommonViewModel>> = - conflatedCallbackFlow { - val listener = OnReorderingAllowedListener { trySend(Unit) } - visualStabilityProvider.addPersistentReorderingAllowedListener(listener) - trySend(Unit) - awaitClose { visualStabilityProvider.removeReorderingAllowedListener(listener) } - } - .flatMapLatest { - combine(interactor.isMediaFromRec, interactor.sortedMedia) { - isRecsToMedia, - sortedItems -> - buildList { - shouldReorder = isRecsToMedia - val reorderAllowed = isReorderingAllowed() - sortedItems.forEach { commonModel -> - if (!reorderAllowed || !modelsPendingRemoval.contains(commonModel)) { - when (commonModel) { - is MediaCommonModel.MediaControl -> - add(toViewModel(commonModel)) - is MediaCommonModel.MediaRecommendations -> - add(toViewModel(commonModel)) - } + interactor.currentMedia + .map { sortedItems -> + buildList { + sortedItems.forEach { commonModel -> + // When view is started we should make sure to clean models that are pending + // removal. + // This action should only be triggered once. + if (!isAttached || !modelsPendingRemoval.contains(commonModel)) { + when (commonModel) { + is MediaCommonModel.MediaControl -> add(toViewModel(commonModel)) + is MediaCommonModel.MediaRecommendations -> + add(toViewModel(commonModel)) } } - if (reorderAllowed) { - modelsPendingRemoval.clear() - } } - } - } - .pairwiseBy { old, new -> - // This condition can only happen when view is attached. So the old emit is of the - // most recent list updated. - // If the old list is empty, it is okay to emit the new ordered list. - if (isReorderingAllowed() || shouldReorder || old.isEmpty()) { - new - } else { - old + if (isAttached) { + modelsPendingRemoval.clear() + } + isAttached = false } } .stateIn( @@ -114,13 +89,18 @@ constructor( private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf() - private var shouldReorder = true + private var isAttached = false fun onSwipeToDismiss() { logger.logSwipeDismiss() interactor.onSwipeToDismiss() } + fun onAttached() { + isAttached = true + interactor.reorderMedia() + } + private fun toViewModel( commonModel: MediaCommonModel.MediaControl ): MediaCommonViewModel.MediaControl { @@ -138,6 +118,7 @@ constructor( mediaControlByInstanceId.remove(instanceId) }, onUpdated = { onMediaControlAddedOrUpdated(it, commonModel) }, + isMediaFromRec = commonModel.isMediaFromRec ) .also { mediaControlByInstanceId[instanceId] = it } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt index aeaa82ed7244..a96d75c9ed30 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt @@ -32,6 +32,7 @@ sealed class MediaCommonViewModel { override val onAdded: (MediaCommonViewModel) -> Unit, override val onRemoved: (Boolean) -> Unit, override val onUpdated: (MediaCommonViewModel) -> Unit, + val isMediaFromRec: Boolean = false, ) : MediaCommonViewModel() data class MediaRecommendations( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index bc364c36a298..1944f072e7dd 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) } } } @@ -283,16 +273,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 +295,7 @@ class MediaControlViewModel( private fun toSemanticActionViewModel( model: MediaControlModel, - mediaAction: MediaAction, + mediaAction: MediaAction?, buttonId: Int, canShowScrubbingTimeViews: Boolean ): MediaActionViewModel { @@ -312,9 +303,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 +317,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 +357,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/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index 88a5f78e407d..061e7ecfa4a3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -48,7 +48,7 @@ public class MediaDreamSentinel implements CoreStartable { } @Override - public void onMediaDataRemoved(@NonNull String key) { + public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) { final boolean hasActiveMedia = mMediaDataManager.hasActiveMedia(); if (DEBUG) { Log.d(TAG, "onMediaDataRemoved(" + key + "), mAdded=" + mAdded + ", hasActiveMedia=" diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt index 89e4760615f0..a144dc2fc998 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt @@ -29,6 +29,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICA import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED +import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags import dagger.Lazy import javax.inject.Inject @@ -48,7 +49,7 @@ constructor( * Returns an override value for the given [flag] or `null` if the scene framework isn't enabled * or if the flag value doesn't need to be overridden. */ - fun flagValueOverride(flag: Int): Boolean? { + fun flagValueOverride(@SystemUiStateFlags flag: Long): Boolean? { if (!SceneContainerFlag.isEnabled) { return null } @@ -79,7 +80,7 @@ constructor( * to be overridden by the scene framework. */ val EvaluatorByFlag = - mapOf<Int, (SceneContainerPluginState) -> Boolean>( + mapOf<Long, (SceneContainerPluginState) -> Boolean>( SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it.scene != Scenes.Gone }, SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java index 2dd2327e5387..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/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/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index d26ae0a4dac8..5d35a69be910 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -42,6 +42,7 @@ import android.text.format.DateUtils; import android.util.Log; import android.view.IWindowManager; import android.view.WindowManagerGlobal; +import android.widget.Button; import android.widget.Switch; import androidx.annotation.Nullable; @@ -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(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 2a726c2835d9..24b7a011f093 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -19,6 +19,8 @@ import static android.os.PowerWhitelistManager.REASON_TILE_ONCLICK; import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT; +import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix; + import android.app.ActivityManager; import android.app.compat.CompatChanges; import android.content.BroadcastReceiver; @@ -88,6 +90,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private static final int MSG_ON_REMOVED = 1; private static final int MSG_ON_CLICK = 2; private static final int MSG_ON_UNLOCK_COMPLETE = 3; + private static final int MSG_ON_STOP_LISTENING = 4; // Bind retry control. private static final int MAX_BIND_RETRIES = 5; @@ -368,6 +371,16 @@ public class TileLifecycleManager extends BroadcastReceiver implements onUnlockComplete(); } } + if (qsCustomTileClickGuaranteedBugFix()) { + if (queue.contains(MSG_ON_STOP_LISTENING)) { + if (mDebug) Log.d(TAG, "Handling pending onStopListening " + getComponent()); + if (mListening) { + onStopListening(); + } else { + Log.w(TAG, "Trying to stop listening when not listening " + getComponent()); + } + } + } if (queue.contains(MSG_ON_REMOVED)) { if (mDebug) Log.d(TAG, "Handling pending onRemoved " + getComponent()); if (mListening) { @@ -586,10 +599,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements @Override public void onStopListening() { - if (mDebug) Log.d(TAG, "onStopListening " + getComponent()); - mListening = false; - if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) { - handleDeath(); + if (qsCustomTileClickGuaranteedBugFix() && hasPendingClick()) { + Log.d(TAG, "Enqueue stop listening"); + queueMessage(MSG_ON_STOP_LISTENING); + } else { + if (mDebug) Log.d(TAG, "onStopListening " + getComponent()); + mListening = false; + if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) { + handleDeath(); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index f8bf0a684506..6bc5095ed1ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -15,6 +15,8 @@ */ package com.android.systemui.qs.external; +import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix; + import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -37,6 +39,7 @@ import com.android.systemui.settings.UserTracker; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; /** * Manages the priority which lets {@link TileServices} make decisions about which tiles @@ -72,6 +75,8 @@ public class TileServiceManager { private boolean mPendingBind = true; private boolean mStarted = false; + private final AtomicBoolean mListeningFromRequest = new AtomicBoolean(false); + TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory, CustomTileAddedRepository customTileAddedRepository) { @@ -159,15 +164,30 @@ public class TileServiceManager { } } + void onStartListeningFromRequest() { + mListeningFromRequest.set(true); + mStateManager.onStartListening(); + } + public void setLastUpdate(long lastUpdate) { mLastUpdate = lastUpdate; if (mBound && isActiveTile()) { - mStateManager.onStopListening(); - setBindRequested(false); + if (qsCustomTileClickGuaranteedBugFix()) { + if (mListeningFromRequest.compareAndSet(true, false)) { + stopListeningAndUnbind(); + } + } else { + stopListeningAndUnbind(); + } } mServices.recalculateBindAllowance(); } + private void stopListeningAndUnbind() { + mStateManager.onStopListening(); + setBindRequested(false); + } + public void handleDestroy() { setBindAllowed(false); mServices.getContext().unregisterReceiver(mUninstallReceiver); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 8278c790226b..d457e88fcf14 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -15,6 +15,8 @@ */ package com.android.systemui.qs.external; +import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix; + import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -222,9 +224,13 @@ public class TileServices extends IQSService.Stub { return; } service.setBindRequested(true); - try { - service.getTileService().onStartListening(); - } catch (RemoteException e) { + if (qsCustomTileClickGuaranteedBugFix()) { + service.onStartListeningFromRequest(); + } else { + try { + service.getTileService().onStartListening(); + } catch (RemoteException e) { + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index b515ce07cc02..278352c6f69b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -28,6 +28,7 @@ import com.android.systemui.log.ConstantStringsLoggerImpl import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.log.dagger.QSConfigLog import com.android.systemui.log.dagger.QSLog @@ -56,6 +57,9 @@ constructor( fun d(@CompileTimeConstant msg: String, arg: Any) { buffer.log(TAG, DEBUG, { str1 = arg.toString() }, { "$msg: $str1" }) } + fun i(@CompileTimeConstant msg: String, arg: Any) { + buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" }) + } fun logTileAdded(tileSpec: String) { buffer.log(TAG, DEBUG, { str1 = tileSpec }, { "[$str1] Tile added" }) diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index e3ba36fe6e32..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,12 +16,23 @@ package com.android.systemui.qs.panels.dagger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository +import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl import com.android.systemui.qs.panels.data.repository.IconTilesRepository import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl +import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor +import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor +import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor +import com.android.systemui.qs.panels.shared.model.GridConsistencyLog import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.panels.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.Provides @@ -31,18 +42,71 @@ import dagger.multibindings.IntoSet interface PanelsModule { @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository + @Binds + fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository + + @Binds + fun bindDefaultGridConsistencyInteractor( + impl: NoopGridConsistencyInteractor + ): GridTypeConsistencyInteractor + companion object { @Provides + @SysUISingleton + @GridConsistencyLog + fun providesGridConsistencyLog(factory: LogBufferFactory): LogBuffer { + return factory.create("GridConsistencyLog", 50) + } + + @Provides @IntoSet fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> { return Pair(InfiniteGridLayoutType, gridLayout) } @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/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt index 92f87e78f090..e581bfceb18f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt @@ -19,20 +19,20 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow /** Repository for retrieving the list of [TileSpec] to be displayed as icons. */ interface IconTilesRepository { - val iconTilesSpecs: Flow<Set<TileSpec>> + val iconTilesSpecs: StateFlow<Set<TileSpec>> } @SysUISingleton class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository { - /** Set of toggleable tiles that are suitable for being shown as an icon. */ - override val iconTilesSpecs: Flow<Set<TileSpec>> = - flowOf( + private val _iconTilesSpecs = + MutableStateFlow( setOf( TileSpec.create("airplane"), TileSpec.create("battery"), @@ -50,4 +50,7 @@ class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository { TileSpec.create("rotation") ) ) + + /** Set of toggleable tiles that are suitable for being shown as an icon. */ + override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt new file mode 100644 index 000000000000..43ccdf663478 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +@SysUISingleton +class InfiniteGridSizeRepository @Inject constructor() { + // Number of columns in the narrowest state for consistency + private val _columns = MutableStateFlow(4) + val columns: StateFlow<Int> = _columns.asStateFlow() +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt new file mode 100644 index 000000000000..7732092d6bcf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.qs.panels.shared.model.GridConsistencyLog +import com.android.systemui.qs.panels.shared.model.GridLayoutType +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +@SysUISingleton +class GridConsistencyInteractor +@Inject +constructor( + private val gridLayoutTypeInteractor: GridLayoutTypeInteractor, + private val currentTilesInteractor: CurrentTilesInteractor, + private val consistencyInteractors: + Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>, + private val defaultConsistencyInteractor: GridTypeConsistencyInteractor, + @GridConsistencyLog private val logBuffer: LogBuffer, + @Application private val applicationScope: CoroutineScope, +) { + fun start() { + applicationScope.launch { + gridLayoutTypeInteractor.layout.collectLatest { type -> + val consistencyInteractor = + consistencyInteractors[type] ?: defaultConsistencyInteractor + currentTilesInteractor.currentTiles + .map { tiles -> tiles.map { it.spec } } + .collectLatest { tiles -> + val newTiles = consistencyInteractor.reconcileTiles(tiles) + if (newTiles != tiles) { + currentTilesInteractor.setTiles(newTiles) + logChange(newTiles) + } + } + } + } + } + + private fun logChange(tiles: List<TileSpec>) { + logBuffer.log( + LOG_BUFFER_CURRENT_TILES_CHANGE_TAG, + LogLevel.DEBUG, + { str1 = tiles.toString() }, + { "Tiles reordered: $str1" } + ) + } + + private companion object { + const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/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/shared/model/GridConsistencyLog.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt new file mode 100644 index 000000000000..884cde35a1aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.shared.model + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class GridConsistencyLog() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/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 index 5c17fd1bf94e..3bda7757f8e5 100644 --- 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 @@ -20,9 +20,9 @@ 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.collectAsState 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 @@ -30,8 +30,8 @@ fun EditMode( viewModel: EditModeViewModel, modifier: Modifier = Modifier, ) { - val gridLayout by viewModel.gridLayout.collectAsState() - val tiles by viewModel.tiles.collectAsState(emptyList()) + val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() + val tiles by viewModel.tiles.collectAsStateWithLifecycle(emptyList()) BackHandler { viewModel.stopEditing() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt index 6539cf35b073..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,89 +16,31 @@ package com.android.systemui.qs.panels.ui.compose -import android.graphics.drawable.Animatable -import android.text.TextUtils -import androidx.appcompat.content.res.AppCompatResources -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.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.basicMarquee -import androidx.compose.foundation.clickable -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.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 -import androidx.compose.runtime.rememberUpdatedState -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.graphics.ColorFilter -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.integerResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.onClick -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.stateDescription -import androidx.compose.ui.unit.dp -import com.android.compose.modifiers.background -import com.android.compose.theme.colorAttr -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.ui.compose.Icon -import com.android.systemui.common.ui.compose.load +import 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.ui.viewmodel.ActiveTileColorAttributes -import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions +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.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.Companion.POSITION_AT_END import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.mapLatest @SysUISingleton -class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: IconTilesInteractor) : - GridLayout { - - private object TileType +class InfiniteGridLayout +@Inject +constructor( + private val iconTilesInteractor: IconTilesInteractor, + private val gridSizeInteractor: InfiniteGridSizeInteractor +) : GridLayout { @Composable override fun TileGrid( @@ -110,10 +52,10 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic tiles.forEach { it.startListening(token) } onDispose { tiles.forEach { it.stopListening(token) } } } - val iconTilesSpecs by - iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet()) + val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() + val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() - TileLazyGrid(modifier) { + TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { items( tiles.size, span = { index -> @@ -134,44 +76,6 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic } } - @OptIn(ExperimentalCoroutinesApi::class) - @Composable - private fun Tile( - tile: TileViewModel, - iconOnly: Boolean, - modifier: Modifier, - ) { - val state: TileUiState by - tile.state - .mapLatest { it.toUiState() } - .collectAsState(initial = tile.currentState.toUiState()) - val context = LocalContext.current - - Row( - modifier = modifier.clickable { tile.onClick(null) }.tileModifier(state.colors), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = tileHorizontalArrangement(iconOnly) - ) { - 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 override fun EditTileGrid( tiles: List<EditTileViewModel>, @@ -179,258 +83,16 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic 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, POSITION_AT_END) - } - val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet()) - val isIconOnly: (TileSpec) -> Boolean = - remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } } - - TileLazyGrid(modifier = modifier) { - // 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 { - "" - } - - 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)) - } - } - } - } + val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() + val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() - @Composable - private 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 - private 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, - ) - } - } - - private enum class ClickAction { - ADD, - REMOVE, - } -} - -@OptIn(ExperimentalAnimationGraphicsApi::class) -@Composable -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 Icon.Loaded -> icon.drawable - is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res) - } - } - if (loadedDrawable !is Animatable) { - Icon( - icon = icon, - tint = color, + DefaultEditTileGrid( + tiles = tiles, + iconOnlySpecs = iconOnlySpecs, + columns = GridCells.Fixed(columns), modifier = modifier, + onAddTile = onAddTile, + onRemoveTile = onRemoveTile, ) - } else if (icon is Icon.Resource) { - val image = AnimatedImageVector.animatedVectorResource(id = icon.res) - 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, - colorFilter = ColorFilter.tint(color = color), - modifier = modifier - ) - } -} - -@Composable -private fun TileLazyGrid( - modifier: Modifier = Modifier, - content: LazyGridScope.() -> Unit, -) { - LazyVerticalGrid( - columns = - GridCells.Fixed(integerResource(R.integer.quick_settings_infinite_grid_num_columns)), - verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), - horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), - modifier = modifier, - content = content, - ) -} - -@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/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 new file mode 100644 index 000000000000..eb45110533a4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 android.graphics.drawable.Animatable +import android.text.TextUtils +import androidx.appcompat.content.res.AppCompatResources +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.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 +import androidx.compose.ui.draw.clip +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 +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( + tile: TileViewModel, + iconOnly: Boolean, + modifier: Modifier, +) { + val state: TileUiState by + tile.state + .mapLatest { it.toUiState() } + .collectAsStateWithLifecycle(tile.currentState.toUiState()) + val context = LocalContext.current + + Expandable( + color = colorAttr(state.colors.background), + shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)), + ) { + 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 { + "" + } + + 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: 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 Icon.Loaded -> icon.drawable + is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res) + } + } + if (loadedDrawable !is Animatable) { + Icon( + icon = icon, + tint = color, + modifier = modifier, + ) + } else if (icon is Icon.Resource) { + val image = AnimatedImageVector.animatedVectorResource(id = icon.res) + 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, + colorFilter = ColorFilter.tint(color = color), + modifier = modifier + ) + } +} + +@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 2f32d7264350..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 @@ -17,15 +17,15 @@ package com.android.systemui.qs.panels.ui.compose 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.systemui.qs.panels.ui.viewmodel.TileGridViewModel @Composable fun TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { - val gridLayout by viewModel.gridLayout.collectAsState() - val tiles by viewModel.tileViewModels.collectAsState(emptyList()) + val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() + val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList()) gridLayout.TileGrid(tiles, modifier) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt index af1d195516e7..c8fbeb50b039 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt @@ -18,6 +18,8 @@ package com.android.systemui.qs.pipeline.domain.startable import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.flags.NewQsUI +import com.android.systemui.qs.panels.domain.interactor.GridConsistencyInteractor import com.android.systemui.qs.pipeline.domain.interactor.AccessibilityTilesInteractor import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor @@ -34,6 +36,7 @@ constructor( private val autoAddInteractor: AutoAddInteractor, private val featureFlags: QSPipelineFlagsRepository, private val restoreReconciliationInteractor: RestoreReconciliationInteractor, + private val gridConsistencyInteractor: GridConsistencyInteractor, ) : CoreStartable { override fun start() { @@ -41,6 +44,10 @@ constructor( accessibilityTilesInteractor.init(currentTilesInteractor) autoAddInteractor.init(currentTilesInteractor) restoreReconciliationInteractor.start() + + if (NewQsUI.isEnabled) { + gridConsistencyInteractor.start() + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index c24113f14f00..56588ff75a5a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -55,6 +55,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.graph.SignalDrawable; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.Expandable; @@ -632,12 +633,23 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } public static class DrawableIcon extends Icon { + protected final Drawable mDrawable; protected final Drawable mInvisibleDrawable; + private static final String TAG = "QSTileImpl"; public DrawableIcon(Drawable drawable) { mDrawable = drawable; - mInvisibleDrawable = drawable.getConstantState().newDrawable(); + Drawable.ConstantState nullableConstantState = drawable.getConstantState(); + if (nullableConstantState == null) { + if (!(drawable instanceof SignalDrawable)) { + Log.w(TAG, "DrawableIcon: drawable has null ConstantState" + + " and is not a SignalDrawable"); + } + mInvisibleDrawable = drawable; + } else { + mInvisibleDrawable = nullableConstantState.newDrawable(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 206879905782..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; @@ -32,9 +34,12 @@ import android.telephony.TelephonyManager; 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; @@ -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( @@ -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/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 9af34f6c9918..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; @@ -33,11 +34,14 @@ import android.text.TextUtils; import android.util.Log; 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; @@ -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( @@ -110,6 +119,24 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { @Override 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(expandable); } else { 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/impl/location/domain/interactor/LocationTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt index d1c80309a1cc..bd2f2c987ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt @@ -17,15 +17,15 @@ package com.android.systemui.qs.tiles.impl.location.domain.interactor import android.os.UserHandle -import com.android.systemui.common.coroutine.ConflatedCallbackFlow import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel import com.android.systemui.statusbar.policy.LocationController +import com.android.systemui.util.kotlin.isLocationEnabledFlow import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map /** Observes location state changes providing the [LocationTileModel]. */ class LocationTileDataInteractor @@ -38,19 +38,7 @@ constructor( user: UserHandle, triggers: Flow<DataUpdateTrigger> ): Flow<LocationTileModel> = - ConflatedCallbackFlow.conflatedCallbackFlow { - val initialValue = locationController.isLocationEnabled - trySend(LocationTileModel(initialValue)) - - val callback = - object : LocationController.LocationChangeCallback { - override fun onLocationSettingsChanged(locationEnabled: Boolean) { - trySend(LocationTileModel(locationEnabled)) - } - } - locationController.addCallback(callback) - awaitClose { locationController.removeCallback(callback) } - } + locationController.isLocationEnabledFlow().map { LocationTileModel(it) } override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt new file mode 100644 index 000000000000..88bd224881b5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.domain.interactor + +import android.content.Context +import android.hardware.display.ColorDisplayManager +import android.os.UserHandle +import com.android.systemui.accessibility.data.repository.NightDisplayRepository +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.util.time.DateFormatUtil +import java.time.LocalTime +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Observes screen record state changes providing the [NightDisplayTileModel]. */ +class NightDisplayTileDataInteractor +@Inject +constructor( + @Application private val context: Context, + private val dateFormatUtil: DateFormatUtil, + private val nightDisplayRepository: NightDisplayRepository, +) : QSTileDataInteractor<NightDisplayTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<NightDisplayTileModel> = + nightDisplayRepository.nightDisplayState(user).map { + generateModel( + it.autoMode, + it.isActivated, + it.startTime, + it.endTime, + it.shouldForceAutoMode, + it.locationEnabled + ) + } + + /** This checks resources and there fore does not make a binder call. */ + override fun availability(user: UserHandle): Flow<Boolean> = + flowOf(ColorDisplayManager.isNightDisplayAvailable(context)) + + private fun generateModel( + autoMode: Int, + isNightDisplayActivated: Boolean, + customStartTime: LocalTime?, + customEndTime: LocalTime?, + shouldForceAutoMode: Boolean, + locationEnabled: Boolean, + ): NightDisplayTileModel { + if (autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) { + return NightDisplayTileModel.AutoModeTwilight( + isNightDisplayActivated, + shouldForceAutoMode, + locationEnabled, + ) + } else if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) { + return NightDisplayTileModel.AutoModeCustom( + isNightDisplayActivated, + shouldForceAutoMode, + customStartTime, + customEndTime, + dateFormatUtil.is24HourFormat, + ) + } else { // auto mode off + return NightDisplayTileModel.AutoModeOff(isNightDisplayActivated, shouldForceAutoMode) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt new file mode 100644 index 000000000000..5cee8c49527d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.domain.interactor + +import android.content.Intent +import android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME +import android.provider.Settings +import com.android.systemui.accessibility.data.repository.NightDisplayRepository +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +/** Handles night display tile clicks. */ +class NightDisplayTileUserActionInteractor +@Inject +constructor( + private val nightDisplayRepository: NightDisplayRepository, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val qsLogger: QSTileLogger, +) : QSTileUserActionInteractor<NightDisplayTileModel> { + override suspend fun handleInput(input: QSTileInput<NightDisplayTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + // Enroll in forced auto mode if eligible. + if (data.isEnrolledInForcedNightDisplayAutoMode) { + nightDisplayRepository.setNightDisplayAutoMode(AUTO_MODE_CUSTOM_TIME, user) + qsLogger.logInfo(spec, "Enrolled in forced night display auto mode") + } + nightDisplayRepository.setNightDisplayActivated(!data.isActivated, user) + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.expandable, + Intent(Settings.ACTION_NIGHT_DISPLAY_SETTINGS) + ) + } + } + } + + companion object { + val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt new file mode 100644 index 000000000000..6b1bd5bc3512 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.domain.model + +import java.time.LocalTime + +/** Data model for night display tile */ +sealed interface NightDisplayTileModel { + val isActivated: Boolean + val isEnrolledInForcedNightDisplayAutoMode: Boolean + data class AutoModeTwilight( + override val isActivated: Boolean, + override val isEnrolledInForcedNightDisplayAutoMode: Boolean, + val isLocationEnabled: Boolean + ) : NightDisplayTileModel + data class AutoModeCustom( + override val isActivated: Boolean, + override val isEnrolledInForcedNightDisplayAutoMode: Boolean, + val startTime: LocalTime?, + val endTime: LocalTime?, + val is24HourFormat: Boolean + ) : NightDisplayTileModel + data class AutoModeOff( + override val isActivated: Boolean, + override val isEnrolledInForcedNightDisplayAutoMode: Boolean + ) : NightDisplayTileModel +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt new file mode 100644 index 000000000000..5c2dcfcaf37c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.ui + +import android.content.res.Resources +import android.service.quicksettings.Tile +import android.text.TextUtils +import androidx.annotation.StringRes +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import java.time.DateTimeException +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import javax.inject.Inject + +/** Maps [NightDisplayTileModel] to [QSTileState]. */ +class NightDisplayTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, + private val logger: QSTileLogger, +) : QSTileDataToStateMapper<NightDisplayTileModel> { + override fun map(config: QSTileConfig, data: NightDisplayTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + label = resources.getString(R.string.quick_settings_night_display_label) + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + sideViewIcon = QSTileState.SideViewIcon.None + + if (data.isActivated) { + activationState = QSTileState.ActivationState.ACTIVE + val loadedIcon = + Icon.Loaded( + resources.getDrawable(R.drawable.qs_nightlight_icon_on, theme), + contentDescription = null + ) + icon = { loadedIcon } + } else { + activationState = QSTileState.ActivationState.INACTIVE + val loadedIcon = + Icon.Loaded( + resources.getDrawable(R.drawable.qs_nightlight_icon_off, theme), + contentDescription = null + ) + icon = { loadedIcon } + } + + secondaryLabel = getSecondaryLabel(data, resources) + + contentDescription = + if (TextUtils.isEmpty(secondaryLabel)) label + else TextUtils.concat(label, ", ", secondaryLabel) + } + + private fun getSecondaryLabel( + data: NightDisplayTileModel, + resources: Resources + ): CharSequence? { + when (data) { + is NightDisplayTileModel.AutoModeTwilight -> { + if (!data.isLocationEnabled) { + return null + } else { + return resources.getString( + if (data.isActivated) + R.string.quick_settings_night_secondary_label_until_sunrise + else R.string.quick_settings_night_secondary_label_on_at_sunset + ) + } + } + is NightDisplayTileModel.AutoModeOff -> { + val subtitleArray = resources.getStringArray(R.array.tile_states_night) + return subtitleArray[ + if (data.isActivated) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE] + } + is NightDisplayTileModel.AutoModeCustom -> { + // User-specified time, approximated to the nearest hour. + @StringRes val toggleTimeStringRes: Int + val toggleTime: LocalTime + if (data.isActivated) { + toggleTime = data.endTime ?: return null + toggleTimeStringRes = R.string.quick_settings_secondary_label_until + } else { + toggleTime = data.startTime ?: return null + toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at + } + + try { + val formatter = if (data.is24HourFormat) formatter24Hour else formatter12Hour + val formatArg = formatter.format(toggleTime) + return resources.getString(toggleTimeStringRes, formatArg) + } catch (exception: DateTimeException) { + logger.logWarning(spec, exception.message.toString()) + return null + } + } + } + } + + private companion object { + val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a") + val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") + val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index b88c1e566c4a..5346b237111f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -201,6 +201,7 @@ constructor( qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) } override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId + override fun getTileLabel(): CharSequence = with(qsTileViewModel.config.uiConfig) { when (this) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 17698f9d8647..6cf2e52ff3d9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -29,7 +29,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter @@ -63,7 +63,7 @@ constructor( private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, private val footerActionsController: FooterActionsController, sceneBackInteractor: SceneBackInteractor, - val mediaDataManager: MediaDataManager, + val mediaCarouselInteractor: MediaCarouselInteractor, ) { private val backScene: StateFlow<SceneKey> = sceneBackInteractor.backScene @@ -101,6 +101,8 @@ constructor( ), ) + val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation + private fun destinationScenes( isUnlocked: Boolean, canSwipeToDismiss: Boolean?, @@ -143,9 +145,4 @@ constructor( } return footerActionsViewModelFactory.create(lifecycleOwner) } - - fun isMediaVisible(): Boolean { - // TODO(b/328207006): use new pipeline to handle updates while visible - return mediaDataManager.hasAnyMediaOrRecommendation() - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index faf2bbc1ba3b..0327ec760ace 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -108,15 +108,18 @@ 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; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -128,8 +131,6 @@ import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Provider; -import dagger.Lazy; - /** * Class to send information from overview to launcher with a binder. */ @@ -414,7 +415,13 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void toggleNotificationPanel() { verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () -> - mCommandQueue.togglePanel()); + mCommandQueue.toggleNotificationsPanel()); + } + + @Override + public void toggleQuickSettingsPanel() { + verifyCallerAndClearCallingIdentityPostMain("toggleQuickSettingsPanel", () -> + mCommandQueue.toggleQuickSettingsPanel()); } private boolean verifyCaller(String reason) { @@ -765,7 +772,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } - private void notifySystemUiStateFlags(int flags) { + private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) { if (SysUiState.DEBUG) { Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy=" + mOverviewProxy + " flags=" + flags); diff --git a/packages/SystemUI/src/com/android/systemui/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/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index eabc42b02665..3e2c6306467f 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -21,6 +21,7 @@ package com.android.systemui.scene.data.repository import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSource @@ -36,6 +37,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn +@SysUISingleton /** Source of truth for scene framework application state. */ class SceneContainerRepository @Inject diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 08efe39d7674..0d0f6e069d2d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -55,6 +55,18 @@ constructor( private val deviceUnlockedInteractor: DeviceUnlockedInteractor, ) { + interface OnSceneAboutToChangeListener { + + /** + * Notifies that the scene is about to change to [toScene]. + * + * The implementation can choose to consume the [sceneState] to prepare the incoming scene. + */ + fun onSceneAboutToChange(toScene: SceneKey, sceneState: Any?) + } + + private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>() + /** * The current scene. * @@ -149,6 +161,10 @@ constructor( return repository.allSceneKeys() } + fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) { + onSceneAboutToChangeListener.add(processor) + } + /** * Requests a scene change to the given scene. * @@ -161,6 +177,7 @@ constructor( toScene: SceneKey, loggingReason: String, transitionKey: TransitionKey? = null, + sceneState: Any? = null, ) { val currentSceneKey = currentScene.value if ( @@ -180,6 +197,7 @@ constructor( isInstant = false, ) + onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(toScene, sceneState) } repository.changeScene(toScene, transitionKey) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 4a6427794def..3ce12dddb08e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -234,7 +234,7 @@ constructor( bouncerInteractor.onImeHiddenByUser.collectLatest { if (sceneInteractor.currentScene.value == Scenes.Bouncer) { sceneInteractor.changeScene( - toScene = Scenes.Lockscreen, + toScene = Scenes.Lockscreen, // TODO(b/336581871): add sceneState? loggingReason = "IME hidden", ) } @@ -252,6 +252,7 @@ constructor( when { isAnySimLocked -> { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Bouncer, loggingReason = "Need to authenticate locked SIM card." ) @@ -259,6 +260,7 @@ constructor( unlockStatus.isUnlocked && deviceEntryInteractor.canSwipeToEnter.value == false -> { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Gone, loggingReason = "All SIM cards unlocked and device already unlocked and " + @@ -267,6 +269,7 @@ constructor( } else -> { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Lockscreen, loggingReason = "All SIM cards unlocked and device still locked" + @@ -325,7 +328,8 @@ constructor( Scenes.Gone to "device was unlocked in Bouncer scene" } else { val prevScene = previousScene.value - (prevScene ?: Scenes.Gone) to + (prevScene + ?: Scenes.Gone) to "device was unlocked in Bouncer scene, from sceneKey=$prevScene" } isOnLockscreen -> @@ -364,6 +368,7 @@ constructor( powerInteractor.isAsleep.collect { isAsleep -> if (isAsleep) { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Lockscreen, loggingReason = "device is starting to sleep", ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt index b91dd0451808..ef393e4858e2 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt @@ -24,6 +24,8 @@ import com.android.compose.animation.scene.TransitionKey * These are the subset of transitions that can be referenced by key when asking for a scene change. */ object TransitionKeys { + /** Reference to the gone/lockscreen to shade transition with split shade enabled. */ + val ToSplitShade = TransitionKey("GoneToSplitShade") /** Reference to a scene transition that can collapse the shade scene instantly. */ val CollapseShadeInstantly = TransitionKey("CollapseShadeInstantly") diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index 259a8bfef175..b971781acd63 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -56,9 +56,8 @@ class SceneWindowRootView( } // TODO(b/298525212): remove once Compose exposes window inset bounds. - override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? { - val insets = super.onApplyWindowInsets(windowInsets) - this.windowInsets.value = insets - return insets + override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets { + this.windowInsets.value = windowInsets + return windowInsets } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 78704e16b586..c20d577d66a1 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -198,7 +198,7 @@ object SceneWindowRootViewBinder { private fun getDisplayWidth(context: Context): Dp { val point = Point() checkNotNull(context.display).getRealSize(point) - return point.x.dp + return point.x.toDp(context) } // TODO(b/298525212): remove once Compose exposes window inset bounds. diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt index b0af7f9ce072..99118bcfc322 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt @@ -24,6 +24,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject @@ -70,10 +71,11 @@ constructor( )] = UserActionResult(Scenes.QuickSettingsShade) } + val downSceneKey = + if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade + val downTransitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split } this[Swipe(direction = SwipeDirection.Down)] = - UserActionResult( - if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade - ) + UserActionResult(downSceneKey, downTransitionKey) } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/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 ef1d87da47be..a1dd4157d996 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt @@ -40,6 +40,7 @@ import dagger.assisted.AssistedInject */ interface ScreenshotActionsProvider { fun onScrollChipReady(onClick: Runnable) + fun onScrollChipInvalidated() fun setCompletedScreenshot(result: ScreenshotSavedResult) /** @@ -67,6 +68,8 @@ constructor( @Assisted val requestId: String, @Assisted val actionExecutor: ActionExecutor, ) : ScreenshotActionsProvider { + private var addedScrollChip = false + private var onScrollClick: Runnable? = null private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null private var result: ScreenshotSavedResult? = null @@ -122,18 +125,26 @@ constructor( } override fun onScrollChipReady(onClick: Runnable) { - viewModel.addAction( - ActionButtonAppearance( - AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll), - context.resources.getString(R.string.screenshot_scroll_label), - context.resources.getString(R.string.screenshot_scroll_label), - ), - showDuringEntrance = true, - ) { - onClick.run() + onScrollClick = onClick + if (!addedScrollChip) { + viewModel.addAction( + ActionButtonAppearance( + AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll), + context.resources.getString(R.string.screenshot_scroll_label), + context.resources.getString(R.string.screenshot_scroll_label), + ), + showDuringEntrance = true, + ) { + onScrollClick?.run() + } + addedScrollChip = true } } + override fun onScrollChipInvalidated() { + onScrollClick = null + } + override fun setCompletedScreenshot(result: ScreenshotSavedResult) { if (this.result != null) { Log.e(TAG, "Got a second completed screenshot for existing request!") diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index bd90de230d5f..9ad6d0faea88 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -317,9 +317,9 @@ 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; }); @@ -586,7 +586,11 @@ public class ScreenshotController { if (mConfigChanges.applyNewConfig(mContext.getResources())) { // Hide the scroll chip until we know it's available in this // orientation - mViewProxy.hideScrollChip(); + if (screenshotShelfUi2()) { + mActionsProvider.onScrollChipInvalidated(); + } else { + mViewProxy.hideScrollChip(); + } // Delay scroll capture eval a bit to allow the underlying activity // to set up in the new orientation. mScreenshotHandler.postDelayed( @@ -619,9 +623,11 @@ public class ScreenshotController { (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)); @@ -653,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); 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 412b08905ae9..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 @@ -58,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 @@ -69,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 } @@ -78,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) } @@ -175,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() {} @@ -215,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/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 06e88f46c5f1..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 = @@ -46,6 +58,14 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { 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, @@ -96,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) @@ -114,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 @@ -131,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 diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt index 916d50fd75b1..969cf482be90 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt @@ -22,6 +22,8 @@ import android.graphics.Insets import android.graphics.Rect import android.graphics.Region import android.util.AttributeSet +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -30,7 +32,6 @@ import android.widget.FrameLayout import android.widget.ImageView import com.android.systemui.res.R import com.android.systemui.screenshot.FloatingWindowUtil -import kotlin.math.max class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { @@ -39,11 +40,50 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : private lateinit var screenshotStatic: ViewGroup var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null + var userInteractionCallback: (() -> Unit)? = null + private val displayMetrics = context.resources.displayMetrics private val tmpRect = Rect() private lateinit var actionsContainerBackground: View + private lateinit var actionsContainer: View private lateinit var dismissButton: View + // Prepare an internal `GestureDetector` to determine when we can initiate a touch-interception + // session (with the client's provided `onTouchInterceptListener`). We delegate out to their + // listener only for gestures that can't be handled by scrolling our `actionsContainer`. + private val gestureDetector = + GestureDetector( + context, + object : SimpleOnGestureListener() { + override fun onScroll( + ev1: MotionEvent?, + ev2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + actionsContainer.getBoundsOnScreen(tmpRect) + val touchedInActionsContainer = + tmpRect.contains(ev2.rawX.toInt(), ev2.rawY.toInt()) + val canHandleInternallyByScrolling = + touchedInActionsContainer + && actionsContainer.canScrollHorizontally(distanceX.toInt()) + return !canHandleInternallyByScrolling + } + } + ) + + init { + + // Delegate to the client-provided `onTouchInterceptListener` if we've already initiated + // touch-interception. + setOnTouchListener({ _: View, ev: MotionEvent -> + userInteractionCallback?.invoke() + onTouchInterceptListener?.invoke(ev) ?: false + }) + + gestureDetector.setIsLongpressEnabled(false) + } + override fun onFinishInflate() { super.onFinishInflate() // Get focus so that the key events go to the layout. @@ -52,7 +92,15 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : blurredScreenshotPreview = requireViewById(R.id.screenshot_preview_blur) screenshotStatic = requireViewById(R.id.screenshot_static) actionsContainerBackground = requireViewById(R.id.actions_container_background) + actionsContainer = requireViewById(R.id.actions_container) dismissButton = requireViewById(R.id.screenshot_dismiss_button) + + // Configure to extend the timeout during ongoing gestures (i.e. scrolls) that are already + // being handled by our child views. + actionsContainer.setOnTouchListener({ _: View, ev: MotionEvent -> + userInteractionCallback?.invoke() + false + }) } fun getTouchRegion(gestureInsets: Insets): Region { @@ -79,6 +127,18 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : val inPortrait = orientation == Configuration.ORIENTATION_PORTRAIT val cutout = insets.displayCutout val navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()) + + // When honoring the navbar or other obstacle offsets, include some extra padding above + // the inset itself. + val verticalPadding = + mContext.resources.getDimensionPixelOffset(R.dimen.screenshot_shelf_vertical_margin) + + // Minimum bottom padding to always enforce (e.g. if there's no nav bar) + val minimumBottomPadding = + context.resources.getDimensionPixelOffset( + R.dimen.overlay_action_container_minimum_edge_spacing + ) + if (cutout == null) { screenshotStatic.setPadding(0, 0, 0, navBarInsets.bottom) } else { @@ -86,25 +146,41 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : if (inPortrait) { screenshotStatic.setPadding( waterfall.left, - max(cutout.safeInsetTop.toDouble(), waterfall.top.toDouble()).toInt(), + max(cutout.safeInsetTop, waterfall.top), waterfall.right, max( - cutout.safeInsetBottom.toDouble(), - max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble()) - ) - .toInt() + navBarInsets.bottom + verticalPadding, + cutout.safeInsetBottom + verticalPadding, + waterfall.bottom + verticalPadding, + minimumBottomPadding, + ) ) } else { screenshotStatic.setPadding( - max(cutout.safeInsetLeft.toDouble(), waterfall.left.toDouble()).toInt(), + max(cutout.safeInsetLeft, waterfall.left), waterfall.top, - max(cutout.safeInsetRight.toDouble(), waterfall.right.toDouble()).toInt(), - max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble()).toInt() + max(cutout.safeInsetRight, waterfall.right), + max( + navBarInsets.bottom + verticalPadding, + waterfall.bottom + verticalPadding, + minimumBottomPadding, + ) ) } } } + // Max function for two or more params. + private fun max(first: Int, second: Int, vararg items: Int): Int { + var largest = if (first > second) first else second + for (item in items) { + if (item > largest) { + largest = item + } + } + return largest + } + private fun getSwipeRegion(): Region { val swipeRegion = Region() val padding = FloatingWindowUtil.dpToPx(displayMetrics, -1 * TOUCH_PADDING_DP).toInt() @@ -127,10 +203,24 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : private const val TOUCH_PADDING_DP = 12f } + override fun onInterceptHoverEvent(event: MotionEvent): Boolean { + userInteractionCallback?.invoke() + return super.onInterceptHoverEvent(event) + } + override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { - if (onTouchInterceptListener?.invoke(ev) == true) { - return true + userInteractionCallback?.invoke() + + // Let the client-provided listener see all `DOWN` events so that they'll be able to + // interpret the remainder of the gesture, even if interception starts partway-through. + // TODO: is this really necessary? And if we don't go on to start interception, should we + // follow up with `ACTION_CANCEL`? + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + onTouchInterceptListener?.invoke(ev) } - return super.onInterceptTouchEvent(ev) + + // Only allow the client-provided touch interceptor to take over the gesture if our + // top-level `GestureDetector` decides not to scroll the action container. + return gestureDetector.onTouchEvent(ev) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt index 750bd530d9b2..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 @@ -23,8 +23,9 @@ 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) @@ -36,7 +37,19 @@ object ActionButtonViewBinder { // 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 43c0107c12d5..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,22 +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( @@ -52,20 +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) { @@ -85,11 +99,48 @@ 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 -> updateActions( actions, @@ -149,14 +200,14 @@ object ScreenshotShelfViewBinder { val currentView: View? = actionsContainer.getChildAt(index) if (action.id == currentView?.tag) { // Same ID, update the display - ActionButtonViewBinder.bind(currentView, action) + 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) - ActionButtonViewBinder.bind(actionButton, action) + buttonViewBinder.bind(actionButton, action) } } } @@ -179,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/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt index 5f36f73f2135..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,6 +27,10 @@ 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>()) @@ -32,6 +38,10 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager 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 @@ -39,6 +49,14 @@ 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 } @@ -107,11 +125,23 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager _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 { 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 6367d44bd439..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 @@ -50,7 +51,6 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not @@ -69,12 +69,12 @@ class GlanceableHubContainerController constructor( private val communalInteractor: CommunalInteractor, private val communalViewModel: CommunalViewModel, - private val dialogFactory: SystemUIDialogFactory, private val keyguardInteractor: KeyguardInteractor, private val shadeInteractor: ShadeInteractor, private val powerManager: PowerManager, private val communalColors: CommunalColors, private val ambientTouchComponentFactory: AmbientTouchComponent.Factory, + private val communalContent: CommunalContent, @Communal private val dataSourceDelegator: SceneDataSourceDelegator ) : LifecycleOwner { /** The container view for the hub. This will not be initialized until [initView] is called. */ @@ -123,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. + * True if the shade is fully expanded, 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. - * - * Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting]. + * Tracks [ShadeInteractor.isAnyFullyExpanded]. */ private var shadeShowing = false @@ -180,7 +174,7 @@ constructor( viewModel = communalViewModel, colors = communalColors, dataSourceDelegator = dataSourceDelegator, - dialogFactory = dialogFactory, + content = communalContent, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7051d5fda159..6bb30c7b97f4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -141,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; @@ -170,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; @@ -1130,8 +1132,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); @@ -1141,7 +1147,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), @@ -1149,16 +1156,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(), @@ -1169,7 +1177,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(), @@ -1181,7 +1190,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(), @@ -1192,7 +1203,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(), diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index b50a3cd442d3..6efa6334b968 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -49,6 +49,7 @@ 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; @@ -137,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; @@ -225,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, @@ -258,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. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index d2c93da671af..884ccef3a080 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -140,7 +140,7 @@ constructor( private fun animateCollapseShadeInternal() { sceneInteractor.changeScene( - getCollapseDestinationScene(), + getCollapseDestinationScene(), // TODO(b/336581871): add sceneState? "ShadeController.animateCollapseShade", SlightlyFasterShadeCollapse, ) diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt index c9949cdc8ab6..55bd8c6c0834 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt @@ -44,6 +44,7 @@ constructor( } else { Scenes.Shade } + // TODO(b/336581871): add sceneState? sceneInteractor.changeScene(key, "animateCollapseQs") } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index ac76becd1797..e4a2424e1ead 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -27,12 +27,13 @@ import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode @@ -60,7 +61,7 @@ constructor( val shadeHeaderViewModel: ShadeHeaderViewModel, val notifications: NotificationsPlaceholderViewModel, val brightnessMirrorViewModel: BrightnessMirrorViewModel, - val mediaDataManager: MediaDataManager, + val mediaCarouselInteractor: MediaCarouselInteractor, shadeInteractor: ShadeInteractor, private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, private val footerActionsController: FooterActionsController, @@ -108,6 +109,8 @@ constructor( val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode + val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation + /** * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded * slightly, in pixels. @@ -125,11 +128,6 @@ constructor( sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty content clicked") } - fun isMediaVisible(): Boolean { - // TODO(b/296122467): handle updates to carousel visibility while scene is still visible - return mediaDataManager.hasActiveMediaOrRecommendation() - } - private val footerActionsControllerInitialized = AtomicBoolean(false) fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { @@ -152,11 +150,13 @@ constructor( else -> Scenes.Lockscreen } + val upTransitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split } + val down = Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } return buildMap { if (!isCustomizing) { - this[Swipe(SwipeDirection.Up)] = UserActionResult(up) + this[Swipe(SwipeDirection.Up)] = UserActionResult(up, upTransitionKey) } // TODO(b/330200163) Add an else to be able to collapse the shade while customizing down?.let { this[Swipe(SwipeDirection.Down)] = UserActionResult(down) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index d955349ffede..c9126161c40f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -134,7 +134,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_DISMISS_KEYBOARD_SHORTCUTS = 32 << MSG_SHIFT; private static final int MSG_HANDLE_SYSTEM_KEY = 33 << MSG_SHIFT; private static final int MSG_SHOW_GLOBAL_ACTIONS = 34 << MSG_SHIFT; - private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT; + private static final int MSG_TOGGLE_NOTIFICATION_PANEL = 35 << MSG_SHIFT; private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT; private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT; private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT; @@ -180,6 +180,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT; private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT; private static final int MSG_SET_SPLITSCREEN_FOCUS = 81 << MSG_SHIFT; + private static final int MSG_TOGGLE_QUICK_SETTINGS_PANEL = 82 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1; @@ -222,10 +223,33 @@ public class CommandQueue extends IStatusBar.Stub implements */ default void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2, boolean animate) { } + + /** + * Called to expand Notifications panel with animation. + */ default void animateExpandNotificationsPanel() { } + /** + * Called to collapse Notifications panel with animation. + * @param flags Exclusion flags. See {@link FLAG_EXCLUDE_NONE}. + * @param force True to force the operation. + */ default void animateCollapsePanels(int flags, boolean force) { } - default void togglePanel() { } - default void animateExpandSettingsPanel(String obj) { } + + /** + * Called to toggle Notifications panel. + */ + default void toggleNotificationsPanel() { } + + /** + * Called to expand Quick Settings panel with animation. + * @param subPanel subPanel one wish to expand. + */ + default void animateExpandSettingsPanel(String subPanel) { } + + /** + * Called to toggle Quick Settings panel. + */ + default void toggleQuickSettingsPanel() { } /** * Called to notify IME window status changes. @@ -696,10 +720,10 @@ public class CommandQueue extends IStatusBar.Stub implements } } - public void togglePanel() { + public void toggleNotificationsPanel() { synchronized (mLock) { - mHandler.removeMessages(MSG_TOGGLE_PANEL); - mHandler.obtainMessage(MSG_TOGGLE_PANEL, 0, 0).sendToTarget(); + mHandler.removeMessages(MSG_TOGGLE_NOTIFICATION_PANEL); + mHandler.obtainMessage(MSG_TOGGLE_NOTIFICATION_PANEL, 0, 0).sendToTarget(); } } @@ -710,6 +734,13 @@ public class CommandQueue extends IStatusBar.Stub implements } } + public void toggleQuickSettingsPanel() { + synchronized (mLock) { + mHandler.removeMessages(MSG_TOGGLE_QUICK_SETTINGS_PANEL); + mHandler.obtainMessage(MSG_TOGGLE_QUICK_SETTINGS_PANEL, 0, 0).sendToTarget(); + } + } + @Override public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { @@ -1494,9 +1525,9 @@ public class CommandQueue extends IStatusBar.Stub implements mCallbacks.get(i).animateCollapsePanels(msg.arg1, msg.arg2 != 0); } break; - case MSG_TOGGLE_PANEL: + case MSG_TOGGLE_NOTIFICATION_PANEL: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).togglePanel(); + mCallbacks.get(i).toggleNotificationsPanel(); } break; case MSG_EXPAND_SETTINGS: @@ -1504,6 +1535,11 @@ public class CommandQueue extends IStatusBar.Stub implements mCallbacks.get(i).animateExpandSettingsPanel((String) msg.obj); } break; + case MSG_TOGGLE_QUICK_SETTINGS_PANEL: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).toggleQuickSettingsPanel(); + } + break; case MSG_SHOW_IME_BUTTON: args = (SomeArgs) msg.obj; handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 7983db137e76..2446473c3b31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -199,12 +199,14 @@ public class KeyguardIndicationController { protected boolean mPowerPluggedInWired; protected boolean mPowerPluggedInWireless; protected boolean mPowerPluggedInDock; + protected int mChargingSpeed; private boolean mPowerCharged; + /** Whether the battery defender is triggered. */ private boolean mBatteryDefender; + /** Whether the battery defender is triggered with the device plugged. */ private boolean mEnableBatteryDefender; private boolean mIncompatibleCharger; - protected int mChargingSpeed; private int mChargingWattage; private int mBatteryLevel; private boolean mBatteryPresent = true; @@ -1244,7 +1246,7 @@ public class KeyguardIndicationController { mChargingSpeed = status.getChargingSpeed(mContext); mBatteryLevel = status.level; mBatteryPresent = status.present; - mBatteryDefender = status.isBatteryDefender(); + mBatteryDefender = isBatteryDefender(status); // when the battery is overheated, device doesn't charge so only guard on pluggedIn: mEnableBatteryDefender = mBatteryDefender && status.isPluggedIn(); mIncompatibleCharger = status.incompatibleCharger.orElse(false); @@ -1516,6 +1518,11 @@ public class KeyguardIndicationController { return mPowerPluggedIn; } + /** Return true if the device is under the battery defender mode. */ + protected boolean isBatteryDefender(BatteryStatus status) { + return status.isBatteryDefender(); + } + private boolean isCurrentUser(int userId) { return getCurrentUser() == userId; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index d7d373226d5c..5bf2f41ae453 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar; +import static com.android.systemui.Flags.mediaControlsUserInitiatedDismiss; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -175,14 +177,18 @@ public class NotificationMediaManager implements Dumpable { } @Override - public void onMediaDataRemoved(@NonNull String key) { + public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) { + if (mediaControlsUserInitiatedDismiss() && !userInitiated) { + // Dismissing the notification will send the app's deleteIntent, so ignore if + // this was an automatic removal + Log.d(TAG, "Not dismissing " + key + " because it was removed by the system"); + return; + } mNotifPipeline.getAllNotifs() .stream() .filter(entry -> Objects.equals(entry.getKey(), key)) .findAny() .ifPresent(entry -> { - // TODO(b/160713608): "removing" this notification won't happen and - // won't send the 'deleteIntent' if the notification is ongoing. mNotifCollection.dismissNotification(entry, getDismissedByUserStats(entry)); }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 70632d5aa27a..79218ae4ca20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD; +import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE; import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.animation.Animator; @@ -49,6 +50,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteract import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus; import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.SceneInteractor; @@ -108,6 +110,7 @@ public class StatusBarStateControllerImpl implements private final UiEventLogger mUiEventLogger; private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy; private final JavaAdapter mJavaAdapter; + private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy; private final Lazy<ShadeInteractor> mShadeInteractorLazy; private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy; private final Lazy<SceneInteractor> mSceneInteractorLazy; @@ -175,6 +178,7 @@ public class StatusBarStateControllerImpl implements UiEventLogger uiEventLogger, Lazy<InteractionJankMonitor> interactionJankMonitorLazy, JavaAdapter javaAdapter, + Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor, Lazy<ShadeInteractor> shadeInteractorLazy, Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy, Lazy<SceneInteractor> sceneInteractorLazy, @@ -182,6 +186,7 @@ public class StatusBarStateControllerImpl implements mUiEventLogger = uiEventLogger; mInteractionJankMonitorLazy = interactionJankMonitorLazy; mJavaAdapter = javaAdapter; + mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor; mShadeInteractorLazy = shadeInteractorLazy; mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy; mSceneInteractorLazy = sceneInteractorLazy; @@ -193,6 +198,14 @@ public class StatusBarStateControllerImpl implements @Override public void start() { + mJavaAdapter.alwaysCollectFlow( + mKeyguardTransitionInteractorLazy.get().isFinishedInState(GONE), + (Boolean isFinishedInState) -> { + if (isFinishedInState) { + setLeaveOpenOnKeyguardHide(false); + } + }); + mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(), this::onShadeOrQsExpanded); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/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/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/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/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index bfc5932c1db0..a901c5f8ae2a 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 @@ -241,9 +241,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, @@ -257,19 +254,15 @@ class AvalancheSuppressor( 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 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 6a38f8df4715..d2d0aaa63003 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.Flags.notificationBackgroundTintOptimization; +import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM; +import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -43,6 +45,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; +import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; @@ -354,12 +357,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; if (mDrawingAppearAnimation) { startAppearAnimation(false /* isAppearing */, translationDirection, - delay, duration, onStartedRunnable, onFinishedRunnable, animationListener); + delay, duration, onStartedRunnable, onFinishedRunnable, animationListener, + clipSide); } else { if (onStartedRunnable != null) { onStartedRunnable.run(); @@ -378,13 +382,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mIsHeadsUpAnimation = isHeadsUpAppear; if (mDrawingAppearAnimation) { startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, - duration, null, null, null); + duration, null, null, null, ClipSide.BOTTOM); } } private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { mAnimationTranslationY = translationDirection * getActualHeight(); cancelAppearAnimation(); if (mAppearAnimationFraction == -1.0f) { @@ -406,9 +410,16 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE; targetValue = 0.0f; } + + if (NotificationHeadsUpCycling.isEnabled()) { + // TODO(b/316404716): add avalanche filtering + mCurrentAppearInterpolator = Interpolators.LINEAR; + } + mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, targetValue); - if (NotificationsImprovedHunAnimation.isEnabled()) { + if (NotificationsImprovedHunAnimation.isEnabled() + || NotificationHeadsUpCycling.isEnabled()) { mAppearAnimator.setInterpolator(mCurrentAppearInterpolator); } else { mAppearAnimator.setInterpolator(Interpolators.LINEAR); @@ -418,7 +429,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimator.addUpdateListener(animation -> { mAppearAnimationFraction = (float) animation.getAnimatedValue(); updateAppearAnimationAlpha(); - updateAppearRect(); + if (NotificationHeadsUpCycling.isEnabled()) { + // For cycling out, we want the HUN to be clipped from the top. + updateAppearRect(clipSide); + } else { + updateAppearRect(); + } invalidate(); }); if (animationListener != null) { @@ -426,7 +442,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } // we need to apply the initial state already to avoid drawn frames in the wrong state updateAppearAnimationAlpha(); - updateAppearRect(); + if (NotificationHeadsUpCycling.isEnabled()) { + updateAppearRect(clipSide); + } else { + updateAppearRect(); + } mAppearAnimator.addListener(new AnimatorListenerAdapter() { private boolean mRunWithoutInterruptions; @@ -508,14 +528,18 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView enableAppearDrawing(false); } - private void updateAppearRect() { + /** + * Update the View's Rect clipping to fit the appear animation + * @param clipSide Which side if view we want to clip from + */ + private void updateAppearRect(ClipSide clipSide) { float interpolatedFraction = - NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction + NotificationsImprovedHunAnimation.isEnabled() + || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction); mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY; - final int actualHeight = getActualHeight(); - float bottom = actualHeight * interpolatedFraction; - + final int fullHeight = getActualHeight(); + float height = fullHeight * interpolatedFraction; if (mTargetPoint != null) { int width = getWidth(); float fraction = 1 - mAppearAnimationFraction; @@ -524,13 +548,26 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAnimationTranslationY + (mAnimationTranslationY - mTargetPoint.y) * fraction, width - (width - mTargetPoint.x) * fraction, - actualHeight - (actualHeight - mTargetPoint.y) * fraction); + fullHeight - (fullHeight - mTargetPoint.y) * fraction); } else { - setOutlineRect(0, mAppearAnimationTranslation, getWidth(), - bottom + mAppearAnimationTranslation); + if (clipSide == TOP) { + setOutlineRect( + 0, + /* top= */ fullHeight - height, + getWidth(), + /* bottom= */ fullHeight + ); + } else if (clipSide == BOTTOM) { + setOutlineRect(0, mAppearAnimationTranslation, getWidth(), + height + mAppearAnimationTranslation); + } } } + private void updateAppearRect() { + updateAppearRect(ClipSide.BOTTOM); + } + private float getInterpolatedAppearAnimationFraction() { if (mAppearAnimationFraction >= 0) { @@ -540,11 +577,36 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private void updateAppearAnimationAlpha() { - float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction, - ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION); - float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION; - float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range; - setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha)); + updateAppearAnimationContentAlpha( + mAppearAnimationFraction, + ALPHA_APPEAR_START_FRACTION, + ALPHA_APPEAR_END_FRACTION, + Interpolators.ALPHA_IN + ); + } + + /** + * Update the alpha value of the content view during the appear animation. We suppose that the + * content alpha changes from 0 to 1 during some part of the appear animation. + * @param appearFraction the current appearFraction, should be in the range of [0, 1], where + * 1 represents fully appeared + * @param startFraction the appear fraction when the content view should be + * * fully transparent + * @param endFraction the appear fraction when the content view should be + * fully in-transparent, should be greater or equals to startFraction + * @param interpolator the interpolator to update the alpha + */ + private void updateAppearAnimationContentAlpha( + float appearFraction, + float startFraction, + float endFraction, + Interpolator interpolator + ) { + float contentAlphaProgress = MathUtils.constrain(appearFraction, startFraction, + endFraction); + float range = endFraction - startFraction; + float alpha = (contentAlphaProgress - startFraction) / range; + setContentAlpha(interpolator.getInterpolation(alpha)); } private void setContentAlpha(float contentAlpha) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 23c0a0db04d5..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; @@ -590,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()); @@ -843,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) { @@ -2788,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); } @@ -3076,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) { @@ -3092,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(); @@ -3100,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/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..bb40b5622159 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.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.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 + override fun onContentUpdated(row: ExpandableNotificationRow?) { + resolveViews() + super.onContentUpdated(row) + } + + private fun resolveViews() { + conversationIconView = compactMessagingView.requireViewById(R.id.conversation_icon) + expandBtn = compactMessagingView.requireViewById(R.id.expand_button) + } + + override fun updateTransformedTypes() { + super.updateTransformedTypes() + + addViewsTransformingToSimilar( + conversationIconView, + 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..d4f8ea385667 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt @@ -33,7 +33,12 @@ object NotificationHeadsUpCycling { /** Is the heads-up cycling animation enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationContentAlphaOptimization() + get() = Flags.notificationHeadsUpCycling() + + /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */ + @JvmStatic + inline val animateTallToShort + get() = false /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt new file mode 100644 index 000000000000..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/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 773a6bf752a6..17b54c8f3970 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -112,6 +112,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; +import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; @@ -127,6 +128,7 @@ import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.Assert; import com.android.systemui.util.ColorUtilKt; import com.android.systemui.util.DumpUtilsKt; +import com.android.systemui.util.ListenerSet; import com.google.errorprone.annotations.CompileTimeConstant; @@ -151,7 +153,6 @@ import java.util.function.Consumer; public class NotificationStackScrollLayout extends ViewGroup implements Dumpable, NotificationScrollView { - public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE); @@ -254,6 +255,7 @@ public class NotificationStackScrollLayout * The raw amount of the overScroll on the bottom, which is not rubber-banded. */ private float mOverScrolledBottomPixels; + private ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>(); private NotificationLogger.OnChildLocationsChangedListener mListener; private OnNotificationLocationsChangedListener mLocationsChangedListener; private OnOverscrollTopChangedListener mOverscrollTopChangedListener; @@ -316,7 +318,7 @@ public class NotificationStackScrollLayout = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { - if (SceneContainerFlag.isEnabled() && !mChildrenUpdateRequested) { + if (SceneContainerFlag.isEnabled()) { getViewTreeObserver().removeOnPreDrawListener(this); return true; } @@ -672,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(); @@ -1083,6 +1085,10 @@ public class NotificationStackScrollLayout for (int i = 0; i < size; i++) { measureChild(getChildAt(i), childWidthSpec, childHeightSpec); } + if (SceneContainerFlag.isEnabled()) { + setMaxLayoutHeight(getMeasuredHeight()); + updateContentHeight(); + } Trace.endSection(); } @@ -1092,6 +1098,22 @@ public class NotificationStackScrollLayout super.requestLayout(); } + private void notifyStackHeightChangedListeners() { + for (Runnable listener : mStackHeightChangedListeners) { + listener.run(); + } + } + + @Override + public void addStackHeightChangedListener(@NonNull Runnable runnable) { + mStackHeightChangedListeners.addIfAbsent(runnable); + } + + @Override + public void removeStackHeightChangedListener(@NonNull Runnable runnable) { + mStackHeightChangedListeners.remove(runnable); + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (!mSuppressChildrenMeasureAndLayout) { @@ -1110,8 +1132,10 @@ public class NotificationStackScrollLayout (int) height); } } - setMaxLayoutHeight(getHeight()); - updateContentHeight(); + if (!SceneContainerFlag.isEnabled()) { + setMaxLayoutHeight(getHeight()); + updateContentHeight(); + } clampScrollPosition(); requestChildrenUpdate(); updateFirstAndLastBackgroundViews(); @@ -1177,11 +1201,6 @@ public class NotificationStackScrollLayout } @Override - public void setStackHeightConsumer(@Nullable Consumer<Float> consumer) { - mScrollViewFields.setStackHeightConsumer(consumer); - } - - @Override public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) { mScrollViewFields.setHeadsUpHeightConsumer(consumer); } @@ -1469,9 +1488,10 @@ public class NotificationStackScrollLayout public void setExpandedHeight(float height) { final boolean skipHeightUpdate = shouldSkipHeightUpdate(); - // when scene framework is enabled, updateStackPosition is already called by - // updateTopPadding every time the stack moves, so skip it here to avoid flickering. - if (!SceneContainerFlag.isEnabled()) { + // when scene framework is enabled and in single shade, updateStackPosition is already + // called by updateTopPadding every time the stack moves, so skip it here to avoid + // flickering. + if (!SceneContainerFlag.isEnabled() || mShouldUseSplitNotificationShade) { updateStackPosition(); } @@ -2403,16 +2423,25 @@ public class NotificationStackScrollLayout /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications, shelfIntrinsicHeight); mIntrinsicContentHeight = height; - mScrollViewFields.sendStackHeight(height + footerIntrinsicHeight); // The topPadding can be bigger than the regular padding when qs is expanded, in that // state the maxPanelHeight and the contentHeight should be bigger mContentHeight = (int) (height + Math.max(mIntrinsicPadding, getTopPadding()) + mBottomPadding); + mScrollViewFields.setIntrinsicStackHeight( + (int) (mIntrinsicPadding + mIntrinsicContentHeight + footerIntrinsicHeight + + mBottomPadding)); updateScrollability(); clampScrollPosition(); updateStackPosition(); mAmbientState.setContentHeight(mContentHeight); + + notifyStackHeightChangedListeners(); + } + + @Override + public int getIntrinsicStackHeight() { + return mScrollViewFields.getIntrinsicStackHeight(); } /** @@ -3143,6 +3172,11 @@ public class NotificationStackScrollLayout type = row.wasJustClicked() ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; + if (NotificationHeadsUpCycling.isEnabled()) { + if (mStackScrollAlgorithm.isCyclingOut(row, mAmbientState)) { + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT; + } + } if (row.isChildInGroup()) { // We can otherwise get stuck in there if it was just isolated row.setHeadsUpAnimatingAway(false); @@ -3163,6 +3197,11 @@ public class NotificationStackScrollLayout if (pinnedAndClosed || shouldHunAppearFromTheBottom) { // Our custom add animation type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; + if (NotificationHeadsUpCycling.isEnabled()) { + if (mStackScrollAlgorithm.isCyclingIn(row, mAmbientState)) { + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN; + } + } } else { // Normal add animation type = AnimationEvent.ANIMATION_TYPE_ADD; @@ -3394,15 +3433,19 @@ public class NotificationStackScrollLayout int action = ev.getActionMasked(); boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL; if (mSendingTouchesToSceneFramework) { - mController.sendTouchToSceneFramework(ev); + MotionEvent adjustedEvent = MotionEvent.obtain(ev); + adjustedEvent.setLocation(ev.getRawX(), ev.getRawY()); + mController.sendTouchToSceneFramework(adjustedEvent); mScrollViewFields.sendCurrentGestureOverscroll( getExpandedInThisMotion() && !isUpOrCancel); + adjustedEvent.recycle(); } else if (!isUpOrCancel) { // if this is the first touch being sent to the scene framework, // convert it into a synthetic DOWN event. mSendingTouchesToSceneFramework = true; MotionEvent downEvent = MotionEvent.obtain(ev); downEvent.setAction(MotionEvent.ACTION_DOWN); + downEvent.setLocation(ev.getRawX(), ev.getRawY()); mController.sendTouchToSceneFramework(downEvent); mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion()); downEvent.recycle(); @@ -6134,6 +6177,22 @@ public class NotificationStackScrollLayout .animateTopInset() .animateY() .animateZ(), + + // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT + new AnimationFilter() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ() + .hasDelays(), + + // ANIMATION_TYPE_HEADS_UP_CYCLING_IN + new AnimationFilter() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ() + .hasDelays(), }; static int[] LENGTHS = new int[]{ @@ -6185,6 +6244,12 @@ public class NotificationStackScrollLayout // ANIMATION_TYPE_EVERYTHING StackStateAnimator.ANIMATION_DURATION_STANDARD, + + // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING, + + // ANIMATION_TYPE_HEADS_UP_CYCLING_IN + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING, }; static final int ANIMATION_TYPE_ADD = 0; @@ -6203,6 +6268,8 @@ public class NotificationStackScrollLayout static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13; static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14; static final int ANIMATION_TYPE_EVERYTHING = 15; + static final int ANIMATION_TYPE_HEADS_UP_CYCLING_OUT = 16; + static final int ANIMATION_TYPE_HEADS_UP_CYCLING_IN = 17; final long eventStartTime; final ExpandableView mChangingView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 5bd4c758d678..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 a3827c140214..6afcf372b039 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -45,6 +45,12 @@ class ScrollViewFields { var isScrolledToTop: Boolean = true /** + * Height in view pixels at which the Notification Stack would like to be laid out, including + * Notification rows, paddings the Shelf and the Footer. + */ + var intrinsicStackHeight: Int = 0 + + /** * When internal NSSL expansion requires the stack to be scrolled (e.g. to keep an expanding * notification in view), that scroll amount can be sent here and it will be handled by the * placeholder @@ -56,11 +62,6 @@ class ScrollViewFields { */ var currentGestureOverscrollConsumer: Consumer<Boolean>? = null /** - * Any time the stack height is recalculated, it should be updated here to be used by the - * placeholder - */ - var stackHeightConsumer: Consumer<Float>? = null - /** * Any time the heads up height is recalculated, it should be updated here to be used by the * placeholder */ @@ -72,8 +73,6 @@ class ScrollViewFields { /** send [isCurrentGestureOverscroll] to the [currentGestureOverscrollConsumer], if present. */ fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) = currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll) - /** send the [stackHeight] to the [stackHeightConsumer], if present. */ - fun sendStackHeight(stackHeight: Float) = stackHeightConsumer?.accept(stackHeight) /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index d0cebae40c5a..0fcfc4b4b2c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import java.util.ArrayList; @@ -75,6 +76,7 @@ public class StackScrollAlgorithm { private float mSmallCornerRadius; private float mLargeCornerRadius; private int mHeadsUpAppearHeightBottom; + private int mHeadsUpCyclingPadding; public StackScrollAlgorithm( Context context, @@ -99,6 +101,8 @@ public class StackScrollAlgorithm { R.dimen.heads_up_status_bar_padding); mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize( R.dimen.heads_up_appear_y_above_screen); + mHeadsUpCyclingPadding = context.getResources() + .getDimensionPixelSize(R.dimen.heads_up_cycling_padding); mPinnedZTranslationExtra = res.getDimensionPixelSize( R.dimen.heads_up_pinned_elevation); mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); @@ -348,7 +352,8 @@ public class StackScrollAlgorithm { && !firstHeadsUp && (isHeadsUp || child.isHeadsUpAnimatingAway()) && newNotificationEnd > firstHeadsUpEnd - && !ambientState.isShadeExpanded()) { + && !ambientState.isShadeExpanded() + && !skipClipBottomForCycling(child, ambientState)) { // The bottom of this view is peeking out from under the previous view. // Clip the part that is peeking out. float overlapAmount = newNotificationEnd - firstHeadsUpEnd; @@ -370,6 +375,44 @@ public class StackScrollAlgorithm { } } + /** + * @return Should we skip clipping the bottom clipping when new hun has lower bottom line for + * the hun cycling animation. + */ + private boolean skipClipBottomForCycling(ExpandableView view, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + if (!isCyclingOut(view, ambientState)) return false; + // skip bottom clipping if we animate the bottom line + return NotificationHeadsUpCycling.getAnimateTallToShort(); + } + + /** + * Whether the view is the hun that is cycling out by the notification avalanche. + */ + public boolean isCyclingOut(ExpandableView view, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + if (!(view instanceof ExpandableNotificationRow)) return false; + return isCyclingOut((ExpandableNotificationRow) view, ambientState); + } + + /** + * Whether the row is the hun that is cycling out by the notification avalanche. + */ + public boolean isCyclingOut(ExpandableNotificationRow row, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + String cyclingOutKey = ambientState.getAvalanchePreviousHunKey(); + return row.getEntry().getKey().equals(cyclingOutKey); + } + + /** + * Whether the row is the hun that is cycling in by the notification avalanche. + */ + public boolean isCyclingIn(ExpandableNotificationRow row, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + String cyclingInKey = ambientState.getAvalancheShowingHunKey(); + return row.getEntry().getKey().equals(cyclingInKey); + } + /** Updates the dimmed and hiding sensitive states of the children. */ private void updateDimmedAndHideSensitive(AmbientState ambientState, StackScrollAlgorithmState algorithmState) { @@ -799,6 +842,7 @@ public class StackScrollAlgorithm { } ExpandableNotificationRow topHeadsUpEntry = null; + int cyclingInHunHeight = -1; for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); if (!(child instanceof ExpandableNotificationRow row)) { @@ -839,6 +883,13 @@ public class StackScrollAlgorithm { childState.setYTranslation( Math.max(childState.getYTranslation(), headsUpTranslation)); childState.height = Math.max(row.getIntrinsicHeight(), childState.height); + if (NotificationHeadsUpCycling.isEnabled()) { + if (isCyclingIn(row, ambientState)) { + if (cyclingInHunHeight == -1) { + cyclingInHunHeight = childState.height; + } + } + } childState.hidden = false; ExpandableViewState topState = topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState(); @@ -860,6 +911,26 @@ public class StackScrollAlgorithm { } } if (row.isHeadsUpAnimatingAway()) { + if (NotificationHeadsUpCycling.isEnabled() && isCyclingOut(row, ambientState)) { + // If the two HUNs in the cycling animation have different heights, we need + // an extra y translation to align the animation. + int extraTranslation; + if (NotificationHeadsUpCycling.getAnimateTallToShort()) { + if (cyclingInHunHeight > 0) { + extraTranslation = cyclingInHunHeight - childState.height; + } else { + extraTranslation = 0; + } + } else { + extraTranslation = cyclingInHunHeight >= childState.height + ? cyclingInHunHeight - childState.height : 0; + } + extraTranslation += mHeadsUpCyclingPadding; + float inSpaceTranslation = Math.max(childState.getYTranslation(), + headsUpTranslation); + childState.setYTranslation(inSpaceTranslation + extraTranslation); + cyclingInHunHeight = -1; + } else if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) { if (shouldHunAppearFromBottom(ambientState, childState)) { // move to the bottom of the screen diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 5963d358443e..5dc544993ddc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK; @@ -57,6 +59,7 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400; public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400; + public static final int ANIMATION_DURATION_HEADS_UP_CYCLING = 400; public static final int ANIMATION_DURATION_FOLD_TO_AOD = AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD; public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500; @@ -68,6 +71,8 @@ public class StackStateAnimator { @VisibleForTesting int mGoToFullShadeAppearingTranslation; @VisibleForTesting float mHeadsUpAppearStartAboveScreen; + // Padding between the old and new heads up notifications for the hun cycling animation + private float mHeadsUpCyclingPadding; private final ExpandableViewState mTmpState = new ExpandableViewState(); private final AnimationProperties mAnimationProperties; public NotificationStackScrollLayout mHostLayout; @@ -125,6 +130,8 @@ public class StackStateAnimator { R.dimen.go_to_full_shade_appearing_translation); mHeadsUpAppearStartAboveScreen = context.getResources() .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen); + mHeadsUpCyclingPadding = context.getResources() + .getDimensionPixelSize(R.dimen.heads_up_cycling_padding); } protected void setLogger(StackStateLogger logger) { @@ -449,7 +456,8 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - startAnimation, postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, getGlobalAnimationFinishedListener(), + ExpandableView.ClipSide.BOTTOM); needsCustomAnimation = true; } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { @@ -464,6 +472,27 @@ public class StackStateAnimator { .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) { ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView; row.prepareExpansionChanged(); + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_IN) { + mHeadsUpAppearChildren.add(changingView); + + mTmpState.copyFrom(changingView.getViewState()); + mTmpState.setYTranslation(changingView.getViewState().getYTranslation() + + getHeadsUpCyclingInYTranslationStart(event.headsUpFromBottom)); + mTmpState.applyToView(changingView); + + // TODO(b/339519404): use a different interpolator + Runnable onAnimationEnd = null; + if (loggable) { + // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with + // normal ADD animations, which would not be logged here. + String finalKey = key; + mLogger.logHUNViewAppearing(key); + onAnimationEnd = () -> { + mLogger.appearAnimationEnded(finalKey); + }; + } + changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING, + /* isHeadsUpAppear= */ true, onAnimationEnd); } else if (NotificationsImprovedHunAnimation.isEnabled() && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) { mHeadsUpAppearChildren.add(changingView); @@ -486,6 +515,87 @@ public class StackStateAnimator { } changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR, /* isHeadsUpAppear= */ true, onAnimationEnd); + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_OUT) { + mHeadsUpDisappearChildren.add(changingView); + Runnable endRunnable = null; + mTmpState.copyFrom(changingView.getViewState()); + + if (changingView.getParent() == null) { + // This notification was actually removed, so we need to add it + // transiently + mHostLayout.addTransientView(changingView, 0); + changingView.setTransientContainer(mHostLayout); + // TODO(b/316404716): remove the hard-coded height + // StackScrollAlgorithm cannot find this view because it has been removed + // from the NSSL. To correctly translate the view to the top or bottom of + // the screen (where it animated from), we need to update its translation. + mTmpState.setYTranslation( + mTmpState.getYTranslation() + 10 + ); + endRunnable = changingView::removeFromTransientContainer; + } + + boolean needsAnimation = true; + if (changingView instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = + (ExpandableNotificationRow) changingView; + if (row.isDismissed()) { + needsAnimation = false; + } + } + if (needsAnimation) { + // We need to add the global animation listener, since once no animations are + // running anymore, the panel will instantly hide itself. We need to wait until + // the animation is fully finished for this though. + final Runnable tmpEndRunnable = endRunnable; + Runnable postAnimation; + Runnable startAnimation; + if (loggable) { + String finalKey1 = key; + final boolean finalIsHeadsUp = isHeadsUp; + final String type = "ANIMATION_TYPE_HEADS_UP_CYCLING_OUT"; + startAnimation = () -> { + mLogger.animationStart(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + + }; + } else { + postAnimation = () -> { + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + startAnimation = () -> { + changingView.setInRemovalAnimation(true); + }; + } + long removeAnimationDelay = changingView.performRemoveAnimation( + ANIMATION_DURATION_HEADS_UP_CYCLING, + /* delay= */ 0, + // It's a shame that translationDirection isn't where we do the y + // translation, the actual translation is in StackScrollAlgorithm. + /* translationDirection= */ 0.0f, + /* isHeadsUpAnimation= */ true, + startAnimation, postAnimation, + getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.TOP); + mAnimationProperties.delay += removeAnimationDelay; + mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_CYCLING; + mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y, + Interpolators.LINEAR); + mAnimationProperties.getAnimationFilter().animateY = true; + mTmpState.animateTo(changingView, mAnimationProperties); + } else if (endRunnable != null) { + endRunnable.run(); + } + needsCustomAnimation |= needsAnimation; } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) { NotificationsImprovedHunAnimation.assertInLegacyMode(); // This item is added, initialize its properties. @@ -565,21 +675,21 @@ public class StackStateAnimator { } }; } else { + startAnimation = () -> { + changingView.setInRemovalAnimation(true); + }; postAnimation = () -> { changingView.setInRemovalAnimation(false); if (tmpEndRunnable != null) { tmpEndRunnable.run(); } }; - startAnimation = () -> { - changingView.setInRemovalAnimation(true); - }; } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 0, 0.0f, true /* isHeadsUpAppear */, startAnimation, postAnimation, - getGlobalAnimationFinishedListener()); + getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM); mAnimationProperties.delay += removeAnimationDelay; if (NotificationsImprovedHunAnimation.isEnabled()) { mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR; @@ -607,6 +717,38 @@ public class StackStateAnimator { return -mStackTopMargin - mHeadsUpAppearStartAboveScreen; } + /** + * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen + * @return The start y translation of the HUN cycling in animation + */ + private float getHeadsUpCyclingInYTranslationStart(boolean headsUpFromBottom) { + if (headsUpFromBottom) { + // start from the bottom of the screen + return mHeadsUpAppearHeightBottom + mHeadsUpCyclingPadding; + } + // start from the top of the screen + return -mHeadsUpCyclingPadding; + } + + /** + * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen + * @param oldHunHeight Height of the old HUN + * @param newHunHeight Height of the new HUN + * @return The y translation target value of the HUN cycling out animation + */ + private float getHeadsUpCyclingOutYTranslation( + boolean headsUpFromBottom, + int oldHunHeight, + int newHunHeight + ) { + final float translationDistance = mHeadsUpCyclingPadding + newHunHeight - oldHunHeight; + if (headsUpFromBottom) { + // start from the bottom of the screen + return mHeadsUpAppearHeightBottom - translationDistance; + } + return translationDistance; + } + public void animateOverScrollToAmount(float targetAmount, final boolean onTop, final boolean isRubberbanded) { final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt index dacafc48fc72..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 @@ -38,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 920c9c213060..463c631db32f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt @@ -27,13 +27,6 @@ import kotlinx.coroutines.flow.MutableStateFlow @SysUISingleton class NotificationViewHeightRepository @Inject constructor() { - /** - * The height in px of the contents of notification stack. Depending on the number of - * notifications, this can exceed the space available on screen to show notifications, at which - * point the notification stack should become scrollable. - */ - val stackHeight = MutableStateFlow(0f) - /** The height in px of the current heads up notification. */ val headsUpHeight = MutableStateFlow(0f) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index b94da388cef4..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,13 +65,6 @@ constructor( } .distinctUntilChanged() - /** - * The height in px of the contents of notification stack. Depending on the number of - * notifications, this can exceed the space available on screen to show notifications, at which - * point the notification stack should become scrollable. - */ - val stackHeight: StateFlow<Float> = viewHeightRepository.stackHeight.asStateFlow() - /** The height in px of the contents of the HUN. */ val headsUpHeight: StateFlow<Float> = viewHeightRepository.headsUpHeight.asStateFlow() @@ -79,12 +72,6 @@ constructor( val alphaForBrightnessMirror: StateFlow<Float> = placeholderRepository.alphaForBrightnessMirror.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 height of the keyguard's available space bounds */ val constrainedAvailableSpace: StateFlow<Int> = placeholderRepository.constrainedAvailableSpace.asStateFlow() @@ -123,26 +110,11 @@ constructor( placeholderRepository.shadeScrimBounds.value = bounds } - /** Sets the height of the contents of the notification stack. */ - fun setStackHeight(height: Float) { - viewHeightRepository.stackHeight.value = height - } - /** Sets the height of heads up notification. */ fun setHeadsUpHeight(height: Float) { viewHeightRepository.headsUpHeight.value = height } - /** 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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 2c8884504c32..14b882f974d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -25,6 +25,13 @@ import java.util.function.Consumer * notification stack, but is otherwise agnostic to the content. */ interface NotificationScrollView { + + /** + * Height in view pixels at which the Notification Stack would like to be laid out, including + * Notification rows, paddings the Shelf and the Footer. + */ + val intrinsicStackHeight: Int + /** * Since this is an interface rather than a literal View, this provides cast-like access to the * underlying view. @@ -53,8 +60,6 @@ interface NotificationScrollView { fun setSyntheticScrollConsumer(consumer: Consumer<Float>?) /** Set a consumer for current gesture overscroll events */ fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?) - /** Set a consumer for stack height changed events */ - fun setStackHeightConsumer(consumer: Consumer<Float>?) /** Set a consumer for heads up height changed events */ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?) @@ -66,4 +71,10 @@ interface NotificationScrollView { /** Sets whether the view is displayed in doze mode. */ fun setDozing(dozing: Boolean) + + /** Sets a listener to be notified, when the stack height might have changed. */ + fun addStackHeightChangedListener(runnable: Runnable) + + /** @see addStackHeightChangedListener */ + fun removeStackHeightChangedListener(runnable: Runnable) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 26f7ad775f1d..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 @@ -79,8 +79,6 @@ constructor( } launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } } - launch { viewModel.stackTop.collect { view.setStackTop(it) } } - launch { viewModel.stackBottom.collect { view.setStackBottom(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)) } } @@ -90,12 +88,10 @@ constructor( launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) - view.setStackHeightConsumer(viewModel.stackHeightConsumer) view.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) - view.setStackHeightConsumer(null) view.setHeadsUpHeightConsumer(null) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index b2184db0879d..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 @@ -130,10 +130,6 @@ constructor( val maxAlpha: Flow<Float> = stackAppearanceInteractor.alphaForBrightnessMirror.dumpValue("maxAlpha") - /** 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") /** * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any * further. @@ -151,8 +147,6 @@ constructor( */ val currentGestureOverscrollConsumer: (Boolean) -> Unit = stackAppearanceInteractor::setCurrentGestureOverscroll - /** Receives the height of the contents of the notification stack. */ - val stackHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setStackHeight /** Receives the height of the heads up notification. */ val headsUpHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setHeadsUpHeight diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 11eaf54efe47..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) @@ -87,13 +74,6 @@ constructor( val shadeScrimRounding: Flow<ShadeScrimRounding> = interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding") - /** - * The height in px of the contents of notification stack. Depending on the number of - * notifications, this can exceed the space available on screen to show notifications, at which - * point the notification stack should become scrollable. - */ - val stackHeight: StateFlow<Float> = interactor.stackHeight.dumpValue("stackHeight") - /** The height in px of the contents of the HUN. */ val headsUpHeight: StateFlow<Float> = interactor.headsUpHeight.dumpValue("headsUpHeight") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 0ba7b3c214c6..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,6 +65,7 @@ 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 @@ -295,8 +297,7 @@ constructor( return combine( isOnLockscreenWithoutShade, keyguardTransitionInteractor.isInTransition( - from = LOCKSCREEN, - to = AOD, + edge = Edge.create(from = LOCKSCREEN, to = AOD) ), ::Pair ) 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 be6bef74565a..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; @@ -1006,14 +1007,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // this handling this post-init task. We force an update in this case, and use a new // token to not conflict with any other disabled flags already requested by SysUI Binder token = new Binder(); - int userId = mContext.getUserId(); - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNavigationHomeDisabled(true); - mBarService.disableForUser(info, token, mContext.getPackageName(), - userId, "set the initial view visibility"); - - mBarService.disableForUser(new StatusBarManager.DisableInfo(), token, - mContext.getPackageName(), userId, "set the initial view visibility"); + mBarService.disable(DISABLE_HOME, token, mContext.getPackageName()); + mBarService.disable(0, token, mContext.getPackageName()); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } @@ -2184,7 +2179,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } if (mStatusBarStateController.leaveOpenOnKeyguardHide()) { if (!mStatusBarStateController.isKeyguardRequested()) { - mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); + if (!MigrateClocksToBlueprint.isEnabled()) { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); + } } long delay = mKeyguardStateController.calculateGoingToFullShadeDelay(); mLockscreenShadeTransitionController.onHideKeyguard(delay, previousState); @@ -2808,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)) { @@ -2817,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(); @@ -2935,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 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 f219b9d3c185..2b26e3f12ef7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -54,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; @@ -133,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 @@ -302,7 +298,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat @Main Executor mainExecutor, @Background Executor backgroundExecutor, KeyguardLogger logger, - NotificationMediaManager notificationMediaManager, StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory ) { super(view); @@ -357,7 +352,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /* mask2= */ DISABLE2_SYSTEM_ICONS, this::updateViewState ); - mNotificationMediaManager = notificationMediaManager; mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory; } 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 74182fc4d2c1..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); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index f0dab3ba1829..fa88be5b638b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -696,6 +696,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb Trace.beginSection("StatusBarKeyguardViewManager#show"); mNotificationShadeWindowController.setKeyguardShowing(true); if (SceneContainerFlag.isEnabled()) { + // TODO(b/336581871): add sceneState? mSceneInteractorLazy.get().changeScene( Scenes.Lockscreen, "StatusBarKeyguardViewManager.show"); } @@ -1548,7 +1549,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/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..a858fb079d72 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(); @@ -594,9 +594,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 +688,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 +808,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/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt index d9d909a49781..fc54f140dec5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt @@ -33,6 +33,18 @@ data class SubscriptionModel( */ val isOpportunistic: Boolean = false, + /** + * True if this subscription **only** supports non-terrestrial networks (NTN) and false + * otherwise. (non-terrestrial == satellite) + * + * Note that we intend to filter these subscriptions out, because these connections are actually + * supported by + * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. See + * [com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor] for + * the filtering. + */ + val isExclusivelyNonTerrestrial: Boolean = false, + /** Subscriptions in the same group may be filtered or treated as a single subscription */ val groupUuid: ParcelUuid? = null, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index 22785979f3ae..425c58b0074b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -23,6 +23,7 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.flow.StateFlow @@ -76,7 +77,17 @@ interface MobileConnectionRepository { */ val isInService: StateFlow<Boolean> - /** Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork] */ + /** + * True if this subscription is actively connected to a non-terrestrial network and false + * otherwise. Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork]. + * + * Notably: This value reflects that this subscription is **currently** using a non-terrestrial + * network, because some subscriptions can switch between terrestrial and non-terrestrial + * networks. [SubscriptionModel.isExclusivelyNonTerrestrial] reflects whether a subscription is + * configured to exclusively connect to non-terrestrial networks. [isNonTerrestrial] can change + * during the lifetime of a subscription but [SubscriptionModel.isExclusivelyNonTerrestrial] + * will stay constant. + */ val isNonTerrestrial: StateFlow<Boolean> /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 5d91ef323ead..0073e9cd3dd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -424,6 +424,7 @@ constructor( SubscriptionModel( subscriptionId = subscriptionId, isOpportunistic = isOpportunistic, + isExclusivelyNonTerrestrial = isOnlyNonTerrestrialNetwork, groupUuid = groupUuid, carrierName = carrierName.toString(), profileClass = profileClass, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index d555c47f4da2..91d7ca65b30d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -172,21 +172,33 @@ constructor( private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> = mobileConnectionsRepo.subscriptions - /** - * Any filtering that we can do based purely on the info of each subscription. Currently this - * only applies the ProfileClass-based filter, but if we need other they can go here - */ + /** Any filtering that we can do based purely on the info of each subscription individually. */ private val subscriptionsBasedFilteredSubs = - unfilteredSubscriptions.map { subs -> applyProvisioningFilter(subs) }.distinctUntilChanged() + unfilteredSubscriptions + .map { it.filterBasedOnProvisioning().filterBasedOnNtn() } + .distinctUntilChanged() - private fun applyProvisioningFilter(subs: List<SubscriptionModel>): List<SubscriptionModel> = + private fun List<SubscriptionModel>.filterBasedOnProvisioning(): List<SubscriptionModel> = if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) { - subs + this } else { - subs.filter { it.profileClass != PROFILE_CLASS_PROVISIONING } + this.filter { it.profileClass != PROFILE_CLASS_PROVISIONING } } /** + * Subscriptions that exclusively support non-terrestrial networks should **never** directly + * show any iconography in the status bar. These subscriptions only exist to provide a backing + * for the device-based satellite connections, and the iconography for those connections are + * already being handled in + * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. We + * need to filter out those subscriptions here so we guarantee the subscription never turns into + * an icon. See b/336881301. + */ + private fun List<SubscriptionModel>.filterBasedOnNtn(): List<SubscriptionModel> { + return this.filter { !it.isExclusivelyNonTerrestrial } + } + + /** * Generally, SystemUI wants to show iconography for each subscription that is listed by * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only * show a single representation of the pair of subscriptions. The docs define opportunistic as: @@ -204,12 +216,8 @@ constructor( subscriptionsBasedFilteredSubs, mobileConnectionsRepo.activeMobileDataSubscriptionId, connectivityRepository.vcnSubId, - ) { unfilteredSubs, activeId, vcnSubId -> - filterSubsBasedOnOpportunistic( - unfilteredSubs, - activeId, - vcnSubId, - ) + ) { preFilteredSubs, activeId, vcnSubId -> + filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId) } .distinctUntilChanged() .logDiffsForTable( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/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/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/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/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/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/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/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..4812765d4afe 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)) + 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..599bd73abb69 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?> { 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/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index b00829e48404..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 @@ -19,12 +19,10 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.content.pm.PackageManager import android.media.VolumeProvider import android.media.session.MediaController -import android.os.Handler import android.util.Log import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.repository.LocalMediaRepository 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.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions @@ -61,7 +59,7 @@ constructor( @VolumePanelScope private val coroutineScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, mediaControllerRepository: MediaControllerRepository, - @Background private val backgroundHandler: Handler, + private val mediaControllerInteractor: MediaControllerInteractor, ) { private val activeMediaControllers: Flow<MediaControllers> = @@ -194,7 +192,10 @@ constructor( return flowOf(null) } - return stateChanges(backgroundHandler).map { this }.onStart { emit(this@stateChanges) } + return mediaControllerInteractor + .stateChanges(this) + .map { this } + .onStart { emit(this@stateChanges) } } private data class MediaControllers( 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..ef5a44a7a2fd --- /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/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index 038633810fd7..c08cd64e4e8c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -49,6 +49,11 @@ constructor( private val uiEventLogger: UiEventLogger, ) : SliderViewModel { + private val streamsAffectedByRing = + setOf( + AudioManager.STREAM_RING, + AudioManager.STREAM_NOTIFICATION, + ) private val audioStream = audioStreamWrapper.audioStream private val iconsByStream = mapOf( @@ -125,15 +130,42 @@ constructor( isEnabled: Boolean, ringerMode: RingerMode, ): State { + val label = + labelsByStream[audioStream]?.let(context::getString) + ?: error("No label for the stream: $audioStream") return State( value = volume.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), icon = getIcon(ringerMode), - label = labelsByStream[audioStream]?.let(context::getString) - ?: error("No label for the stream: $audioStream"), + label = label, disabledMessage = disabledTextByStream[audioStream]?.let(context::getString), isEnabled = isEnabled, a11yStep = volumeRange.step, + a11yClickDescription = + context.getString( + if (isMuted) { + R.string.volume_panel_hint_unmute + } else { + R.string.volume_panel_hint_mute + }, + label, + ), + a11yStateDescription = + if (volume == volumeRange.first) { + context.getString( + if (audioStream.value in streamsAffectedByRing) { + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.string.volume_panel_hint_vibrate + } else { + R.string.volume_panel_hint_muted + } + } else { + R.string.volume_panel_hint_muted + } + ) + } else { + null + }, audioStreamModel = this, isMutable = audioVolumeInteractor.isAffectedByMute(audioStream), ) @@ -143,27 +175,14 @@ constructor( val isMutedOrNoVolume = isMuted || volume == minVolume val iconRes = if (isMutedOrNoVolume) { - when (audioStream.value) { - AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off - AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off - AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off - AudioManager.STREAM_RING -> - if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { - R.drawable.ic_volume_ringer_vibrate - } else { - R.drawable.ic_volume_off - } - AudioManager.STREAM_NOTIFICATION -> - if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { - R.drawable.ic_volume_ringer_vibrate - } else { - R.drawable.ic_volume_off - } - AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off - else -> { - Log.wtf(TAG, "No icon for the stream: $audioStream") + if (audioStream.value in streamsAffectedByRing) { + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.drawable.ic_volume_ringer_vibrate + } else { R.drawable.ic_volume_off } + } else { + R.drawable.ic_volume_off } } else { iconsByStream[audioStream] @@ -186,6 +205,8 @@ constructor( override val disabledMessage: String?, override val isEnabled: Boolean, override val a11yStep: Int, + override val a11yClickDescription: String?, + override val a11yStateDescription: String?, override val isMutable: Boolean, val audioStreamModel: AudioStreamModel, ) : SliderState diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt index 956ab66ac0c3..10714d1f41af 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt @@ -68,7 +68,7 @@ constructor( icon = Icon.Resource(R.drawable.ic_cast, null), label = context.getString(R.string.media_device_cast), isEnabled = true, - a11yStep = 1 + a11yStep = 1, ) } @@ -85,6 +85,12 @@ constructor( override val isMutable: Boolean get() = false + + override val a11yClickDescription: String? + get() = null + + override val a11yStateDescription: String? + get() = null } @AssistedFactory diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt index d71a9d8dae94..c951928bb977 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt @@ -34,6 +34,8 @@ sealed interface SliderState { * enough to trigger rounding to the correct value. */ val a11yStep: Int + val a11yClickDescription: String? + val a11yStateDescription: String? val disabledMessage: String? val isMutable: Boolean @@ -44,6 +46,8 @@ sealed interface SliderState { override val label: String = "" override val disabledMessage: String? = null override val a11yStep: Int = 0 + override val a11yClickDescription: String? = null + override val a11yStateDescription: String? = null override val isEnabled: Boolean = true override val isMutable: Boolean = false } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt index 26d6a9a0153d..4b4d69a31db4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt @@ -31,13 +31,15 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch @@ -53,12 +55,39 @@ class AudioVolumeComponentViewModel constructor( @VolumePanelScope private val scope: CoroutineScope, mediaOutputInteractor: MediaOutputInteractor, - private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, + mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory, private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory, streamsInteractor: AudioSlidersInteractor, ) { + private val mutableIsExpanded = MutableStateFlow<Boolean?>(null) + private val isPlaybackActive: Flow<Boolean?> = + mediaOutputInteractor.defaultActiveMediaSession + .filterData() + .flatMapLatest { session -> + if (session == null) { + flowOf(false) + } else { + mediaDeviceSessionInteractor.playbackState(session).map { it?.isActive == true } + } + } + .onEach { isPlaybackActive -> mutableIsExpanded.value = !isPlaybackActive } + .stateIn(scope, SharingStarted.Eagerly, null) + private val portraitExpandable: Flow<SlidersExpandableViewModel> = + isPlaybackActive + .filterNotNull() + .flatMapLatest { isActive -> + if (isActive) { + mutableIsExpanded.filterNotNull().map { isExpanded -> + SlidersExpandableViewModel.Expandable(isExpanded) + } + } else { + flowOf(SlidersExpandableViewModel.Fixed) + } + } + .stateIn(scope, SharingStarted.Eagerly, SlidersExpandableViewModel.Unavailable) + val sliderViewModels: StateFlow<List<SliderViewModel>> = streamsInteractor.volumePanelSliders .transformLatest { sliderTypes -> @@ -76,24 +105,16 @@ constructor( } .stateIn(scope, SharingStarted.Eagerly, emptyList()) - private val mutableIsExpanded = MutableSharedFlow<Boolean>() - - val isExpanded: StateFlow<Boolean> = - merge( - mutableIsExpanded, - mediaOutputInteractor.defaultActiveMediaSession.filterData().flatMapLatest { session - -> - if (session == null) flowOf(true) - else - mediaDeviceSessionInteractor.playbackState(session).map { - it?.isActive != true - } - }, - ) - .stateIn(scope, SharingStarted.Eagerly, false) + fun isExpandable(isPortrait: Boolean): Flow<SlidersExpandableViewModel> { + return if (isPortrait) { + portraitExpandable + } else { + flowOf(SlidersExpandableViewModel.Fixed) + } + } fun onExpandedChanged(isExpanded: Boolean) { - scope.launch { mutableIsExpanded.emit(isExpanded) } + scope.launch { mutableIsExpanded.value = isExpanded } } private fun CoroutineScope.createSessionViewModel( diff --git a/keystore/java/android/security/KeyStore.java b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt index d1d7c145680f..19b9ead88ebb 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,18 @@ * limitations under the License. */ -package android.security; +package com.android.systemui.volume.panel.component.volume.ui.viewmodel /** - * This class provides some constants and helper methods related to Android's Keystore service. - * This class was originally much larger, but its functionality was superseded by other classes. - * It now just contains a few remaining pieces for which the users haven't been updated yet. - * You may be looking for {@link java.security.KeyStore} instead. - * - * @hide + * Models expandability state of the + * [com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent]. */ -public class KeyStore { +sealed interface SlidersExpandableViewModel { + + /** [SlidersExpandableViewModel] is not loaded. */ + data object Unavailable : SlidersExpandableViewModel + + data class Expandable(val isExpanded: Boolean) : SlidersExpandableViewModel - // Used for UID field to indicate the calling UID. - public static final int UID_SELF = -1; + data object Fixed : SlidersExpandableViewModel } diff --git a/packages/SystemUI/src/com/android/systemui/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 b86a7c92ba47..e073f7cde826 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -98,7 +98,7 @@ public final class WMShell implements CoreStartable, CommandQueue.Callbacks { private static final String TAG = WMShell.class.getName(); - private static final int INVALID_SYSUI_STATE_MASK = + private static final long INVALID_SYSUI_STATE_MASK = SYSUI_STATE_DIALOG_SHOWING | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index e72027a921b7..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/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt index bf6ca0684398..e371b39faab4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt @@ -46,12 +46,12 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever private const val ON: Int = 1 private const val OFF: Int = 0 @@ -90,7 +90,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() { secureSettings = FakeSettings() systemClock = FakeSystemClock() backgroundDelayableExecutor = FakeExecutor(systemClock) - whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState) + whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState) fontScalingDialogDelegate = spy( diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index ebb6b48f9532..8895a5e1a97c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -118,7 +119,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices); when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS); when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt index 41974f46ea7b..fd37cad72371 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt @@ -8,6 +8,7 @@ import android.content.pm.ApplicationInfo import android.graphics.Point import android.graphics.Rect import android.os.Looper +import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.IRemoteAnimationFinishedCallback @@ -17,15 +18,20 @@ import android.view.SurfaceControl import android.view.ViewGroup import android.widget.FrameLayout import android.widget.LinearLayout +import android.window.RemoteTransition +import android.window.TransitionFilter import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.Flags import com.android.systemui.util.mockito.any +import com.android.wm.shell.shared.ShellTransitions import junit.framework.Assert.assertFalse import junit.framework.Assert.assertNotNull import junit.framework.Assert.assertNull import junit.framework.Assert.assertTrue import junit.framework.AssertionFailedError import kotlin.concurrent.thread +import kotlin.test.assertEquals import org.junit.After import org.junit.Assert.assertThrows import org.junit.Before @@ -46,7 +52,9 @@ import org.mockito.junit.MockitoJUnit @RunWithLooper class ActivityTransitionAnimatorTest : SysuiTestCase() { private val transitionContainer = LinearLayout(mContext) - private val testTransitionAnimator = fakeTransitionAnimator() + private val mainExecutor = context.mainExecutor + private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor) + private val testShellTransitions = FakeShellTransitions() @Mock lateinit var callback: ActivityTransitionAnimator.Callback @Mock lateinit var listener: ActivityTransitionAnimator.Listener @Spy private val controller = TestTransitionAnimatorController(transitionContainer) @@ -54,14 +62,19 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { private lateinit var activityTransitionAnimator: ActivityTransitionAnimator @get:Rule val rule = MockitoJUnit.rule() + @get:Rule val setFlagsRule = SetFlagsRule() @Before fun setup() { activityTransitionAnimator = ActivityTransitionAnimator( + mainExecutor, + ActivityTransitionAnimator.TransitionRegister.fromShellTransitions( + testShellTransitions + ), testTransitionAnimator, testTransitionAnimator, - disableWmTimeout = true + disableWmTimeout = true, ) activityTransitionAnimator.callback = callback activityTransitionAnimator.addListener(listener) @@ -162,6 +175,34 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { } @Test + fun registersReturnIffCookieIsPresent() { + setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY) + `when`(callback.isOnKeyguard()).thenReturn(false) + + startIntentWithAnimation(activityTransitionAnimator, controller) { _ -> + ActivityManager.START_DELIVERED_TO_TOP + } + + waitForIdleSync() + assertTrue(testShellTransitions.remotes.isEmpty()) + assertTrue(testShellTransitions.remotesForTakeover.isEmpty()) + + val controller = + object : DelegateTransitionAnimatorController(controller) { + override val transitionCookie + get() = ActivityTransitionAnimator.TransitionCookie("testCookie") + } + + startIntentWithAnimation(activityTransitionAnimator, controller) { _ -> + ActivityManager.START_DELIVERED_TO_TOP + } + + waitForIdleSync() + assertEquals(1, testShellTransitions.remotes.size) + assertTrue(testShellTransitions.remotesForTakeover.isEmpty()) + } + + @Test fun doesNotStartIfAnimationIsCancelled() { val runner = activityTransitionAnimator.createRunner(controller) runner.onAnimationCancelled() @@ -241,6 +282,35 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { } /** + * A fake implementation of [ShellTransitions] which saves filter-transition pairs locally and + * allows inspection. + */ +private class FakeShellTransitions : ShellTransitions { + val remotes = mutableMapOf<TransitionFilter, RemoteTransition>() + val remotesForTakeover = mutableMapOf<TransitionFilter, RemoteTransition>() + + override fun registerRemote(filter: TransitionFilter, remoteTransition: RemoteTransition) { + remotes[filter] = remoteTransition + } + + override fun registerRemoteForTakeover( + filter: TransitionFilter, + remoteTransition: RemoteTransition + ) { + remotesForTakeover[filter] = remoteTransition + } + + override fun unregisterRemote(remoteTransition: RemoteTransition) { + while (remotes.containsValue(remoteTransition)) { + remotes.values.remove(remoteTransition) + } + while (remotesForTakeover.containsValue(remoteTransition)) { + remotesForTakeover.values.remove(remoteTransition) + } + } +} + +/** * A simple implementation of [ActivityTransitionAnimator.Controller] which throws if it is called * outside of the main thread. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt index d84a578294a3..e14762cd8792 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt @@ -156,6 +156,7 @@ class DialogTransitionAnimatorTest : SysuiTestCase() { fun testActivityLaunchWhenLockedWithoutAlternateAuth() { val dialogTransitionAnimator = fakeDialogTransitionAnimator( + mainExecutor = mContext.mainExecutor, isUnlocked = false, isShowingAlternateAuthOnUnlock = false, interactionJankMonitor = kosmos.interactionJankMonitor) @@ -166,6 +167,7 @@ class DialogTransitionAnimatorTest : SysuiTestCase() { @Test fun testActivityLaunchWhenLockedWithAlternateAuth() { val dialogTransitionAnimator = fakeDialogTransitionAnimator( + mainExecutor = mContext.mainExecutor, isUnlocked = false, isShowingAlternateAuthOnUnlock = true, interactionJankMonitor = kosmos.interactionJankMonitor diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt index b31fe21f8e91..42fcd547408a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt @@ -16,12 +16,16 @@ package com.android.systemui.animation +import android.os.HandlerThread import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.View import android.widget.FrameLayout import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.animation.view.LaunchableFrameLayout +import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith @@ -30,6 +34,13 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() { + companion object { + private const val LAUNCH_CUJ = 0 + private const val RETURN_CUJ = 1 + } + + private val interactionJankMonitor = FakeInteractionJankMonitor() + @Test fun animatingOrphanViewDoesNotCrash() { val state = TransitionAnimator.State(top = 0, bottom = 0, left = 0, right = 0) @@ -47,4 +58,63 @@ class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() { GhostedViewTransitionAnimatorController(FrameLayout(mContext)) } } + + @Test + fun cujsAreLoggedCorrectly() { + val parent = FrameLayout(mContext) + + val launchView = LaunchableFrameLayout(mContext) + parent.addView((launchView)) + val launchController = + GhostedViewTransitionAnimatorController( + launchView, + launchCujType = LAUNCH_CUJ, + returnCujType = RETURN_CUJ, + interactionJankMonitor = interactionJankMonitor + ) + launchController.onTransitionAnimationStart(isExpandingFullyAbove = true) + assertThat(interactionJankMonitor.ongoing).containsExactly(LAUNCH_CUJ) + launchController.onTransitionAnimationEnd(isExpandingFullyAbove = true) + assertThat(interactionJankMonitor.ongoing).isEmpty() + assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ) + + val returnView = LaunchableFrameLayout(mContext) + parent.addView((returnView)) + val returnController = + object : GhostedViewTransitionAnimatorController( + returnView, + launchCujType = LAUNCH_CUJ, + returnCujType = RETURN_CUJ, + interactionJankMonitor = interactionJankMonitor + ) { + override val isLaunching = false + } + returnController.onTransitionAnimationStart(isExpandingFullyAbove = true) + assertThat(interactionJankMonitor.ongoing).containsExactly(RETURN_CUJ) + returnController.onTransitionAnimationEnd(isExpandingFullyAbove = true) + assertThat(interactionJankMonitor.ongoing).isEmpty() + assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ, RETURN_CUJ) + } + + /** + * A fake implementation of [InteractionJankMonitor] which stores ongoing and finished CUJs and + * allows inspection. + */ + private class FakeInteractionJankMonitor : InteractionJankMonitor( + HandlerThread("testThread") + ) { + val ongoing: MutableSet<Int> = mutableSetOf() + val finished: MutableSet<Int> = mutableSetOf() + + override fun begin(v: View?, cujType: Int): Boolean { + ongoing.add(cujType) + return true + } + + override fun end(cujType: Int): Boolean { + ongoing.remove(cujType) + finished.add(cujType) + return true + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt index e64df905470d..259ece9fdf3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt @@ -25,6 +25,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.activity.EmptyTestActivity +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -58,9 +60,11 @@ class TransitionAnimatorTest : SysuiTestCase() { ) } + private val kosmos = Kosmos() private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig()) private val transitionAnimator = TransitionAnimator( + kosmos.fakeExecutor, ActivityTransitionAnimator.TIMINGS, ActivityTransitionAnimator.INTERPOLATORS ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index de3b741b762c..e81369d9631c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -43,6 +43,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils +import com.android.launcher3.icons.IconProvider import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository @@ -150,6 +151,7 @@ open class AuthContainerViewTest : SysuiTestCase() { private lateinit var displayStateInteractor: DisplayStateInteractor private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private lateinit var biometricStatusInteractor: BiometricStatusInteractor + private lateinit var iconProvider: IconProvider private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) @@ -178,6 +180,7 @@ open class AuthContainerViewTest : SysuiTestCase() { biometricStatusInteractor = BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository, fingerprintRepository) + iconProvider = IconProvider(context) // Set up default logo icon whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon) context.setMockPackageManager(packageManager) @@ -649,14 +652,15 @@ open class AuthContainerViewTest : SysuiTestCase() { lockPatternUtils, interactionJankMonitor, { promptSelectorInteractor }, - { bpCredentialInteractor }, PromptViewModel( displayStateInteractor, promptSelectorInteractor, context, udfpsOverlayInteractor, biometricStatusInteractor, - udfpsUtils + udfpsUtils, + iconProvider, + activityTaskManager ), { credentialViewModel }, Handler(TestableLooper.get(this).looper), diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt index df0e5a718ed9..5e4272f125d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt @@ -16,13 +16,8 @@ package com.android.systemui.biometrics.data.repository -import android.hardware.biometrics.BiometricManager -import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT -import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton import android.hardware.biometrics.PromptInfo -import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.shared.model.PromptKind @@ -139,83 +134,6 @@ class PromptRepositoryImplTest : SysuiTestCase() { } @Test - fun showBpWithoutIconForCredential_withVerticalListContentView() = - testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP) - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - for (case in - listOf( - PromptKind.Biometric(), - PromptKind.Pin, - PromptKind.Password, - PromptKind.Pattern - )) { - val hasCredentialViewShown = case !is PromptKind.Biometric - val promptInfo = - PromptInfo().apply { - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL - contentView = PromptVerticalListContentView.Builder().build() - } - repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME) - repository.setShouldShowBpWithoutIconForCredential(promptInfo) - - assertThat(repository.showBpWithoutIconForCredential.value) - .isEqualTo(!hasCredentialViewShown) - } - } - - @Test - fun showBpWithoutIconForCredential_withContentViewWithMoreOptionsButton() = - testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP) - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - val promptInfo = - PromptInfo().apply { - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL - contentView = - PromptContentViewWithMoreOptionsButton.Builder() - .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> } - .build() - } - for (case in - listOf( - PromptKind.Biometric(), - PromptKind.Pin, - PromptKind.Password, - PromptKind.Pattern - )) { - repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME) - repository.setShouldShowBpWithoutIconForCredential(promptInfo) - - assertThat(repository.showBpWithoutIconForCredential.value).isFalse() - } - } - - @Test - fun showBpWithoutIconForCredential_withDescription() = - testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP) - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - for (case in - listOf( - PromptKind.Biometric(), - PromptKind.Pin, - PromptKind.Password, - PromptKind.Pattern - )) { - val promptInfo = - PromptInfo().apply { - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL - description = "description" - } - repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME) - repository.setShouldShowBpWithoutIconForCredential(promptInfo) - - assertThat(repository.showBpWithoutIconForCredential.value).isFalse() - } - } - - @Test fun setsAndUnsetsPrompt() = testScope.runTest { val kind = PromptKind.Pin @@ -223,7 +141,7 @@ class PromptRepositoryImplTest : SysuiTestCase() { repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME) - assertThat(repository.kind.value).isEqualTo(kind) + assertThat(repository.promptKind.value).isEqualTo(kind) assertThat(repository.userId.value).isEqualTo(USER_ID) assertThat(repository.challenge.value).isEqualTo(CHALLENGE) assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt index 2172bc5ee8e1..8695c01e89d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt @@ -5,12 +5,12 @@ import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.domain.model.BiometricOperationInfo import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.biometrics.promptInfo import com.android.systemui.biometrics.shared.model.BiometricUserInfo +import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.coroutines.collectLastValue import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -110,7 +110,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.description = description it.subtitle = subtitle }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME @@ -135,7 +135,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.subtitle = subtitle it.contentView = contentView }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME @@ -163,7 +163,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.subtitle = subtitle it.contentView = contentView }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME @@ -171,13 +171,13 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(showTitleOnly).isFalse() } - @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN) + @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pin) - @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD) + @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(PromptKind.Password) - @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN) + @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pattern) - private fun useCredentialForPrompt(kind: Int) = + private fun useCredentialForPrompt(kind: PromptKind) = testScope.runTest { val isStealth = false credentialInteractor.stealthMode = isStealth @@ -211,11 +211,10 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(prompt) .isInstanceOf( when (kind) { - Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java - Utils.CREDENTIAL_PASSWORD -> + PromptKind.Pin -> BiometricPromptRequest.Credential.Pin::class.java + PromptKind.Password -> BiometricPromptRequest.Credential.Password::class.java - Utils.CREDENTIAL_PATTERN -> - BiometricPromptRequest.Credential.Pattern::class.java + PromptKind.Pattern -> BiometricPromptRequest.Credential.Pattern::class.java else -> throw Exception("wrong kind") } ) @@ -341,6 +340,28 @@ class PromptCredentialInteractorTest : SysuiTestCase() { job.cancel() } + + /** Update the current request to use credential-based authentication instead of biometrics. */ + private fun PromptCredentialInteractor.useCredentialsForAuthentication( + promptInfo: PromptInfo, + kind: PromptKind, + userId: Int, + challenge: Long, + opPackageName: String, + ) { + biometricPromptRepository.setPrompt( + promptInfo, + userId, + challenge, + kind, + opPackageName, + ) + } + + /** Unset the current authentication request. */ + private fun PromptCredentialInteractor.resetPrompt() { + biometricPromptRepository.unsetPrompt() + } } private fun pinRequest(): BiometricPromptRequest.Credential.Pin = diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt index 2817780cbf03..4068404da29f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -17,12 +17,14 @@ package com.android.systemui.biometrics.domain.interactor import android.app.admin.DevicePolicyManager +import android.content.ComponentName import android.hardware.biometrics.BiometricManager.Authenticators +import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton import android.hardware.biometrics.PromptInfo +import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.faceSensorPropertiesInternal @@ -30,8 +32,10 @@ import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -44,19 +48,22 @@ import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.junit.MockitoJUnit -private const val TITLE = "hey there" -private const val SUBTITLE = "ok" -private const val DESCRIPTION = "football" -private const val NEGATIVE_TEXT = "escape" - -private const val USER_ID = 8 -private const val CHALLENGE = 999L -private const val OP_PACKAGE_NAME = "biometric.testapp" - @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class PromptSelectorInteractorImplTest : SysuiTestCase() { + companion object { + private const val TITLE = "hey there" + private const val SUBTITLE = "ok" + private const val DESCRIPTION = "football" + private const val NEGATIVE_TEXT = "escape" + + private const val USER_ID = 8 + private const val CHALLENGE = 999L + private const val OP_PACKAGE_NAME = "biometric.testapp" + private val componentNameOverriddenForConfirmDeviceCredentialActivity = + ComponentName("not.com.android.settings", "testapp") + } @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @@ -65,6 +72,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { private val testScope = TestScope() private val fingerprintRepository = FakeFingerprintPropertyRepository() private val promptRepository = FakePromptRepository() + private val fakeExecutor = FakeExecutor(FakeSystemClock()) private lateinit var interactor: PromptSelectorInteractor @@ -74,6 +82,23 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) } + private fun basicPromptInfo() = + PromptInfo().apply { + title = TITLE + subtitle = SUBTITLE + description = DESCRIPTION + negativeButtonText = NEGATIVE_TEXT + isConfirmationRequested = true + isDeviceCredentialAllowed = true + authenticators = Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL + } + + private val modalities = + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal().first(), + faceProperties = faceSensorPropertiesInternal().first(), + ) + @Test fun useBiometricsAndReset() = testScope.runTest { useBiometricsAndReset(allowCredentialFallback = true) } @@ -82,16 +107,24 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { fun useBiometricsAndResetWithoutFallback() = testScope.runTest { useBiometricsAndReset(allowCredentialFallback = false) } - private fun TestScope.useBiometricsAndReset(allowCredentialFallback: Boolean) { + @Test + fun useBiometricsAndResetOnConfirmDeviceCredentialActivity() = + testScope.runTest { + useBiometricsAndReset( + allowCredentialFallback = true, + setComponentNameForConfirmDeviceCredentialActivity = true + ) + } + + private fun TestScope.useBiometricsAndReset( + allowCredentialFallback: Boolean, + setComponentNameForConfirmDeviceCredentialActivity: Boolean = false + ) { setUserCredentialType(isPassword = true) val confirmationRequired = true val info = - PromptInfo().apply { - title = TITLE - subtitle = SUBTITLE - description = DESCRIPTION - negativeButtonText = NEGATIVE_TEXT + basicPromptInfo().apply { isConfirmationRequested = confirmationRequired authenticators = if (allowCredentialFallback) { @@ -100,26 +133,27 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { Authenticators.BIOMETRIC_STRONG } isDeviceCredentialAllowed = allowCredentialFallback + componentNameForConfirmDeviceCredentialActivity = + if (setComponentNameForConfirmDeviceCredentialActivity) + componentNameOverriddenForConfirmDeviceCredentialActivity + else null } - val modalities = - BiometricModalities( - fingerprintProperties = fingerprintSensorPropertiesInternal().first(), - faceProperties = faceSensorPropertiesInternal().first(), - ) val currentPrompt by collectLastValue(interactor.prompt) - val credentialKind by collectLastValue(interactor.credentialKind) + val promptKind by collectLastValue(interactor.promptKind) val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed) - val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequired) + val credentialKind by collectLastValue(interactor.credentialKind) + val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired) assertThat(currentPrompt).isNull() - interactor.useBiometricsForAuthentication( + interactor.setPrompt( info, USER_ID, - CHALLENGE, modalities, - OP_PACKAGE_NAME + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ ) assertThat(currentPrompt).isNotNull() @@ -128,36 +162,179 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE) assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT) assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME) + assertThat(promptKind!!.isBiometric()).isTrue() + assertThat(currentPrompt?.componentNameForConfirmDeviceCredentialActivity) + .isEqualTo( + if (setComponentNameForConfirmDeviceCredentialActivity) + componentNameOverriddenForConfirmDeviceCredentialActivity + else null + ) if (allowCredentialFallback) { assertThat(credentialKind).isSameInstanceAs(PromptKind.Password) assertThat(isCredentialAllowed).isTrue() } else { - assertThat(credentialKind).isEqualTo(PromptKind.Biometric()) + assertThat(credentialKind).isEqualTo(PromptKind.None) assertThat(isCredentialAllowed).isFalse() } - assertThat(isExplicitConfirmationRequired).isEqualTo(confirmationRequired) + assertThat(isConfirmationRequired).isEqualTo(confirmationRequired) interactor.resetPrompt() verifyUnset() } @Test - fun usePinCredentialAndReset() = - testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) } + fun usePinCredentialAndReset() = testScope.runTest { useCredentialAndReset(PromptKind.Pin) } @Test fun usePatternCredentialAndReset() = - testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) } + testScope.runTest { useCredentialAndReset(PromptKind.Pattern) } @Test fun usePasswordCredentialAndReset() = - testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PASSWORD) } + testScope.runTest { useCredentialAndReset(PromptKind.Password) } + + @Test + fun promptKind_isBiometric_whenBiometricAllowed() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = basicPromptInfo() + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind?.isBiometric()).isTrue() + + interactor.resetPrompt() + verifyUnset() + } + + @Test + fun promptKind_isCredential_onSwitchToCredential() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = basicPromptInfo() + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + true /*onSwitchToCredential*/ + ) + + assertThat(promptKind).isEqualTo(PromptKind.Password) + + interactor.resetPrompt() + verifyUnset() + } - private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) { + @Test + fun promptKind_isCredential_whenBiometricIsNotAllowed() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = + basicPromptInfo().apply { + isDeviceCredentialAllowed = true + authenticators = Authenticators.DEVICE_CREDENTIAL + } + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind).isEqualTo(PromptKind.Password) + + interactor.resetPrompt() + verifyUnset() + } + + @Test + fun promptKind_isCredential_whenBiometricIsNotAllowed_withMoreOptionsButton() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = + basicPromptInfo().apply { + isDeviceCredentialAllowed = true + authenticators = Authenticators.DEVICE_CREDENTIAL + contentView = + PromptContentViewWithMoreOptionsButton.Builder() + .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> } + .build() + } + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind).isEqualTo(PromptKind.Password) + + interactor.resetPrompt() + verifyUnset() + } + + @Test + fun promptKind_isBiometric_whenBiometricIsNotAllowed_withVerticalList() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = + basicPromptInfo().apply { + isDeviceCredentialAllowed = true + authenticators = Authenticators.DEVICE_CREDENTIAL + contentView = PromptVerticalListContentView.Builder().build() + } + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind?.isBiometric()).isTrue() + + interactor.resetPrompt() + verifyUnset() + } + + private fun TestScope.useCredentialAndReset(kind: PromptKind) { setUserCredentialType( - isPin = kind == Utils.CREDENTIAL_PIN, - isPassword = kind == Utils.CREDENTIAL_PASSWORD, + isPin = kind == PromptKind.Pin, + isPassword = kind == PromptKind.Password, ) val info = @@ -175,11 +352,18 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { assertThat(currentPrompt).isNull() - interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME) + interactor.setPrompt( + info, + USER_ID, + BiometricModalities(), + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) // not using biometrics, should be null with no fallback option assertThat(currentPrompt).isNull() - assertThat(credentialKind).isEqualTo(PromptKind.Biometric()) + assertThat(credentialKind).isEqualTo(PromptKind.None) interactor.resetPrompt() verifyUnset() @@ -187,13 +371,16 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { private fun TestScope.verifyUnset() { val currentPrompt by collectLastValue(interactor.prompt) + val promptKind by collectLastValue(interactor.promptKind) + val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed) val credentialKind by collectLastValue(interactor.credentialKind) + val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired) assertThat(currentPrompt).isNull() - - val kind = credentialKind as? PromptKind.Biometric - assertThat(kind).isNotNull() - assertThat(kind?.activeModalities?.isEmpty).isTrue() + assertThat(promptKind).isEqualTo(PromptKind.None) + assertThat(isCredentialAllowed).isFalse() + assertThat(credentialKind).isEqualTo(PromptKind.None) + assertThat(isConfirmationRequired).isFalse() } private fun setUserCredentialType(isPin: Boolean = false, isPassword: Boolean = false) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt index 5caa146f6351..0d01472b45c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt @@ -16,97 +16,95 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.TestMocksModule -import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule -import com.android.systemui.collectLastValue -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.flags.parameterizeSceneContainerFlag +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.android.systemui.user.domain.UserDomainLayerModule -import com.android.systemui.util.mockito.mock +import com.android.systemui.statusbar.phone.systemUIDialogManager +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) -class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class DefaultUdfpsTouchOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { + + private val kosmos = + testKosmos().apply { + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) } + } + private val testScope = kosmos.testScope + @Captor private lateinit var sysuiDialogListenerCaptor: ArgumentCaptor<SystemUIDialogManager.Listener> - private var systemUIDialogManager: SystemUIDialogManager = mock() + private var systemUIDialogManager = kosmos.systemUIDialogManager + private val keyguardRepository = kosmos.fakeKeyguardRepository + + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + + private lateinit var underTest: DefaultUdfpsTouchOverlayViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return parameterizeSceneContainerFlag() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Before fun setUp() { MockitoAnnotations.initMocks(this) + underTest = + DefaultUdfpsTouchOverlayViewModel( + kosmos.shadeInteractor, + systemUIDialogManager, + ) } - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - BiometricsDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<DefaultUdfpsTouchOverlayViewModel> { - val keyguardRepository: FakeKeyguardRepository - val shadeRepository: FakeShadeRepository - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - mocks: TestMocksModule, - ): TestComponent - } - } - - private fun TestComponent.shadeExpanded(expanded: Boolean) { + private fun shadeExpanded(expanded: Boolean) { if (expanded) { - shadeRepository.setLegacyShadeExpansion(1f) - shadeRepository.setLegacyShadeTracking(false) - shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true) + shadeTestUtil.setShadeExpansion(1f) + shadeTestUtil.setTracking(false) + shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(true) } else { keyguardRepository.setStatusBarState(StatusBarState.SHADE) - shadeRepository.setLegacyShadeExpansion(0f) - shadeRepository.setLegacyShadeTracking(false) - shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false) + shadeTestUtil.setShadeExpansion(0f) + shadeTestUtil.setTracking(false) + shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false) } } - private val testComponent: TestComponent = - DaggerDefaultUdfpsTouchOverlayViewModelTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - mocks = TestMocksModule(systemUIDialogManager = systemUIDialogManager), - ) - @Test + @BrokenWithSceneContainer(339465026) fun shadeNotExpanded_noDialogShowing_shouldHandleTouchesTrue() = - testComponent.runTest { + testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) runCurrent() @@ -120,7 +118,7 @@ class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() { @Test fun shadeNotExpanded_dialogShowing_shouldHandleTouchesFalse() = - testComponent.runTest { + testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) runCurrent() @@ -134,7 +132,7 @@ class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() { @Test fun shadeExpanded_noDialogShowing_shouldHandleTouchesFalse() = - testComponent.runTest { + testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index a7324182a91a..fa78f0c6ec1d 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 @@ -94,6 +100,7 @@ private const val CHALLENGE = 2L private const val DELAY = 1000L private const val OP_PACKAGE_NAME = "biometric.testapp" private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon" +private const val OP_PACKAGE_NAME_CAN_NOT_BE_FOUND = "can.not.be.found" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -107,18 +114,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 +181,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 +196,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 +1273,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 +1296,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 +1307,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 +1321,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 +1332,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 +1452,26 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa packageName: String = OP_PACKAGE_NAME, block: suspend TestScope.() -> Unit, ) { + val topActivity = ComponentName(packageName, "test app") + runningTaskInfo.topActivity = topActivity + whenever(activityTaskManager.getTasks(1)).thenReturn(listOf(runningTaskInfo)) + selector = + PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) + selector.resetPrompt() + + viewModel = + PromptViewModel( + displayStateInteractor, + selector, + mContext, + udfpsOverlayInteractor, + biometricStatusInteractor, + udfpsUtils, + iconProvider, + activityTaskManager + ) + iconViewModel = viewModel.iconViewModel + selector.initializePrompt( requireConfirmation = testCase.confirmationRequested, allowCredentialFallback = allowCredentialFallback, @@ -1630,12 +1685,13 @@ private fun PromptSelectorInteractor.initializePrompt( isConfirmationRequested = requireConfirmation } - useBiometricsForAuthentication( + setPrompt( info, USER_ID, - CHALLENGE, BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face), + CHALLENGE, packageName, + false /*onUseDeviceCredential*/ ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java index a569ceec32b2..49f204372730 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java @@ -21,7 +21,7 @@ import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMIS import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -92,7 +92,7 @@ public class BroadcastDialogDelegateTest extends SysuiTestCase { when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); when(mSystemUIDialogFactory.create(any(), any())).thenReturn(mDialog); mBroadcastDialogDelegate = new BroadcastDialogDelegate( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt index 62c98b05cbd5..72156194b0e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt @@ -50,7 +50,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit @@ -104,7 +104,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dispatcher = UnconfinedTestDispatcher(scheduler) testScope = TestScope(dispatcher) - whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState) + whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState) mBluetoothTileDialogDelegate = BluetoothTileDialogDelegate( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt index f62a55d02f61..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,6 +17,7 @@ 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 @@ -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() @@ -113,7 +115,6 @@ 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) 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/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/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/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt index 86533086f67a..44a8904f50da 100644 --- 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 @@ -26,6 +26,8 @@ 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 @@ -47,7 +49,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val testHelper = kosmos.shortcutHelperTestHelper - + private val sysUiState = kosmos.sysUiState private val viewModel = kosmos.shortcutHelperViewModel @Test @@ -90,12 +92,12 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { } @Test - fun shouldShow_falseAfterViewDestroyed() = + fun shouldShow_falseAfterViewClosed() = testScope.runTest { val shouldShow by collectLastValue(viewModel.shouldShow) testHelper.toggle(deviceId = 567) - viewModel.onUserLeave() + viewModel.onViewClosed() assertThat(shouldShow).isFalse() } @@ -108,7 +110,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { testHelper.hideForSystem() testHelper.toggle(deviceId = 987) testHelper.showFromActivity() - viewModel.onUserLeave() + viewModel.onViewClosed() testHelper.hideFromActivity() testHelper.hideForSystem() testHelper.toggle(deviceId = 456) @@ -127,4 +129,27 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { 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/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/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/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 1dc58d1784d7..00f94a57a2d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.keyguard.KeyguardSecurityModel @@ -29,7 +31,10 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dock.fakeDockManager +import com.android.systemui.flags.BrokenWithSceneContainer +import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeCommandQueue import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -46,7 +51,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.commandQueue import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever @@ -61,13 +67,14 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.reset import org.mockito.Mockito.spy import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters /** * Class for testing user journeys through the interactors. They will all be activated during setup, @@ -75,8 +82,8 @@ import org.mockito.MockitoAnnotations */ @ExperimentalCoroutinesApi @SmallTest -@RunWith(JUnit4::class) -class KeyguardTransitionScenariosTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository()) @@ -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 = kosmos.powerInteractor - private val communalInteractor = kosmos.communalInteractor - private val dockManager = kosmos.fakeDockManager + private val powerInteractor by lazy { kosmos.powerInteractor } + private val communalInteractor by lazy { kosmos.communalInteractor } + private val dockManager by lazy { 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 @@ -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 @@ -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,6 +1280,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun occludedToGlanceableHubWhenInitiallyOnHub() = testScope.runTest { // GIVEN a device on lockscreen and communal is available @@ -1314,6 +1366,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun primaryBouncerToOccluded() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1339,6 +1392,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun dozingToOccluded() = testScope.runTest { // GIVEN a prior transition has run to DOZING @@ -1364,6 +1418,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun dreamingToOccluded() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -1392,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 @@ -1445,6 +1533,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun dreamingToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -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 @@ -1586,6 +1679,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToPrimaryBouncerDragging() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1595,8 +1689,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 +1707,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 +1723,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun lockscreenToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1686,6 +1781,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1740,6 +1836,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToDozing() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1761,6 +1858,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -1782,6 +1880,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToAlternateBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -1803,6 +1902,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun glanceableHubToOccluded() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1833,6 +1933,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToGone() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1854,6 +1955,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToDreaming() = testScope.runTest { // GIVEN that we are dreaming and not dozing diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index b1a8dd1d3fdc..a77169e74de5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -18,20 +18,29 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -57,14 +66,22 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { .thenReturn(surfaceBehindIsAnimatingFlow) } - private val underTest = kosmos.windowManagerLockscreenVisibilityInteractor + private val underTest = lazy { kosmos.windowManagerLockscreenVisibilityInteractor } private val testScope = kosmos.testScope private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + @Before + fun setUp() { + // lazy value needs to be called here otherwise flow collection misbehaves + underTest.value + kosmos.sceneContainerRepository.setTransitionState(sceneTransitions) + } + @Test + @DisableSceneContainer fun surfaceBehindVisibility_switchesToCorrectFlow() = testScope.runTest { - val values by collectValues(underTest.surfaceBehindVisibility) + val values by collectValues(underTest.value.surfaceBehindVisibility) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -170,9 +187,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun testUsingGoingAwayAnimation_duringTransitionToGone() = testScope.runTest { - val values by collectValues(underTest.usingKeyguardGoingAwayAnimation) + val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -230,9 +248,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() = testScope.runTest { - val values by collectValues(underTest.usingKeyguardGoingAwayAnimation) + val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -319,9 +338,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun lockscreenVisibility_visibleWhenGone() = testScope.runTest { - val values by collectValues(underTest.lockscreenVisibility) + val values by collectValues(underTest.value.lockscreenVisibility) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -385,9 +405,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun testLockscreenVisibility_usesFromState_ifCanceled() = testScope.runTest { - val values by collectValues(underTest.lockscreenVisibility) + val values by collectValues(underTest.value.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -486,9 +507,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { * state during the AOD/isAsleep -> GONE transition is AOD (where lockscreen visibility = true). */ @Test + @DisableSceneContainer fun testLockscreenVisibility_falseDuringTransitionToGone_fromCanceledGone() = testScope.runTest { - val values by collectValues(underTest.lockscreenVisibility) + val values by collectValues(underTest.value.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -587,11 +609,11 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { ) } - /** */ @Test + @DisableSceneContainer fun testLockscreenVisibility_trueDuringTransitionToGone_fromNotCanceledGone() = testScope.runTest { - val values by collectValues(underTest.lockscreenVisibility) + val values by collectValues(underTest.value.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -702,4 +724,109 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { values ) } + + @Test + @EnableSceneContainer + fun sceneContainer_lockscreenVisibility_visibleWhenNotGone() = + testScope.runTest { + val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility) + + sceneTransitions.value = lsToGone + assertThat(lockscreenVisibility).isTrue() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = goneToLs + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + assertThat(lockscreenVisibility).isTrue() + } + + @Test + @EnableSceneContainer + fun sceneContainer_lockscreenVisibility_notVisibleWhenReturningToGone() = + testScope.runTest { + val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility) + + sceneTransitions.value = goneToLs + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = lsToGone + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = goneToLs + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + assertThat(lockscreenVisibility).isTrue() + } + + @Test + @EnableSceneContainer + fun sceneContainer_usingGoingAwayAnimation_duringTransitionToGone() = + testScope.runTest { + val usingKeyguardGoingAwayAnimation by + collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation) + + sceneTransitions.value = lsToGone + assertThat(usingKeyguardGoingAwayAnimation).isTrue() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + assertThat(usingKeyguardGoingAwayAnimation).isFalse() + } + + @Test + @EnableSceneContainer + fun sceneContainer_usingGoingAwayAnimation_surfaceBehindIsAnimating() = + testScope.runTest { + val usingKeyguardGoingAwayAnimation by + collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation) + + sceneTransitions.value = lsToGone + surfaceBehindIsAnimatingFlow.emit(true) + assertThat(usingKeyguardGoingAwayAnimation).isTrue() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + assertThat(usingKeyguardGoingAwayAnimation).isTrue() + + sceneTransitions.value = goneToLs + assertThat(usingKeyguardGoingAwayAnimation).isTrue() + + surfaceBehindIsAnimatingFlow.emit(false) + assertThat(usingKeyguardGoingAwayAnimation).isFalse() + } + + companion object { + private val progress = MutableStateFlow(0f) + + private val sceneTransitions = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + + private val lsToGone = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Gone, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + private val goneToLs = + ObservableTransitionState.Transition( + Scenes.Gone, + Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt new file mode 100644 index 000000000000..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/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 index 0bca36775e9f..f61ddeb47514 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue @@ -150,6 +151,51 @@ class DeviceEntryIconViewModelTest : SysuiTestCase() { assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE) } + fun accessibilityDelegateHint_accessibilityNotEnabled() = + testScope.runTest { + val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint) + kosmos.fakeAccessibilityRepository.isEnabled.value = false + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE) + } + + @Test + fun accessibilityDelegateHint_accessibilityEnabled_locked() = + testScope.runTest { + val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint) + kosmos.fakeAccessibilityRepository.isEnabled.value = true + + // interactive lock icon + keyguardRepository.setKeyguardDismissible(false) + fingerprintPropertyRepository.supportsUdfps() + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE) + + // non-interactive lock icon + keyguardRepository.setKeyguardDismissible(false) + fingerprintPropertyRepository.supportsRearFps() + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE) + } + + @Test + fun accessibilityDelegateHint_accessibilityEnabled_unlocked() = + testScope.runTest { + val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint) + kosmos.fakeAccessibilityRepository.isEnabled.value = true + + // interactive unlock icon + keyguardRepository.setKeyguardDismissible(true) + fingerprintPropertyRepository.supportsUdfps() + advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay + runCurrent() + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.ENTER) + } + private fun deviceEntryIconTransitionAlpha(alpha: Float) { deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 1881a9e0c205..16421a0f83b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -88,7 +88,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { @Mock private lateinit var expandable: Expandable @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper @@ -115,7 +115,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTest private val kosmos = testKosmos() init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt index 0c98cff89ee2..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..7856f9bce5cc 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)) @@ -186,6 +200,15 @@ class MediaCarouselControllerTest : SysuiTestCase() { ) } + @After + fun tearDown() { + Settings.Secure.putInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RESUME, + originalResumeSetting + ) + } + @Test fun testPlayerOrdering() { // Test values: key, data, last active time @@ -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/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java index 3b6a88af1ee0..5dbfe475fedc 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; @@ -91,8 +93,8 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { } @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()); @@ -105,8 +107,8 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { } @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()); @@ -119,8 +121,8 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { } @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()); @@ -133,8 +135,8 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { } @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); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index ff7c970960e9..8f8630e90694 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -104,11 +104,11 @@ public class MediaDreamSentinelTest extends SysuiTestCase { listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true, /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false); - listener.onMediaDataRemoved(mKey); + listener.onMediaDataRemoved(mKey, false); verify(mDreamOverlayStateController, never()).removeComplication(any()); when(mMediaDataManager.hasActiveMedia()).thenReturn(false); - listener.onMediaDataRemoved(mKey); + listener.onMediaDataRemoved(mKey, false); verify(mDreamOverlayStateController).removeComplication(eq(mMediaEntryComplication)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt index 8e0541008f59..c06a28e3d840 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt @@ -42,13 +42,13 @@ class SysUiStateExtTest : SysuiTestCase() { fun updateFlags() { underTest.updateFlags( Display.DEFAULT_DISPLAY, - 1 to true, - 2 to false, - 3 to true, + 1L to true, + 2L to false, + 3L to true, ) - assertThat(underTest.flags and 1).isNotEqualTo(0) - assertThat(underTest.flags and 2).isEqualTo(0) - assertThat(underTest.flags and 3).isNotEqualTo(0) + assertThat(underTest.flags and 1L).isNotEqualTo(0L) + assertThat(underTest.flags and 2L).isEqualTo(0L) + assertThat(underTest.flags and 3L).isNotEqualTo(0L) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/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/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/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/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index f57f04000be9..68307b1b905e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -16,10 +16,12 @@ package com.android.systemui.qs.external; import static android.os.PowerExemptionManager.REASON_TILE_ONCLICK; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -55,13 +57,15 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IDeviceIdleController; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.quicksettings.IQSService; import android.service.quicksettings.IQSTileService; import android.service.quicksettings.TileService; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -73,12 +77,24 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mockito; import org.mockito.MockitoSession; +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) public class TileLifecycleManagerTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + private final PackageManagerAdapter mMockPackageManagerAdapter = mock(PackageManagerAdapter.class); private final BroadcastDispatcher mMockBroadcastDispatcher = @@ -98,6 +114,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { private TestContextWrapper mWrappedContext; private MockitoSession mMockitoSession; + public TileLifecycleManagerTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { setPackageEnabled(true); @@ -263,7 +284,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { } @Test - public void testNoClickOfNotListeningAnymore() throws Exception { + @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testNoClickIfNotListeningAnymore() throws Exception { mStateManager.onTileAdded(); mStateManager.onStartListening(); mStateManager.onClick(null); @@ -279,6 +301,42 @@ public class TileLifecycleManagerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testNoClickIfNotListeningBeforeClick() throws Exception { + mStateManager.onTileAdded(); + mStateManager.onStartListening(); + mStateManager.onStopListening(); + mStateManager.onClick(null); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); + + verifyBind(1); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); + assertFalse(mContext.isBound(mTileServiceComponentName)); + verify(mMockTileService, never()).onClick(null); + } + + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testClickIfStopListeningBeforeProcessedClick() throws Exception { + mStateManager.onTileAdded(); + mStateManager.onStartListening(); + mStateManager.onClick(null); + mStateManager.onStopListening(); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); + + verifyBind(1); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); + assertFalse(mContext.isBound(mTileServiceComponentName)); + InOrder inOrder = Mockito.inOrder(mMockTileService); + inOrder.verify(mMockTileService).onClick(null); + inOrder.verify(mMockTileService).onStopListening(); + } + + @Test public void testComponentEnabling() throws Exception { mStateManager.onTileAdded(); mStateManager.onStartListening(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java index 0ff29dbbfde7..1c86638c9f27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java @@ -15,12 +15,18 @@ */ package com.android.systemui.qs.external; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; +import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -32,16 +38,19 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; -import android.os.HandlerThread; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; @@ -51,10 +60,20 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) public class TileServiceManagerTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + @Mock private TileServices mTileServices; @Mock @@ -68,17 +87,22 @@ public class TileServiceManagerTest extends SysuiTestCase { @Mock private CustomTileAddedRepository mCustomTileAddedRepository; - private HandlerThread mThread; - private Handler mHandler; + private FakeExecutor mFakeExecutor; + private TileServiceManager mTileServiceManager; private ComponentName mComponentName; + public TileServiceManagerTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mThread = new HandlerThread("TestThread"); - mThread.start(); - mHandler = Handler.createAsync(mThread.getLooper()); + mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + Handler handler = mockExecutorHandler(mFakeExecutor); + when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); @@ -90,13 +114,12 @@ public class TileServiceManagerTest extends SysuiTestCase { mComponentName = new ComponentName(mContext, TileServiceManagerTest.class); when(mTileLifecycle.getComponent()).thenReturn(mComponentName); - mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker, + mTileServiceManager = new TileServiceManager(mTileServices, handler, mUserTracker, mCustomTileAddedRepository, mTileLifecycle); } @After public void tearDown() throws Exception { - mThread.quit(); mTileServiceManager.handleDestroy(); } @@ -201,4 +224,59 @@ public class TileServiceManagerTest extends SysuiTestCase { verify(mTileLifecycle, times(2)).executeSetBindService(captor.capture()); assertFalse((boolean) captor.getValue()); } + + @Test + @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testStopListeningAndUnbindImmediatelyAfterUpdate() { + when(mTileLifecycle.isActiveTile()).thenReturn(true); + mTileServiceManager.startLifecycleManagerAndAddTile(); + mTileServiceManager.setBindAllowed(true); + clearInvocations(mTileLifecycle); + + mTileServiceManager.setBindRequested(true); + verify(mTileLifecycle).executeSetBindService(true); + + mTileServiceManager.setLastUpdate(0); + mFakeExecutor.advanceClockToLast(); + mFakeExecutor.runAllReady(); + verify(mTileLifecycle).onStopListening(); + verify(mTileLifecycle).executeSetBindService(false); + } + + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testStopListeningAndUnbindImmediatelyAfterUpdate_ifRequestedFromTileService() { + when(mTileLifecycle.isActiveTile()).thenReturn(true); + mTileServiceManager.startLifecycleManagerAndAddTile(); + mTileServiceManager.setBindAllowed(true); + clearInvocations(mTileLifecycle); + + mTileServiceManager.setBindRequested(true); + mTileServiceManager.onStartListeningFromRequest(); + verify(mTileLifecycle).onStartListening(); + + mTileServiceManager.setLastUpdate(0); + mFakeExecutor.advanceClockToLast(); + mFakeExecutor.runAllReady(); + verify(mTileLifecycle).onStopListening(); + verify(mTileLifecycle).executeSetBindService(false); + } + + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testNotUnbindImmediatelyAfterUpdate_ifRequestedFromSystemUI() { + when(mTileLifecycle.isActiveTile()).thenReturn(true); + mTileServiceManager.startLifecycleManagerAndAddTile(); + mTileServiceManager.setBindAllowed(true); + clearInvocations(mTileLifecycle); + + mTileServiceManager.setBindRequested(true); + // The tile requests startListening (because a click happened) + + mTileServiceManager.setLastUpdate(0); + mFakeExecutor.advanceClockToLast(); + mFakeExecutor.runAllReady(); + verify(mTileLifecycle, never()).onStopListening(); + verify(mTileLifecycle, never()).executeSetBindService(false); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index b62d59d3a2f2..bcff88a49ad6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -15,6 +15,10 @@ */ package com.android.systemui.qs.external; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; @@ -33,8 +37,10 @@ import android.os.Binder; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.quicksettings.IQSTileService; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -64,13 +70,23 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.List; import javax.inject.Provider; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper public class TileServicesTest extends SysuiTestCase { + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2; private static final ComponentName TEST_COMPONENT = @@ -106,6 +122,11 @@ public class TileServicesTest extends SysuiTestCase { @Mock private CustomTileAddedRepository mCustomTileAddedRepository; + public TileServicesTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -194,6 +215,7 @@ public class TileServicesTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) public void testRequestListeningStatusCommand() throws RemoteException { ArgumentCaptor<CommandQueue.Callbacks> captor = ArgumentCaptor.forClass(CommandQueue.Callbacks.class); @@ -213,6 +235,26 @@ public class TileServicesTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testRequestListeningStatusCommand_onStartListeningFromRequest() { + ArgumentCaptor<CommandQueue.Callbacks> captor = + ArgumentCaptor.forClass(CommandQueue.Callbacks.class); + verify(mCommandQueue).addCallback(captor.capture()); + + CustomTile mockTile = mock(CustomTile.class); + when(mockTile.getComponent()).thenReturn(TEST_COMPONENT); + + TileServiceManager manager = mTileService.getTileWrapper(mockTile); + when(manager.isActiveTile()).thenReturn(true); + when(manager.getTileService()).thenReturn(mock(IQSTileService.class)); + + captor.getValue().requestTileServiceListeningState(TEST_COMPONENT); + mTestableLooper.processAllMessages(); + verify(manager).setBindRequested(true); + verify(manager).onStartListeningFromRequest(); + } + + @Test public void testValidCustomTileStartsActivity() { CustomTile tile = mock(CustomTile.class); PendingIntent pi = mock(PendingIntent.class); @@ -263,6 +305,7 @@ public class TileServicesTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) public void tileFreedForCorrectUser() throws RemoteException { verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture()); @@ -297,6 +340,42 @@ public class TileServicesTest extends SysuiTestCase { verify(manager1.getTileService()).onStartListening(); } + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void tileFreedForCorrectUser_onStartListeningFromRequest() throws RemoteException { + verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture()); + + ComponentName componentName = new ComponentName("pkg", "cls"); + CustomTile tileUser0 = mock(CustomTile.class); + CustomTile tileUser1 = mock(CustomTile.class); + + when(tileUser0.getComponent()).thenReturn(componentName); + when(tileUser1.getComponent()).thenReturn(componentName); + when(tileUser0.getUser()).thenReturn(0); + when(tileUser1.getUser()).thenReturn(1); + + // Create a tile for user 0 + TileServiceManager manager0 = mTileService.getTileWrapper(tileUser0); + when(manager0.isActiveTile()).thenReturn(true); + // Then create a tile for user 1 + TileServiceManager manager1 = mTileService.getTileWrapper(tileUser1); + when(manager1.isActiveTile()).thenReturn(true); + + // When the tile for user 0 gets freed + mTileService.freeService(tileUser0, manager0); + // and the user is 1 + when(mUserTracker.getUserId()).thenReturn(1); + + // a call to requestListeningState + mCallbacksArgumentCaptor.getValue().requestTileServiceListeningState(componentName); + mTestableLooper.processAllMessages(); + + // will call in the correct tile + verify(manager1).setBindRequested(true); + // and set it to listening + verify(manager1).onStartListeningFromRequest(); + } + private class TestTileServices extends TileServices { TestTileServices(QSHost host, Provider<Handler> handlerProvider, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt new file mode 100644 index 000000000000..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/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/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/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index 2536a9335908..9798562ab5a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -5,6 +5,7 @@ import static android.provider.Settings.Global.AIRPLANE_MODE_ON; import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS; import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT; import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR; +import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource; @@ -217,6 +218,8 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry); when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt())) .thenReturn(mSystemUIToast); when(mSystemUIToast.getView()).thenReturn(mToastView); @@ -1083,19 +1086,34 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { } @Test - public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() { - when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{}); + public void hasActiveSubIdOnDds_noDds_returnFalse() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); - assertThat(mInternetDialogController.hasActiveSubId()).isFalse(); + assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse(); } @Test - public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() { - when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); + public void hasActiveSubIdOnDds_activeDds_returnTrue() { + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isTrue(); + } + + @Test + public void hasActiveSubIdOnDds_activeDdsAndHasProvisioning_returnFalse() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(info.isEmbedded()).thenReturn(true); + when(info.getProfileClass()).thenReturn(PROFILE_CLASS_PROVISIONING); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); - assertThat(mInternetDialogController.hasActiveSubId()).isTrue(); + assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse(); } private String getResourcesString(String name) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java index 6f88891c92d7..aefcc87e79cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java @@ -251,7 +251,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { // Mobile network should be gone if the list of active subscriptionId is null. when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false); when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); - when(mInternetDialogController.hasActiveSubId()).thenReturn(false); + when(mInternetDialogController.hasActiveSubIdOnDds()).thenReturn(false); mInternetDialogDelegate.updateDialog(true); @@ -336,7 +336,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { @Test public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() { - doReturn(true).when(mInternetDialogController).hasActiveSubId(); + doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds(); when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true); mMobileToggleSwitch.setChecked(false); @@ -348,7 +348,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { @Test public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() { - doReturn(true).when(mInternetDialogController).hasActiveSubId(); + doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds(); when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false); mMobileToggleSwitch.setChecked(false); @@ -361,7 +361,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() { mInternetDialogDelegate.dismissDialog(); - doReturn(true).when(mInternetDialogController).hasActiveSubId(); + doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds(); createInternetDialog(); // The preconditions WiFi ON and Internet WiFi are already in setUp() doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); @@ -522,7 +522,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { public void updateDialog_showSecondaryDataSub() { mInternetDialogDelegate.dismissDialog(); doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId(); - doReturn(true).when(mInternetDialogController).hasActiveSubId(); + doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds(); doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled(); createInternetDialog(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java index f88a5a0d9f41..b75b3188ae64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java @@ -20,7 +20,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotSame; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -81,7 +81,7 @@ public class RearDisplayDialogControllerTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog); when(mSystemUIDialog.getContext()).thenReturn(mContext); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index effae5f67f02..74deae323b5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -72,7 +72,7 @@ import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.atLeast import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.intThat +import org.mockito.Mockito.longThat import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.times @@ -162,7 +162,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { verify(overviewProxy) .onSystemUiStateChanged( - intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE } + longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE } ) } @@ -172,7 +172,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { verify(overviewProxy) .onSystemUiStateChanged( - intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING } + longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING } ) } @@ -182,7 +182,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { verify(overviewProxy) .onSystemUiStateChanged( - intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP } + longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP } ) } @@ -194,7 +194,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { verify(overviewProxy) .onSystemUiStateChanged( - intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP } + longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP } ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt index 6846c7227d9c..fcc6b4f093ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt @@ -55,6 +55,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.spy @@ -94,7 +95,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) whenever(dprLazy.get()).thenReturn(devicePolicyResolver) - whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState) + whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState) whenever(screenCaptureDisabledDialogDelegate.createSysUIDialog()) .thenReturn(screenCaptureDisabledDialog) whenever( diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt index 5e7d8fb5df02..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,7 +21,6 @@ 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 @@ -49,7 +48,7 @@ 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>() @@ -70,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/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 537049c7b957..49a467e152ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -40,6 +40,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.ui.compose.CommunalContent import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors import com.android.systemui.coroutines.collectLastValue @@ -50,7 +51,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.shared.model.sceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.shadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat @@ -83,9 +83,9 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Mock private lateinit var communalViewModel: CommunalViewModel @Mock private lateinit var powerManager: PowerManager - @Mock private lateinit var dialogFactory: SystemUIDialogFactory @Mock private lateinit var touchMonitor: TouchMonitor @Mock private lateinit var communalColors: CommunalColors + @Mock private lateinit var communalContent: CommunalContent private lateinit var ambientTouchComponentFactory: AmbientTouchComponent.Factory private lateinit var parentView: FrameLayout @@ -117,12 +117,12 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) } @@ -159,12 +159,12 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) @@ -303,12 +303,12 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) @@ -322,12 +322,12 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 3793970394a8..8e3290748039 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -446,6 +446,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mUiEventLogger, () -> mKosmos.getInteractionJankMonitor(), mJavaAdapter, + () -> mKeyguardTransitionInteractor, () -> mShadeInteractor, () -> mKosmos.getDeviceUnlockedInteractor(), () -> mKosmos.getSceneInteractor(), @@ -546,6 +547,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 +603,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { new UiEventLoggerFake(), () -> mKosmos.getInteractionJankMonitor(), mJavaAdapter, + () -> mKeyguardTransitionInteractor, () -> mShadeInteractor, () -> mKosmos.getDeviceUnlockedInteractor(), () -> mKosmos.getSceneInteractor(), @@ -653,7 +657,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mView.getParent()).thenReturn(mViewParent); when(mQs.getHeader()).thenReturn(mQsHeader); when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); mMainHandler = new Handler(Looper.getMainLooper()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index a867b0f7df00..4a867a8ecf22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -46,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 @@ -102,7 +103,7 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @RunWithLooper(setAsMainLooper = true) -class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : SysuiTestCase() { +class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : SysuiTestCase() { @Mock private lateinit var view: NotificationShadeWindowView @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController @@ -160,7 +161,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -173,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() @@ -518,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 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 04fa5904d2e7..845744a54791 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -42,13 +42,10 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.keyguard.data.repository.FakeCommandQueue; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor; -import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.kosmos.KosmosJavaAdapter; @@ -59,9 +56,7 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; -import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; @@ -176,12 +171,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { protected Handler mMainHandler; protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback; - protected final ShadeExpansionStateManager mShadeExpansionStateManager = - new ShadeExpansionStateManager(); - protected FragmentHostManager.FragmentListener mFragmentListener; - private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor; - private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor; @Before public void setup() { @@ -190,19 +180,11 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { mStatusBarStateController = mKosmos.getStatusBarStateController(); mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true); - FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); PowerInteractor powerInteractor = mKosmos.getPowerInteractor(); - SceneInteractor sceneInteractor = new SceneInteractor( - mTestScope.getBackgroundScope(), - new SceneContainerRepository( - mTestScope.getBackgroundScope(), - mKosmos.getFakeSceneContainerConfig(), - mKosmos.getSceneDataSource()), - mock(SceneLogger.class), - mKosmos.getDeviceUnlockedInteractor()); + SceneInteractor sceneInteractor = mKosmos.getSceneInteractor(); KeyguardTransitionInteractor keyguardTransitionInteractor = mKosmos.getKeyguardTransitionInteractor(); @@ -220,10 +202,6 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { () -> mKosmos.getSharedNotificationContainerInteractor(), mTestScope); - mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); - mFromPrimaryBouncerTransitionInteractor = - mKosmos.getFromPrimaryBouncerTransitionInteractor(); - ResourcesSplitShadeStateController splitShadeStateController = new ResourcesSplitShadeStateController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/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/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/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt index 347620a1631a..83ad18b6468b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt @@ -55,7 +55,7 @@ import platform.test.runner.parameterized.Parameters @RunWith(ParameterizedAndroidJunit4::class) @SmallTest @EnableFlags(FooterViewRefactor.FLAG_NAME) -class FooterViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } @@ -79,7 +79,7 @@ class FooterViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt new file mode 100644 index 000000000000..5e50af39203f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.app.Flags.FLAG_COMPACT_HEADS_UP_NOTIFICATION +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class HeadsUpStyleProviderImplTest : SysuiTestCase() { + + @Rule @JvmField val setFlagsRule = SetFlagsRule() + + private lateinit var statusBarModeRepositoryStore: FakeStatusBarModeRepository + private lateinit var headsUpStyleProvider: HeadsUpStyleProviderImpl + + @Before + fun setUp() { + statusBarModeRepositoryStore = FakeStatusBarModeRepository() + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true + + headsUpStyleProvider = HeadsUpStyleProviderImpl(statusBarModeRepositoryStore) + } + + @Test + @DisableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION) + fun shouldApplyCompactStyle_returnsFalse_whenCompactFlagDisabled() { + assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse() + } + + @Test + @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION) + fun shouldApplyCompactStyle_returnsTrue_whenImmersiveModeEnabled() { + // GIVEN + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true + + // THEN + assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isTrue() + } + + @Test + @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION) + fun shouldApplyCompactStyle_returnsFalse_whenImmersiveModeDisabled() { + // GIVEN + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = false + + // THEN + assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 745d20dd686b..1eed4207541c 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,5 +1,6 @@ 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 @@ -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/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt index 4f0f91a7ee56..926c35f32967 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt @@ -134,7 +134,8 @@ class StackStateAnimatorTest : SysuiTestCase() { /* isHeadsUpAnimation= */ eq(true), /* onStartedRunnable= */ any(), /* onFinishedRunnable= */ runnableCaptor.capture(), - /* animationListener= */ any() + /* animationListener= */ any(), + /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM), ) animatorTestRule.advanceTimeBy(disappearDuration) // move to the end of SSA animations diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index c08860906d0b..fe6a88d00374 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -51,6 +51,7 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeHeaderController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -80,6 +81,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private QuickSettingsController mQuickSettingsController; @Mock private ShadeViewController mShadeViewController; @Mock private PanelExpansionInteractor mPanelExpansionInteractor; + @Mock private Lazy<ShadeInteractor> mShadeInteractorLazy; @Mock private ShadeHeaderController mShadeHeaderController; @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final MetricsLogger mMetricsLogger = new FakeMetricsLogger(); @@ -115,6 +117,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { mShadeController, mCommandQueue, mPanelExpansionInteractor, + mShadeInteractorLazy, mShadeHeaderController, mRemoteInputQuickSettingsDisabler, mMetricsLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index b9312d3ce2be..4488799cfd7a 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,6 +21,7 @@ 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; @@ -70,6 +71,8 @@ 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; @@ -105,8 +108,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; @@ -224,6 +225,7 @@ import javax.inject.Provider; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) +@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION) public class CentralSurfacesImplTest extends SysuiTestCase { private static final int FOLD_STATE_FOLDED = 0; @@ -238,8 +240,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; @@ -362,13 +362,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // Set default value to avoid IllegalStateException. mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION); // 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, @@ -528,7 +524,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mScreenLifecycle, mWakefulnessLifecycle, mPowerInteractor, - mCommunalInteractor, + mKosmos.getCommunalInteractor(), mStatusBarStateController, Optional.of(mBubbles), () -> mNoteTaskController, @@ -837,6 +833,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); @@ -848,7 +845,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 @@ -862,6 +860,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); @@ -878,16 +909,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. @@ -901,16 +934,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. @@ -1105,18 +1140,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(); @@ -1151,10 +1184,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotDismissAny() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1163,10 +1196,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_largeScreen_newFlagsDisabled_dismissesTabletVersion() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1175,10 +1208,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_largeScreen_bothFlagsDisabled_dismissesPhoneVersion() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1188,10 +1221,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotDismissAny() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1200,10 +1233,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_smallScreen_newFlagsDisabled_dismissesPhoneVersion() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1213,10 +1246,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_smallScreen_bothFlagsDisabled_dismissesPhoneVersion() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1226,10 +1259,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotTogglesAny() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 321; @@ -1239,10 +1272,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_largeScreen_newFlagsDisabled_togglesTabletVersion() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 654; @@ -1253,10 +1286,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_largeScreen_bothFlagsDisabled_togglesPhoneVersion() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 987; @@ -1267,10 +1300,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotToggleAny() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 789; @@ -1280,10 +1313,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_smallScreen_newFlagsDisabled_togglesPhoneVersion() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 456; @@ -1294,10 +1327,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_smallScreen_bothFlagsDisabled_togglesPhoneVersion() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 123; 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..e8346933f192 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,6 +26,8 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; import android.content.res.Resources; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -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/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index dfee7374c104..5b2526e22679 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 @@ -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; @@ -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; @@ -225,7 +222,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mFakeExecutor, mBackgroundExecutor, mLogger, - mNotificationMediaManager, mStatusOverlayHoverListenerFactory ); } 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/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index b5525b1ce8e9..36df61d287a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -295,6 +295,50 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun subscriptions_subIsOnlyNtn_modelHasExclusivelyNtnTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) + + val onlyNtnSub = + mock<SubscriptionInfo>().also { + whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(true) + whenever(it.subscriptionId).thenReturn(45) + whenever(it.groupUuid).thenReturn(GROUP_1) + whenever(it.carrierName).thenReturn("NTN only") + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) + } + + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(onlyNtnSub)) + getSubscriptionCallback().onSubscriptionsChanged() + + assertThat(latest).hasSize(1) + assertThat(latest!![0].isExclusivelyNonTerrestrial).isTrue() + } + + @Test + fun subscriptions_subIsNotOnlyNtn_modelHasExclusivelyNtnFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) + + val notOnlyNtnSub = + mock<SubscriptionInfo>().also { + whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(false) + whenever(it.subscriptionId).thenReturn(45) + whenever(it.groupUuid).thenReturn(GROUP_1) + whenever(it.carrierName).thenReturn("NTN only") + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) + } + + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(notOnlyNtnSub)) + getSubscriptionCallback().onSubscriptionsChanged() + + assertThat(latest).hasSize(1) + assertThat(latest!![0].isExclusivelyNonTerrestrial).isFalse() + } + + @Test fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() = testScope.runTest { val latest by collectLastValue(underTest.subscriptions) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 0b14be1eefbd..0f9cbfa66b5b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -42,14 +42,11 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.UUID import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.yield -import org.junit.After import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -68,7 +65,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) } - private val testDispatcher = UnconfinedTestDispatcher() + private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) private val tableLogBuffer = @@ -113,17 +110,12 @@ class MobileIconsInteractorTest : SysuiTestCase() { ) } - @After fun tearDown() {} - @Test fun filteredSubscriptions_default() = testScope.runTest { - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf<SubscriptionModel>()) - - job.cancel() } // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2 @@ -133,12 +125,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) - - job.cancel() } @Test @@ -146,12 +135,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { testScope.runTest { connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) - - job.cancel() } @Test @@ -160,12 +146,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP)) - - job.cancel() } @Test @@ -180,12 +163,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.setSubscriptions(listOf(sub1, sub2)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(sub1, sub2)) - - job.cancel() } @Test @@ -202,13 +182,10 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) // Filtered subscriptions should show the active one when the config is false assertThat(latest).isEqualTo(listOf(sub3)) - - job.cancel() } @Test @@ -225,13 +202,10 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) // Filtered subscriptions should show the active one when the config is false assertThat(latest).isEqualTo(listOf(sub4)) - - job.cancel() } @Test @@ -248,14 +222,11 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) // Filtered subscriptions should show the primary (non-opportunistic) if the config is // true assertThat(latest).isEqualTo(listOf(sub1)) - - job.cancel() } @Test @@ -272,14 +243,11 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) // Filtered subscriptions should show the primary (non-opportunistic) if the config is // true assertThat(latest).isEqualTo(listOf(sub1)) - - job.cancel() } @Test @@ -297,12 +265,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(sub3)) - - job.cancel() } @Test @@ -320,12 +285,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(sub1)) - - job.cancel() } @Test @@ -446,313 +408,345 @@ class MobileIconsInteractorTest : SysuiTestCase() { } @Test + fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() = + testScope.runTest { + val notExclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = false, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + + connectionsRepository.setSubscriptions(listOf(notExclusivelyNonTerrestrialSub)) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub)) + } + + @Test + fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() = + testScope.runTest { + val exclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = true, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + + connectionsRepository.setSubscriptions(listOf(exclusivelyNonTerrestrialSub)) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + assertThat(latest).isEmpty() + } + + @Test + fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() = + testScope.runTest { + val exclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = true, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + val otherSub1 = + SubscriptionModel( + isExclusivelyNonTerrestrial = false, + subscriptionId = 1, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, + ) + val otherSub2 = + SubscriptionModel( + isExclusivelyNonTerrestrial = false, + subscriptionId = 2, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, + ) + + connectionsRepository.setSubscriptions( + listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2) + ) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2)) + } + + @Test + fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() = + testScope.runTest { + // Exclusively non-terrestrial sub + val exclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = true, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + + // Opportunistic subs + val (sub3, sub4) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + + // WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included + connectionsRepository.setSubscriptions(listOf(sub3, sub4, exclusivelyNonTerrestrialSub)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + // THEN both the only-non-terrestrial sub and the non-active sub are filtered out, + // leaving only sub3. + assertThat(latest).isEqualTo(listOf(sub3)) + } + + @Test fun activeDataConnection_turnedOn() = testScope.runTest { CONNECTION_1.setDataEnabled(true) - var latest: Boolean? = null - val job = - underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) - assertThat(latest).isTrue() + val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) - job.cancel() + assertThat(latest).isTrue() } @Test fun activeDataConnection_turnedOff() = testScope.runTest { CONNECTION_1.setDataEnabled(true) - var latest: Boolean? = null - val job = - underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) CONNECTION_1.setDataEnabled(false) - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun activeDataConnection_invalidSubId() = testScope.runTest { - var latest: Boolean? = null - val job = - underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID) - yield() // An invalid active subId should tell us that data is off assertThat(latest).isFalse() - - job.cancel() } @Test fun failedConnection_default_validated_notFailed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun failedConnection_notDefault_notValidated_notFailed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = false connectionsRepository.defaultConnectionIsValidated.value = false - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun failedConnection_default_notValidated_failed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = false - yield() assertThat(latest).isTrue() - - job.cancel() } @Test fun failedConnection_carrierMergedDefault_notValidated_failed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.hasCarrierMergedConnection.value = true connectionsRepository.defaultConnectionIsValidated.value = false - yield() assertThat(latest).isTrue() - - job.cancel() } /** Regression test for b/275076959. */ @Test fun failedConnection_dataSwitchInSameGroup_notFailed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true + runCurrent() // WHEN there's a data change in the same subscription group connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) connectionsRepository.defaultConnectionIsValidated.value = false + runCurrent() // THEN the default connection is *not* marked as failed because of forced validation assertThat(latest).isFalse() - - job.cancel() } @Test fun failedConnection_dataSwitchNotInSameGroup_isFailed() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) + connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true + runCurrent() // WHEN the connection is invalidated without a activeSubChangedInGroupEvent connectionsRepository.defaultConnectionIsValidated.value = false // THEN the connection is immediately marked as failed - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun alwaysShowDataRatIcon_configHasTrue() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.alwaysShowDataRatIcon) val config = MobileMappings.Config() config.alwaysShowDataRatIcon = true connectionsRepository.defaultDataSubRatConfig.value = config - yield() assertThat(latest).isTrue() - - job.cancel() } @Test fun alwaysShowDataRatIcon_configHasFalse() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.alwaysShowDataRatIcon) val config = MobileMappings.Config() config.alwaysShowDataRatIcon = false connectionsRepository.defaultDataSubRatConfig.value = config - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun alwaysUseCdmaLevel_configHasTrue() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.alwaysUseCdmaLevel) val config = MobileMappings.Config() config.alwaysShowCdmaRssi = true connectionsRepository.defaultDataSubRatConfig.value = config - yield() assertThat(latest).isTrue() - - job.cancel() } @Test fun alwaysUseCdmaLevel_configHasFalse() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.alwaysUseCdmaLevel) val config = MobileMappings.Config() config.alwaysShowCdmaRssi = false connectionsRepository.defaultDataSubRatConfig.value = config - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun isSingleCarrier_zeroSubscriptions_false() = testScope.runTest { - var latest: Boolean? = true - val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(emptyList()) - assertThat(latest).isFalse() - job.cancel() + assertThat(latest).isFalse() } @Test fun isSingleCarrier_oneSubscription_true() = testScope.runTest { - var latest: Boolean? = false - val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(listOf(SUB_1)) - assertThat(latest).isTrue() - job.cancel() + assertThat(latest).isTrue() } @Test fun isSingleCarrier_twoSubscriptions_false() = testScope.runTest { - var latest: Boolean? = true - val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) - assertThat(latest).isFalse() - job.cancel() + assertThat(latest).isFalse() } @Test fun isSingleCarrier_updates() = testScope.runTest { - var latest: Boolean? = false - val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(listOf(SUB_1)) assertThat(latest).isTrue() connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) assertThat(latest).isFalse() - - job.cancel() } @Test fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = false connectionsRepository.hasCarrierMergedConnection.value = false assertThat(latest).isFalse() - - job.cancel() } @Test fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = true connectionsRepository.hasCarrierMergedConnection.value = false assertThat(latest).isTrue() - - job.cancel() } /** Regression test for b/272586234. */ @Test fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = false connectionsRepository.hasCarrierMergedConnection.value = true assertThat(latest).isTrue() - - job.cancel() } @Test fun mobileIsDefault_updatesWhenRepoUpdates() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = true assertThat(latest).isTrue() @@ -762,8 +756,6 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.hasCarrierMergedConnection.value = true assertThat(latest).isTrue() - - job.cancel() } // The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow @@ -772,95 +764,79 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true + runCurrent() // Trigger a data change in the same subscription group that's not yet validated connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) connectionsRepository.defaultConnectionIsValidated.value = false + runCurrent() // After 1s, the force validation bit is still present, so the connection is not marked // as failed advanceTimeBy(1000) - assertThat(latestConnectionFailed).isFalse() + assertThat(latest).isFalse() // After 2s, the force validation expires so the connection updates to failed advanceTimeBy(1001) - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = false + runCurrent() connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) // GIVEN the network starts validated connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true + runCurrent() // WHEN a data change happens in the same group connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) // WHEN the validation bit is lost connectionsRepository.defaultConnectionIsValidated.value = false + runCurrent() // WHEN another data change happens in the same group connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) // THEN the forced validation bit is still used... - assertThat(latestConnectionFailed).isFalse() + assertThat(latest).isFalse() advanceTimeBy(1000) - assertThat(latestConnectionFailed).isFalse() + assertThat(latest).isFalse() // ... but expires after 2s advanceTimeBy(1001) - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun dataSwitch_whileAlreadyForcingValidation_resetsClock() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true + runCurrent() connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) @@ -869,44 +845,37 @@ class MobileIconsInteractorTest : SysuiTestCase() { // WHEN another change in same group event happens connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) connectionsRepository.defaultConnectionIsValidated.value = false + runCurrent() // THEN the forced validation remains for exactly 2 more seconds from now // 1.500s from second event advanceTimeBy(1500) - assertThat(latestConnectionFailed).isFalse() + assertThat(latest).isFalse() // 2.001s from the second event advanceTimeBy(501) - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun isForceHidden_repoHasMobileHidden_true() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isForceHidden) connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) assertThat(latest).isTrue() - - job.cancel() } @Test fun isForceHidden_repoDoesNotHaveMobileHidden_false() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isForceHidden) connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) assertThat(latest).isFalse() - - job.cancel() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index cfa734a14811..ab10bc4a4acc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -47,7 +47,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @OptIn(ExperimentalCoroutinesApi::class) @RunWith(ParameterizedAndroidJunit4::class) -class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } @@ -66,7 +66,7 @@ class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestC } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/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/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/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..df781108cf79 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; @@ -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(), new Rect()); + 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); + 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(), new Rect()); + 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); + 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(), new Rect()); + // 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(), new Rect()); + // 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..dc069fc149ab 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,21 @@ 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.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/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt index 66c9afba0cd6..b23767e9a6e1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt @@ -17,5 +17,13 @@ package com.android.systemui.animation import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase -val Kosmos.activityTransitionAnimator by Kosmos.Fixture { ActivityTransitionAnimator() } +val Kosmos.activityTransitionAnimator by + Kosmos.Fixture { + ActivityTransitionAnimator( + // The main thread is checked in a bunch of places inside the different transitions + // animators, so we have to pass the real main executor here. + mainExecutor = testCase.context.mainExecutor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt index 77cb1670bc42..5a092f38f929 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt @@ -19,7 +19,13 @@ package com.android.systemui.animation import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testCase val Kosmos.dialogTransitionAnimator by Fixture { - fakeDialogTransitionAnimator(interactionJankMonitor = interactionJankMonitor) + fakeDialogTransitionAnimator( + // The main thread is checked in a bunch of places inside the different transitions + // animators, so we have to pass the real main executor here. + mainExecutor = testCase.context.mainExecutor, + interactionJankMonitor = interactionJankMonitor, + ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt index 48b72d0a4cf1..17093291e8b0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt @@ -15,17 +15,20 @@ package com.android.systemui.animation import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.jank.interactionJankMonitor +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor /** A [DialogTransitionAnimator] to be used in tests. */ @JvmOverloads fun fakeDialogTransitionAnimator( + @Main mainExecutor: Executor, isUnlocked: Boolean = true, isShowingAlternateAuthOnUnlock: Boolean = false, isPredictiveBackQsDialogAnim: Boolean = false, interactionJankMonitor: InteractionJankMonitor, ): DialogTransitionAnimator { return DialogTransitionAnimator( + mainExecutor = mainExecutor, callback = FakeCallback( isUnlocked = isUnlocked, @@ -36,7 +39,7 @@ fun fakeDialogTransitionAnimator( object : AnimationFeatureFlags { override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim }, - transitionAnimator = fakeTransitionAnimator(), + transitionAnimator = fakeTransitionAnimator(mainExecutor), isForTesting = true, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt index bc7ec3f3b6d0..d07875f24beb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt @@ -15,10 +15,12 @@ package com.android.systemui.animation import com.android.app.animation.Interpolators +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor /** A [TransitionAnimator] to be used in tests. */ -fun fakeTransitionAnimator(): TransitionAnimator { - return TransitionAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) +fun fakeTransitionAnimator(@Main mainExecutor: Executor): TransitionAnimator { + return TransitionAnimator(mainExecutor, TEST_TIMINGS, TEST_INTERPOLATORS) } /** diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt index 0975687b4744..e37bdc15c0ac 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt @@ -1,8 +1,6 @@ package com.android.systemui.biometrics.data.repository -import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.shared.model.PromptKind import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -22,15 +20,12 @@ class FakePromptRepository : PromptRepository { private var _challenge = MutableStateFlow<Long?>(null) override val challenge = _challenge.asStateFlow() - private val _kind = MutableStateFlow<PromptKind>(PromptKind.Biometric()) - override val kind = _kind.asStateFlow() + private val _promptKind = MutableStateFlow<PromptKind>(PromptKind.None) + override val promptKind = _promptKind.asStateFlow() private val _isConfirmationRequired = MutableStateFlow(false) override val isConfirmationRequired = _isConfirmationRequired.asStateFlow() - private val _showBpWithoutIconForCredential = MutableStateFlow(false) - override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow() - private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) override val opPackageName = _opPackageName.asStateFlow() @@ -61,7 +56,7 @@ class FakePromptRepository : PromptRepository { _promptInfo.value = promptInfo _userId.value = userId _challenge.value = gatekeeperChallenge - _kind.value = kind + _promptKind.value = kind _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation _opPackageName.value = opPackageName } @@ -70,22 +65,11 @@ class FakePromptRepository : PromptRepository { _promptInfo.value = null _userId.value = null _challenge.value = null - _kind.value = PromptKind.Biometric() + _promptKind.value = PromptKind.None + _opPackageName.value = null _isConfirmationRequired.value = false } - override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { - val hasCredentialViewShown = kind.value !is PromptKind.Biometric - val showBpForCredential = - Flags.customBiometricPrompt() && - com.android.systemui.Flags.constraintBp() && - !Utils.isBiometricAllowed(promptInfo) && - Utils.isDeviceCredentialAllowed(promptInfo) && - promptInfo.contentView != null && - !promptInfo.isContentViewMoreOptionsButtonUsed - _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown - } - fun setIsShowing(showing: Boolean) { _isShowing.value = showing } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt index 2ae6f542ac3e..4999a5a29a7a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt @@ -17,6 +17,8 @@ package com.android.systemui.biometrics.ui.viewmodel import android.content.applicationContext +import com.android.app.activityTaskManager +import com.android.launcher3.icons.IconProvider import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor import com.android.systemui.biometrics.domain.interactor.displayStateInteractor import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor @@ -32,6 +34,8 @@ val Kosmos.promptViewModel by Fixture { context = applicationContext, udfpsOverlayInteractor = udfpsOverlayInteractor, biometricStatusInteractor = biometricStatusInteractor, - udfpsUtils = udfpsUtils + udfpsUtils = udfpsUtils, + iconProvider = IconProvider(applicationContext), + activityTaskManager = activityTaskManager, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt index 3a61bf625804..9b3482b8ce42 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt @@ -18,8 +18,12 @@ package com.android.systemui.common.data.repository import android.os.UserHandle import com.android.systemui.common.shared.model.PackageChangeModel +import com.android.systemui.common.shared.model.PackageInstallSession import com.android.systemui.util.time.SystemClock +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filter class FakePackageChangeRepository(private val systemClock: SystemClock) : PackageChangeRepository { @@ -31,6 +35,15 @@ class FakePackageChangeRepository(private val systemClock: SystemClock) : Packag user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid) } + private val _packageInstallSessions = MutableStateFlow<List<PackageInstallSession>>(emptyList()) + + override val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> = + _packageInstallSessions.asStateFlow() + + fun setInstallSessions(sessions: List<PackageInstallSession>) { + _packageInstallSessions.value = sessions + } + suspend fun notifyChange(model: PackageChangeModel) { _packageChanged.emit(model) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index 329c0f1ab5b4..f7ce367ebb18 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -51,6 +51,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) : override fun abortRestoreWidgets() {} private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) { - _communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority)) + _communalWidgets.value += + listOf(CommunalWidgetContentModel.Available(id, providerInfo, priority)) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 38f2a56d317f..3401cc4be231 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -27,6 +27,9 @@ 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) } @@ -42,7 +45,9 @@ val Kosmos.shortcutHelperTestHelper by } val Kosmos.shortcutHelperInteractor by - Kosmos.Fixture { ShortcutHelperInteractor(shortcutHelperRepository) } + Kosmos.Fixture { + ShortcutHelperInteractor(displayTracker, testScope, sysUiState, shortcutHelperRepository) + } val Kosmos.shortcutHelperViewModel by Kosmos.Fixture { ShortcutHelperViewModel(testDispatcher, shortcutHelperInteractor) } 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/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..b38acc8a46dc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor val Kosmos.windowManagerLockscreenVisibilityInteractor by @@ -29,5 +30,6 @@ val Kosmos.windowManagerLockscreenVisibilityInteractor by fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor, fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor, + sceneInteractor = sceneInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt new file mode 100644 index 000000000000..3c1f7b1b2394 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor.scenetransition + +import com.android.systemui.keyguard.data.repository.lockscreenSceneTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneInteractor + +var Kosmos.lockscreenSceneTransitionInteractor by + Kosmos.Fixture { + LockscreenSceneTransitionInteractor( + transitionInteractor = keyguardTransitionInteractor, + applicationScope = applicationCoroutineScope, + sceneInteractor = sceneInteractor, + repository = lockscreenSceneTransitionRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/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/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt index 3762497656c6..ec56327c1101 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt @@ -20,6 +20,7 @@ import com.android.internal.logging.uiEventLogger import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -33,6 +34,7 @@ var Kosmos.statusBarStateController: SysuiStatusBarStateController by uiEventLogger, { interactionJankMonitor }, mock(), + { keyguardTransitionInteractor }, { shadeInteractor }, { deviceUnlockedInteractor }, { sceneInteractor }, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt index 3f9112238822..d72630dc2f03 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt @@ -32,6 +32,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher import com.android.systemui.plugins.activityStarter import com.android.systemui.plugins.qs.QSFactory +import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl import com.android.systemui.qs.footer.foregroundServicesRepository import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel @@ -48,7 +49,7 @@ val Kosmos.qsEventLogger: QsEventLoggerFake by Fixture { QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake) } -var Kosmos.qsTileFactory by Fixture<QSFactory>() +var Kosmos.qsTileFactory by Fixture<QSFactory> { FakeQSFactory(::tileCreator) } val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() } @@ -98,3 +99,7 @@ val Kosmos.footerActionsViewModelFactory by Fixture { showPowerButton = true, ) } + +private fun tileCreator(spec: String): QSTile { + return FakeQSTile(0).apply { tileSpec = spec } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt index cff59807e00f..744942c0053b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt @@ -16,11 +16,11 @@ package com.android.systemui.qs.external -import android.os.Binder import android.os.IBinder +import android.os.IInterface import android.service.quicksettings.IQSTileService -class FakeIQSTileService : IQSTileService { +class FakeIQSTileService : IQSTileService.Stub() { var isTileAdded: Boolean = false private set @@ -31,9 +31,11 @@ class FakeIQSTileService : IQSTileService { get() = mutableClicks private val mutableClicks: MutableList<IBinder?> = mutableListOf() - private val binder = Binder() + override fun queryLocalInterface(descriptor: String): IInterface { + return this + } - override fun asBinder(): IBinder = binder + override fun asBinder(): IBinder = this override fun onTileAdded() { isTileAdded = true diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt new file mode 100644 index 000000000000..a0fc76b3d7de --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import android.app.activityManager +import android.content.applicationContext +import android.os.fakeExecutorHandler +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade +import com.android.systemui.util.mockito.mock + +val Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by + Kosmos.Fixture { + TileLifecycleManager.Factory { intent, userHandle -> + TileLifecycleManager( + fakeExecutorHandler, + applicationContext, + tileServices, + packageManagerAdapterFacade.packageManagerAdapter, + broadcastDispatcher, + intent, + userHandle, + activityManager, + mock(), + fakeExecutor, + ) + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt new file mode 100644 index 000000000000..3f129dac2600 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import android.content.applicationContext +import android.os.fakeExecutorHandler +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository +import com.android.systemui.qs.pipeline.domain.interactor.panelInteractor +import com.android.systemui.settings.userTracker +import com.android.systemui.statusbar.commandQueue +import com.android.systemui.statusbar.phone.ui.StatusBarIconController +import com.android.systemui.statusbar.policy.keyguardStateController +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever + +val Kosmos.tileServices: TileServices by + Kosmos.Fixture { + val qsHost: QSHost = mock { whenever(context).thenReturn(applicationContext) } + TileServices( + qsHost, + { fakeExecutorHandler }, + broadcastDispatcher, + userTracker, + keyguardStateController, + commandQueue, + mock<StatusBarIconController>(), + panelInteractor, + tileLifecycleManagerFactory, + customTileAddedRepository, + fakeExecutor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt index 36c2c2b6eb23..9a6730e07666 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt @@ -18,13 +18,9 @@ package com.android.systemui.qs.external import android.content.ComponentName import com.android.systemui.kosmos.Kosmos -import com.android.systemui.util.mockito.mock var Kosmos.componentName: ComponentName by Kosmos.Fixture() -/** Returns mocks */ -var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by Kosmos.Fixture { mock {} } - val Kosmos.iQSTileService: FakeIQSTileService by Kosmos.Fixture { FakeIQSTileService() } val Kosmos.tileServiceManagerFacade: FakeTileServiceManagerFacade by Kosmos.Fixture { FakeTileServiceManagerFacade(iQSTileService) } @@ -34,4 +30,3 @@ val Kosmos.tileServiceManager: TileServiceManager by val Kosmos.tileServicesFacade: FakeTileServicesFacade by Kosmos.Fixture { (FakeTileServicesFacade(tileServiceManager)) } -val Kosmos.tileServices: TileServices by Kosmos.Fixture { tileServicesFacade.tileServices } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt index f846d5712497..4acedaa9044d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt @@ -18,4 +18,5 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.kosmos.Kosmos -val Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() } +var Kosmos.gridLayoutTypeRepository: GridLayoutTypeRepository by + Kosmos.Fixture { GridLayoutTypeRepositoryImpl() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt index 685e77223fd5..e40152aa588f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt @@ -18,4 +18,4 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.kosmos.Kosmos -val Kosmos.iconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() } +var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() } diff --git a/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/domain/interactor/GridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt new file mode 100644 index 000000000000..edbc4c1cd65e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor + +val Kosmos.gridConsistencyInteractor by + Kosmos.Fixture { + GridConsistencyInteractor( + gridLayoutTypeInteractor, + currentTilesInteractor, + gridConsistencyInteractorsMap, + noopGridConsistencyInteractor, + FakeLogBuffer.Factory.create(), + applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt index c9516429553b..34e99d3a9a3c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt @@ -27,3 +27,6 @@ val Kosmos.gridLayoutTypeInteractor by val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) } + +var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by + Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt new file mode 100644 index 000000000000..7f387d7c706d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.infiniteGridConsistencyInteractor by + Kosmos.Fixture { + InfiniteGridConsistencyInteractor(iconTilesInteractor, infiniteGridSizeInteractor) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt index 1893c302c332..34b266a54f41 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt @@ -19,4 +19,5 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout -val Kosmos.infiniteGridLayout by Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor) } +val Kosmos.infiniteGridLayout by + Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt new file mode 100644 index 000000000000..6e1197737630 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.panels.data.repository.infiniteGridSizeRepository + +val Kosmos.infiniteGridSizeInteractor by + Kosmos.Fixture { InfiniteGridSizeInteractor(infiniteGridSizeRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt new file mode 100644 index 000000000000..e3beff727ae3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.noopGridConsistencyInteractor by Kosmos.Fixture { NoopGridConsistencyInteractor() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt index 5fd876222852..9481fcac97d6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt @@ -20,8 +20,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor -import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor -import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor val Kosmos.tileGridViewModel by @@ -30,7 +29,7 @@ val Kosmos.tileGridViewModel by gridLayoutTypeInteractor, gridLayoutMap, currentTilesInteractor, - InfiniteGridLayout(iconTilesInteractor), + infiniteGridLayout, applicationCoroutineScope, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt new file mode 100644 index 000000000000..d10780b6b817 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.shade.shadeController + +val Kosmos.panelInteractor by Kosmos.Fixture { PanelInteractorImpl(shadeController) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt index 5c4b39081143..419e7810b604 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt @@ -16,23 +16,63 @@ package com.android.systemui.qs.tiles.di +import android.os.UserHandle import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.instanceIdSequenceFake +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import javax.inject.Provider -import org.mockito.Mockito +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() } +val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by + Kosmos.Fixture { + mock { + whenever(create(any())).thenAnswer { invocation -> + val tileSpec = invocation.getArgument<TileSpec>(0) + val config = + QSTileConfig( + tileSpec, + QSTileUIConfig.Empty, + instanceIdSequenceFake.newInstanceId(), + ) + object : QSTileViewModel { + override val state: SharedFlow<QSTileState> = + MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {}) + override val config: QSTileConfig = config + override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true) + + override fun onUserChanged(user: UserHandle) {} + + override fun forceUpdate() {} + + override fun onActionPerformed(userAction: QSTileUserAction) {} + + override fun destroy() {} + } + } + } + } + val Kosmos.newQSTileFactory by Kosmos.Fixture { NewQSTileFactory( qSTileConfigProvider, qsTileViewModelAdaperFactory, newFactoryTileMap, - mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)), - mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)), + customTileViewModelFactory, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt index 561e2540d465..42437d5a5b81 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.qs.external.FakeCustomTileStatePersister import com.android.systemui.qs.external.tileServices +import com.android.systemui.qs.external.tileServicesFacade import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.logging.QSTileLogger @@ -38,10 +39,10 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder import com.android.systemui.user.data.repository.userRepository import com.android.systemui.util.mockito.mock -var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture() +var Kosmos.customTileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture() var Kosmos.customTileQsTileConfig: QSTileConfig by - Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.tileSpec } } + Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.customTileSpec } } val Kosmos.qsTileLogger: QSTileLogger by Kosmos.Fixture { mock {} } val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by @@ -50,7 +51,7 @@ val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by val Kosmos.customTileInteractor: CustomTileInteractor by Kosmos.Fixture { CustomTileInteractor( - tileSpec, + customTileSpec, customTileDefaultsRepository, customTileRepository, testScope.backgroundScope, @@ -61,7 +62,7 @@ val Kosmos.customTileInteractor: CustomTileInteractor by val Kosmos.customTileRepository: FakeCustomTileRepository by Kosmos.Fixture { FakeCustomTileRepository( - tileSpec, + customTileSpec, customTileStatePersister, packageManagerAdapterFacade, testScope.testScheduler, @@ -75,18 +76,18 @@ val Kosmos.customTilePackagesUpdatesRepository: FakeCustomTilePackageUpdatesRepo Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() } val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by - Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) } + Kosmos.Fixture { FakePackageManagerAdapterFacade(customTileSpec.componentName) } val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by Kosmos.Fixture { CustomTileServiceInteractor( - tileSpec, + customTileSpec, activityStarter, { customTileUserActionInteractor }, customTileInteractor, userRepository, qsTileLogger, - tileServices, + tileServicesFacade.tileServices, testScope.backgroundScope, ) } @@ -95,7 +96,7 @@ val Kosmos.customTileUserActionInteractor: CustomTileUserActionInteractor by Kosmos.Fixture { CustomTileUserActionInteractor( testCase.context, - tileSpec, + customTileSpec, qsTileLogger, mock {}, mock {}, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt index 634d121a556c..fa8d36366415 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.custom.data.repository import android.content.ComponentName +import android.content.pm.PackageInfo import android.content.pm.ServiceInfo import android.os.Bundle import com.android.systemui.qs.external.PackageManagerAdapter @@ -24,6 +25,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import org.mockito.ArgumentMatchers.anyInt /** * Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class @@ -45,19 +47,33 @@ class FakePackageManagerAdapterFacade( init { whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer { - ServiceInfo().apply { - metaData = - Bundle().apply { - putBoolean( - android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE, - isToggleable - ) - putBoolean( - android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE, - isActive - ) - } - } + createServiceInfo() + } + whenever( + packageManagerAdapter.getPackageInfoAsUser( + eq(componentName.packageName), + anyInt(), + anyInt() + ) + ) + .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } } + whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt())) + .thenAnswer { createServiceInfo() } + } + + private fun createServiceInfo(): ServiceInfo { + return ServiceInfo().apply { + metaData = + Bundle().apply { + putBoolean( + android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE, + isToggleable + ) + putBoolean( + android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE, + isActive + ) + } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt new file mode 100644 index 000000000000..5c21ab6e7fa8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night + +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger + +val Kosmos.qsNightDisplayTileConfig by + Kosmos.Fixture { QSAccessibilityModule.provideNightDisplayTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/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/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt index 38ede44efef6..ea02d0c7ac9a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt @@ -76,6 +76,16 @@ class ShadeTestUtil constructor(val delegate: ShadeTestUtilDelegate) { delegate.assertFlagValid() delegate.programmaticCollapseShade() } + + fun setQsFullscreen(qsFullscreen: Boolean) { + delegate.assertFlagValid() + delegate.setQsFullscreen(qsFullscreen) + } + + fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean) { + delegate.assertFlagValid() + delegate.setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer) + } } /** Sets up shade state for tests for a specific value of the scene container flag. */ @@ -103,6 +113,10 @@ interface ShadeTestUtilDelegate { /** Sets the shade to half collapsed with no touch input. */ fun programmaticCollapseShade() + + fun setQsFullscreen(qsFullscreen: Boolean) + + fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean) } /** Sets up shade state for tests when the scene container flag is disabled. */ @@ -146,6 +160,14 @@ class ShadeTestUtilLegacyImpl(val testScope: TestScope, val shadeRepository: Fak shadeRepository.setLegacyShadeExpansion(.5f) testScope.runCurrent() } + + override fun setQsFullscreen(qsFullscreen: Boolean) { + shadeRepository.legacyQsFullscreen.value = true + } + + override fun setLegacyExpandedOrAwaitingInputTransfer(expanded: Boolean) { + shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expanded) + } } /** Sets up shade state for tests when the scene container flag is enabled. */ @@ -183,6 +205,16 @@ class ShadeTestUtilSceneImpl(val testScope: TestScope, val sceneInteractor: Scen setTransitionProgress(Scenes.Shade, Scenes.Lockscreen, .5f, false) } + override fun setQsFullscreen(qsFullscreen: Boolean) { + setQsExpansion(1f) + } + + override fun setLegacyExpandedOrAwaitingInputTransfer( + legacyExpandedOrAwaitingInputTransfer: Boolean + ) { + setShadeExpansion(.1f) + } + override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) { if (lockscreenShadeExpansion == 0f) { setIdleScene(Scenes.Lockscreen) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/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/phone/SystemUIDialogFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt new file mode 100644 index 000000000000..3bb9580e69fa --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.content.applicationContext +import com.android.systemui.animation.dialogTransitionAnimator +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.model.sysUiState + +val Kosmos.systemUIDialogFactory by + Kosmos.Fixture { + SystemUIDialogFactory( + applicationContext, + systemUIDialogManager, + sysUiState, + broadcastDispatcher, + dialogTransitionAnimator, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt index 184d4b53f2f8..92f9248b2935 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt @@ -17,6 +17,8 @@ package com.android.systemui.util.concurrency import android.os.Handler +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyLong @@ -29,9 +31,14 @@ import org.mockito.stubbing.Answer * Wrap an [Executor] in a mock [Handler] that execute when [Handler.post] is called, and throws an * exception otherwise. This is useful when a class requires a Handler only because Handlers are * used by ContentObserver, and no other methods are used. + * + * If the [executor] is a [DelayableExecutor], it also supports: + * * [Handler.postDelayed] with a Runnable parameter + * * [Handler.postAtTime] with a RunnableParameter */ fun mockExecutorHandler(executor: Executor): Handler { val handlerMock = Mockito.mock(Handler::class.java, RuntimeExceptionAnswer()) + val cancellations = ConcurrentHashMap<Runnable, MutableList<Cancellation>>() doAnswer { invocation: InvocationOnMock -> executor.execute(invocation.getArgument(0)) true @@ -42,7 +49,19 @@ fun mockExecutorHandler(executor: Executor): Handler { doAnswer { invocation: InvocationOnMock -> val runnable = invocation.getArgument<Runnable>(0) val uptimeMillis = invocation.getArgument<Long>(1) - executor.executeAtTime(runnable, uptimeMillis) + val token = Any() + val canceller = + executor.executeAtTime( + { + cancellations.get(runnable)?.removeIf { it.token == token } + cancellations.remove(runnable, emptyList()) + runnable.run() + }, + uptimeMillis + ) + cancellations + .getOrPut(runnable) { CopyOnWriteArrayList() } + .add(Cancellation(token, canceller)) true } .`when`(handlerMock) @@ -50,15 +69,36 @@ fun mockExecutorHandler(executor: Executor): Handler { doAnswer { invocation: InvocationOnMock -> val runnable = invocation.getArgument<Runnable>(0) val delayInMillis = invocation.getArgument<Long>(1) - executor.executeDelayed(runnable, delayInMillis) + val token = Any() + val canceller = + executor.executeDelayed( + { + cancellations.get(runnable)?.removeIf { it.token == token } + cancellations.remove(runnable, emptyList()) + runnable.run() + }, + delayInMillis + ) + cancellations + .getOrPut(runnable) { CopyOnWriteArrayList() } + .add(Cancellation(token, canceller)) true } .`when`(handlerMock) .postDelayed(any(), anyLong()) + doAnswer { invocation: InvocationOnMock -> + val runnable = invocation.getArgument<Runnable>(0) + cancellations.remove(runnable)?.forEach(Runnable::run) + Unit + } + .`when`(handlerMock) + .removeCallbacks(any()) } return handlerMock } +private class Cancellation(val token: Any, canceller: Runnable) : Runnable by canceller + private class RuntimeExceptionAnswer : Answer<Any> { override fun answer(invocation: InvocationOnMock): Any { throw RuntimeException(invocation.method.name + " is not stubbed") diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt index d74355894581..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 @@ -33,6 +30,7 @@ import com.android.systemui.volume.panel.component.mediaoutput.data.repository.F 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 by @@ -54,7 +52,7 @@ val Kosmos.mediaOutputInteractor by testScope.backgroundScope, testScope.testScheduler, mediaControllerRepository, - Handler(TestableLooper.get(testCase).looper), + 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/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/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/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/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 bc608c5ac0c0..95cbb6b2130a 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -221,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 cf58bd2f3717..43b61a46b747 100755 --- a/ravenwood/scripts/ravenwood-stats-collector.sh +++ b/ravenwood/scripts/ravenwood-stats-collector.sh @@ -18,8 +18,14 @@ 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/ @@ -76,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/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 index 371c3acab144..9d29a051d092 100644 --- a/ravenwood/texts/ravenwood-framework-policies.txt +++ b/ravenwood/texts/ravenwood-framework-policies.txt @@ -1,59 +1,59 @@ # Ravenwood "policy" file for framework-minus-apex. # Keep all AIDL interfaces -class :aidl stubclass +class :aidl keepclass # Keep all feature flag implementations -class :feature_flags stubclass +class :feature_flags keepclass # Keep all sysprops generated code implementations -class :sysprops stubclass +class :sysprops keepclass # 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 +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 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 +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 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 +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 stubclass -class android.net.UriCodec stubclass +class android.net.Uri keepclass +class android.net.UriCodec keepclass # Telephony -class android.telephony.PinResult stubclass +class android.telephony.PinResult keepclass # 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 +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/ravenwood-services-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt index d8d563e05435..5cdb4f74d7c0 100644 --- a/ravenwood/texts/ravenwood-services-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 82579d807eba..3f30b0a5b059 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -152,6 +152,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 +193,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/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index e830523cba60..71b16c3a18b6 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"; @@ -736,7 +737,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 +754,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 +763,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 +782,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 +1572,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; } } @@ -2955,6 +2986,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 +3023,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(); @@ -3564,6 +3601,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 +3620,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); } } @@ -4661,6 +4704,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } if (newInfo != null) { info = newInfo; + if (DEBUG) { + Objects.requireNonNull(info); + } updateGeneratedPreviewCategoriesLocked(); } } @@ -4682,12 +4728,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 +5766,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 763879e5379a..6fc05b72da9b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -1998,6 +1998,19 @@ public final class AutofillManagerService } @Override + public void setViewAutofilled(int sessionId, @NonNull AutofillId id, int userId) { + synchronized (mLock) { + final AutofillManagerServiceImpl service = + peekServiceForUserWithLocalBinderIdentityLocked(userId); + if (service != null) { + service.setViewAutofilled(sessionId, getCallingUid(), id); + } else if (sVerbose) { + Slog.v(TAG, "setAutofillFailure(): no service for " + userId); + } + } + } + + @Override public void finishSession(int sessionId, int userId, @AutofillCommitReason int commitReason) { synchronized (mLock) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 92acce24e64e..588266fba47a 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -466,6 +466,7 @@ final class AutofillManagerServiceImpl @GuardedBy("mLock") void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) { if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); return; } final Session session = mSessions.get(sessionId); @@ -477,8 +478,23 @@ final class AutofillManagerServiceImpl } @GuardedBy("mLock") + void setViewAutofilled(int sessionId, int uid, @NonNull AutofillId id) { + if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); + return; + } + final Session session = mSessions.get(sessionId); + if (session == null || uid != session.uid) { + Slog.v(TAG, "setViewAutofilled(): no session for " + sessionId + "(" + uid + ")"); + return; + } + session.setViewAutofilled(id); + } + + @GuardedBy("mLock") void finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason) { if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); return; } diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 9c84b123a435..f289115159b8 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -65,6 +65,7 @@ import android.content.pm.PackageManager; import android.provider.Settings; import android.service.autofill.Dataset; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Slog; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; @@ -548,9 +549,10 @@ public final class PresentationStatsEventLogger { /** * Set views_fillable_total_count as long as mEventInternal presents. */ - public void maybeSetViewFillableCounts(int totalFillableCount) { + public void maybeSetViewFillablesAndCount(List<AutofillId> autofillIds) { mEventInternal.ifPresent(event -> { - event.mViewFillableTotalCount = totalFillableCount; + event.mAutofillIdsAttemptedAutofill = new ArraySet<>(autofillIds); + event.mViewFillableTotalCount = event.mAutofillIdsAttemptedAutofill.size(); }); } @@ -564,6 +566,41 @@ public final class PresentationStatsEventLogger { }); } + /** Sets focused_autofill_id using view id */ + public void maybeSetFocusedId(AutofillId id) { + maybeSetFocusedId(id.getViewId()); + } + + /** Sets focused_autofill_id as long as mEventInternal is present */ + public void maybeSetFocusedId(int id) { + mEventInternal.ifPresent(event -> { + event.mFocusedId = id; + }); + } + /** + * Set views_filled_failure_count using failure count as long as mEventInternal + * presents. + */ + public void maybeAddSuccessId(AutofillId autofillId) { + mEventInternal.ifPresent(event -> { + ArraySet<AutofillId> autofillIds = event.mAutofillIdsAttemptedAutofill; + if (autofillIds == null) { + Slog.w(TAG, "Attempted autofill ids is null, but received autofillId:" + autofillId + + " successfully filled"); + event.mViewFilledButUnexpectedCount++; + } else if (autofillIds.contains(autofillId)) { + if (sVerbose) { + Slog.v(TAG, "Logging autofill for id:" + autofillId); + event.mViewFillSuccessCount++; + } + } else { + Slog.w(TAG, "Successfully filled autofillId:" + autofillId + + " not found in list of attempted autofill ids: " + autofillIds); + event.mViewFilledButUnexpectedCount++; + } + }); + } + public void logAndEndEvent() { if (!mEventInternal.isPresent()) { Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same " @@ -608,7 +645,10 @@ public final class PresentationStatsEventLogger { + " mIsCredentialRequest=" + event.mIsCredentialRequest + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential + " mViewFillableTotalCount=" + event.mViewFillableTotalCount - + " mViewFillFailureCount=" + event.mViewFillFailureCount); + + " mViewFillFailureCount=" + event.mViewFillFailureCount + + " mFocusedId=" + event.mFocusedId + + " mViewFillSuccessCount=" + event.mViewFillSuccessCount + + " mViewFilledButUnexpectedCount=" + event.mViewFilledButUnexpectedCount); } // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. @@ -651,7 +691,10 @@ public final class PresentationStatsEventLogger { event.mIsCredentialRequest, event.mWebviewRequestedCredential, event.mViewFillableTotalCount, - event.mViewFillFailureCount); + event.mViewFillFailureCount, + event.mFocusedId, + event.mViewFillSuccessCount, + event.mViewFilledButUnexpectedCount); mEventInternal = Optional.empty(); } @@ -689,7 +732,14 @@ public final class PresentationStatsEventLogger { boolean mWebviewRequestedCredential = false; int mViewFillableTotalCount = -1; int mViewFillFailureCount = -1; + int mFocusedId = -1; + + // Default value for success count is set to 0 explicitly. Setting it to -1 for + // uninitialized doesn't help much, as this would be non-zero only if callback is received. + int mViewFillSuccessCount = 0; + int mViewFilledButUnexpectedCount = 0; + ArraySet<AutofillId> mAutofillIdsAttemptedAutofill; PresentationStatsEventInternal() {} } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 8b13c4b762d5..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; } @@ -3717,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; } } @@ -3801,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) { @@ -4669,6 +4715,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFieldClassificationIdSnapshot); mPresentationStatsEventLogger.maybeSetAvailableCount( response.getDatasets(), mCurrentViewId); + mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId); } @GuardedBy("mLock") @@ -4713,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()) { @@ -5381,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") @@ -6589,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/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/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 d7c65c748d8f..70af49c88433 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -224,7 +224,7 @@ class InputController { token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer()); String phys = inputDeviceDescriptor.getPhys(); - InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys); + InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys); // Type associations are added in the case of navigation touchpads. Those should be removed // once the input device gets closed. if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) { @@ -287,7 +287,7 @@ class InputController { private void setUniqueIdAssociation(int displayId, String phys) { final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId; - InputManagerGlobal.getInstance().addUniqueIdAssociation(phys, displayUniqueId); + InputManagerGlobal.getInstance().addUniqueIdAssociationByPort(phys, displayUniqueId); } boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { @@ -789,7 +789,7 @@ class InputController { throw e; } } catch (DeviceCreationException e) { - InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys); + InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys); throw e; } diff --git a/services/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/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..d9e6186639ce 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -121,7 +121,6 @@ public final class PinnerService extends SystemService { SystemProperties.getBoolean("pinner.use_pinlist", true); private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app. - private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app. private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app. public static final String ANON_REGION_STAT_NAME = "[anon]"; @@ -176,7 +175,7 @@ public final class PinnerService extends SystemService { // Resource-configured pinner flags; private final boolean mConfiguredToPinCamera; - private final boolean mConfiguredToPinHome; + private final int mConfiguredHomePinBytes; private final boolean mConfiguredToPinAssistant; private final int mConfiguredWebviewPinBytes; @@ -238,8 +237,8 @@ public final class PinnerService extends SystemService { mDeviceConfigInterface = mInjector.getDeviceConfigInterface(); mConfiguredToPinCamera = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerCameraApp); - mConfiguredToPinHome = context.getResources().getBoolean( - com.android.internal.R.bool.config_pinnerHomeApp); + mConfiguredHomePinBytes = context.getResources().getInteger( + com.android.internal.R.integer.config_pinnerHomePinBytes); mConfiguredToPinAssistant = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerAssistantApp); mConfiguredWebviewPinBytes = context.getResources().getInteger( @@ -390,7 +389,7 @@ public final class PinnerService extends SystemService { @Override public void onChange(boolean selfChange, Uri uri) { if (userSetupCompleteUri.equals(uri)) { - if (mConfiguredToPinHome) { + if (mConfiguredHomePinBytes > 0) { sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(), true /* force */); } @@ -594,7 +593,7 @@ public final class PinnerService extends SystemService { Slog.i(TAG, "Pinner - skip pinning camera app"); } - if (mConfiguredToPinHome) { + if (mConfiguredHomePinBytes > 0) { pinKeys.add(KEY_HOME); } if (mConfiguredToPinAssistant) { @@ -815,7 +814,7 @@ public final class PinnerService extends SystemService { case KEY_CAMERA: return MAX_CAMERA_PIN_SIZE; case KEY_HOME: - return MAX_HOME_PIN_SIZE; + return mConfiguredHomePinBytes; case KEY_ASSISTANT: return MAX_ASSISTANT_PIN_SIZE; default: @@ -871,6 +870,7 @@ public final class PinnerService extends SystemService { } synchronized (this) { pinnedApp.mFiles.add(pf); + mPinnedFiles.put(pf.fileName, pf); } apkPinSizeLimit -= pf.bytesPinned; @@ -1342,18 +1342,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/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 5933639f2317..04e85c7f117a 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -187,6 +187,20 @@ }, { "name": "SelinuxFrameworksTests" + }, + { + "name": "CtsWindowManagerBackgroundActivityTestCases", + "file_patterns": [ + "Background.*\\.java", + "Activity.*\\.java" + ] + }, + { + "name": "WmTests", + "file_patterns": [ + "Background.*\\.java", + "Activity.*\\.java" + ] } ] } 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/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 4ca9e3380a92..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; @@ -784,7 +785,7 @@ public final class ActiveServices { ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, "SERVICE_FOREGROUND_TIMEOUT"); this.mFGSAnrTimer = new ServiceAnrTimer(service, - ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG, + ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, "FGS_TIMEOUT"); } @@ -1168,9 +1169,7 @@ public final class ActiveServices { } private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) { - @PowerExemptionManager.ReasonCode final int fgsStartReasonCode = - r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode - : REASON_DENIED; + @PowerExemptionManager.ReasonCode final int fgsStartReasonCode = r.getFgsAllowStart(); if (Flags.fgsBootCompleted() && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid) && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) { @@ -2454,10 +2453,21 @@ public final class ActiveServices { } else if (lastTimeOutAt > 0) { // Time limit was exhausted within the past 24 hours and the app // has not been in the TOP state since then, throw an exception. - throw new ForegroundServiceStartNotAllowedException("Time limit" - + " already exhausted for foreground service type " + final String exceptionMsg = "Time limit already exhausted for" + + " foreground service type " + ServiceInfo.foregroundServiceTypeToLabel( - foregroundServiceType)); + foregroundServiceType); + // Only throw an exception if the new ANR behavior + // ("do nothing") is not gated or the new crashing logic gate + // is enabled; otherwise, reset the limit temporarily. + if (!android.app.Flags.gateFgsTimeoutAnrBehavior() + || android.app.Flags.enableFgsTimeoutCrashBehavior()) { + throw new ForegroundServiceStartNotAllowedException( + exceptionMsg); + } else { + Slog.wtf(TAG, exceptionMsg); + fgsTypeInfo.reset(); + } } } } else { @@ -3773,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 @@ -3783,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()) { @@ -3815,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); @@ -3842,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); @@ -3903,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 { @@ -3929,34 +3939,65 @@ public final class ActiveServices { Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e); } - // ANR the service after giving the service some time to clean up. - mFGSAnrTimer.start(sr, mAm.mConstants.mFgsAnrExtraWaitDuration); + // Crash the service after giving the service some time to clean up. + mFGSAnrTimer.start(sr, mAm.mConstants.mFgsCrashExtraWaitDuration); } } - void onFgsAnrTimeout(ServiceRecord sr) { + void onFgsCrashTimeout(ServiceRecord sr) { final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType); if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) { return; // no timed out FGS type was found (either it was stopped or it switched types) } + + 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(); - - Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr); - traceInstant("FGS ANR: ", sr); - if (sr.app != null) { - mAm.appNotResponding(sr.app, tr); + if (android.app.Flags.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(); - // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR - // dialog really doesn't remember the "cause" (especially if there have been multiple - // ANRs), so it's not doable. + Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr); + traceInstant("FGS ANR: ", sr); + if (sr.app != null) { + mAm.appNotResponding(sr.app, tr); + } + + // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR + // dialog really doesn't remember the "cause" (especially if there have been + // multiple ANRs), so it's not doable. + } } } @@ -3973,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(); @@ -6259,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. @@ -6384,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 00c2df67c8f0..00d8efa764ce 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -598,6 +598,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 +1673,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 +2044,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; } } @@ -9939,126 +9942,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(); @@ -10321,6 +10268,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. * @@ -12326,8 +12334,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 @@ -12335,7 +12343,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" @@ -12343,7 +12351,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" diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 3cea01464c10..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]"); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 4f841497b201..58732fd200d2 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -159,6 +159,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 +411,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, @@ -515,6 +505,26 @@ public final class BatteryStatsService extends IBatteryStats.Stub 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. */ diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java index f2b9b25d6097..31704c442290 100644 --- a/services/core/java/com/android/server/am/ConnectionRecord.java +++ b/services/core/java/com/android/server/am/ConnectionRecord.java @@ -136,6 +136,13 @@ final class ConnectionRecord implements OomAdjusterModernImpl.Connection{ false, oomAdjReason, UNKNOWN_ADJ, false, false); } + @Override + public boolean canAffectCapabilities() { + return hasFlag(Context.BIND_INCLUDE_CAPABILITIES + | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS); + } + + public long getFlags() { return flags; } diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java index 3988277ab525..ae5ae0133e1b 100644 --- a/services/core/java/com/android/server/am/ContentProviderConnection.java +++ b/services/core/java/com/android/server/am/ContentProviderConnection.java @@ -82,6 +82,12 @@ public final class ContentProviderConnection extends Binder implements false, oomAdjReason, UNKNOWN_ADJ, false, false); } + @Override + public boolean canAffectCapabilities() { + return false; + } + + public void startAssociationIfNeeded() { // If we don't already have an active association, create one... but only if this // is an association between two different processes. diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index cc6ae5ca03e3..8647750d510f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -3277,7 +3277,13 @@ public class OomAdjuster { } final int curSchedGroup = state.getCurrentSchedulingGroup(); - if (state.getSetSchedGroup() != curSchedGroup) { + if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0 + && ActivityManager.isProcStateBackground(state.getCurProcState()) + && !state.hasStartedServices()) { + app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED, + ApplicationExitInfo.SUBREASON_REMOVE_TASK, true); + success = false; + } else if (state.getSetSchedGroup() != curSchedGroup) { int oldSchedGroup = state.getSetSchedGroup(); state.setSetSchedGroup(curSchedGroup); if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) { @@ -3285,75 +3291,67 @@ public class OomAdjuster { + " to " + curSchedGroup + ": " + state.getAdjType(); reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); } - if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0 - && ActivityManager.isProcStateBackground(state.getSetProcState()) - && !state.hasStartedServices()) { - app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_REMOVE_TASK, true); - success = false; - } else { - int processGroup; - switch (curSchedGroup) { - case SCHED_GROUP_BACKGROUND: - processGroup = THREAD_GROUP_BACKGROUND; - break; - case SCHED_GROUP_TOP_APP: - case SCHED_GROUP_TOP_APP_BOUND: - processGroup = THREAD_GROUP_TOP_APP; - break; - case SCHED_GROUP_RESTRICTED: - processGroup = THREAD_GROUP_RESTRICTED; - break; - default: - processGroup = THREAD_GROUP_DEFAULT; - break; - } - mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage( - 0 /* unused */, app.getPid(), processGroup, app.processName)); - try { - final int renderThreadTid = app.getRenderThreadTid(); - if (curSchedGroup == SCHED_GROUP_TOP_APP) { - // do nothing if we already switched to RT - if (oldSchedGroup != SCHED_GROUP_TOP_APP) { - app.getWindowProcessController().onTopProcChanged(); - if (app.useFifoUiScheduling()) { - // Switch UI pipeline for app to SCHED_FIFO - state.setSavedPriority(Process.getThreadPriority(app.getPid())); - ActivityManagerService.setFifoPriority(app, true /* enable */); - } else { - // Boost priority for top app UI and render threads - setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); - if (renderThreadTid != 0) { - try { - setThreadPriority(renderThreadTid, - THREAD_PRIORITY_TOP_APP_BOOST); - } catch (IllegalArgumentException e) { - // thread died, ignore - } - } - } - } - } else if (oldSchedGroup == SCHED_GROUP_TOP_APP - && curSchedGroup != SCHED_GROUP_TOP_APP) { + int processGroup; + switch (curSchedGroup) { + case SCHED_GROUP_BACKGROUND: + processGroup = THREAD_GROUP_BACKGROUND; + break; + case SCHED_GROUP_TOP_APP: + case SCHED_GROUP_TOP_APP_BOUND: + processGroup = THREAD_GROUP_TOP_APP; + break; + case SCHED_GROUP_RESTRICTED: + processGroup = THREAD_GROUP_RESTRICTED; + break; + default: + processGroup = THREAD_GROUP_DEFAULT; + break; + } + mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage( + 0 /* unused */, app.getPid(), processGroup, app.processName)); + try { + final int renderThreadTid = app.getRenderThreadTid(); + if (curSchedGroup == SCHED_GROUP_TOP_APP) { + // do nothing if we already switched to RT + if (oldSchedGroup != SCHED_GROUP_TOP_APP) { app.getWindowProcessController().onTopProcChanged(); if (app.useFifoUiScheduling()) { - // Reset UI pipeline to SCHED_OTHER - ActivityManagerService.setFifoPriority(app, false /* enable */); - setThreadPriority(app.getPid(), state.getSavedPriority()); + // Switch UI pipeline for app to SCHED_FIFO + state.setSavedPriority(Process.getThreadPriority(app.getPid())); + ActivityManagerService.setFifoPriority(app, true /* enable */); } else { - // Reset priority for top app UI and render threads - setThreadPriority(app.getPid(), 0); - } - - if (renderThreadTid != 0) { - setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY); + // Boost priority for top app UI and render threads + setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); + if (renderThreadTid != 0) { + try { + setThreadPriority(renderThreadTid, + THREAD_PRIORITY_TOP_APP_BOOST); + } catch (IllegalArgumentException e) { + // thread died, ignore + } + } } } - } catch (Exception e) { - if (DEBUG_ALL) { - Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e); + } else if (oldSchedGroup == SCHED_GROUP_TOP_APP + && curSchedGroup != SCHED_GROUP_TOP_APP) { + app.getWindowProcessController().onTopProcChanged(); + if (app.useFifoUiScheduling()) { + // Reset UI pipeline to SCHED_OTHER + ActivityManagerService.setFifoPriority(app, false /* enable */); + setThreadPriority(app.getPid(), state.getSavedPriority()); + } else { + // Reset priority for top app UI and render threads + setThreadPriority(app.getPid(), 0); + } + + if (renderThreadTid != 0) { + setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY); } } + } catch (Exception e) { + if (DEBUG_ALL) { + Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e); + } } } if (state.hasRepForegroundActivities() != state.hasForegroundActivities()) { diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 3268b661df65..9600317faca1 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL; import static android.app.ActivityManager.PROCESS_STATE_BACKUP; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; @@ -521,6 +522,11 @@ public class OomAdjusterModernImpl extends OomAdjuster { */ void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host, ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, int oomAdjReason, int cachedAdj); + + /** + * Returns true if this connection can propagate capabilities. + */ + boolean canAffectCapabilities(); } /** @@ -553,16 +559,29 @@ public class OomAdjusterModernImpl extends OomAdjuster { */ private class ComputeConnectionIgnoringReachableClientsConsumer implements BiConsumer<Connection, ProcessRecord> { - public OomAdjusterArgs args = null; + private OomAdjusterArgs mArgs = null; + public boolean hasReachableClient = false; + + public void init(OomAdjusterArgs args) { + mArgs = args; + hasReachableClient = false; + } @Override public void accept(Connection conn, ProcessRecord client) { - final ProcessRecord host = args.mApp; - final ProcessRecord topApp = args.mTopApp; - final long now = args.mNow; - final @OomAdjReason int oomAdjReason = args.mOomAdjReason; + final ProcessRecord host = mArgs.mApp; + final ProcessRecord topApp = mArgs.mTopApp; + final long now = mArgs.mNow; + final @OomAdjReason int oomAdjReason = mArgs.mOomAdjReason; + + if (client.mState.isReachable()) { + hasReachableClient = true; + return; + } - if (client.mState.isReachable()) return; + if (unimportantConnectionLSP(conn, host, client)) { + return; + } conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, false, oomAdjReason, UNKNOWN_ADJ); @@ -591,6 +610,10 @@ public class OomAdjusterModernImpl extends OomAdjuster { final int prevProcState = host.mState.getCurProcState(); final int prevAdj = host.mState.getCurRawAdj(); + if (unimportantConnectionLSP(conn, host, client)) { + return; + } + conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, fullUpdate, oomAdjReason, cachedAdj); @@ -874,7 +897,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { // processes cannot change as a part of this update, their current values can be used // right now. mProcessRecordProcStateNodes.resetLastNodes(); - initReachableStatesLSP(reachables, mTmpOomAdjusterArgs); + initReachableStatesLSP(reachables, targets.size(), mTmpOomAdjusterArgs); // Set adj last nodes now, this way a process will only be reevaluated during the adj node // iteration if they adj score changed during the procState node iteration. @@ -914,8 +937,6 @@ public class OomAdjusterModernImpl extends OomAdjuster { /** * Mark all processes reachable from the {@code reachables} processes and add them to the * provided {@code reachables} list (targets excluded). - * - * Returns true if a cycle exists within the reachable process graph. */ @GuardedBy({"mService", "mProcLock"}) private void collectAndMarkReachableProcessesLSP(ArrayList<ProcessRecord> reachables) { @@ -930,8 +951,35 @@ public class OomAdjusterModernImpl extends OomAdjuster { * Calculate initial importance states for {@code reachables} and update their slot position * if necessary. */ - private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, OomAdjusterArgs args) { - for (int i = 0, size = reachables.size(); i < size; i++) { + private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, int targetCount, + OomAdjusterArgs args) { + int i = 0; + boolean initReachables = !Flags.skipUnimportantConnections(); + for (; i < targetCount && !initReachables; i++) { + final ProcessRecord target = reachables.get(i); + final int prevProcState = target.mState.getCurProcState(); + final int prevAdj = target.mState.getCurRawAdj(); + final int prevCapability = target.mState.getCurCapability(); + final boolean prevShouldNotFreeze = target.mOptRecord.shouldNotFreeze(); + + args.mApp = target; + // If target client is a reachable, reachables need to be reinited in case this + // client is important enough to change this target in the computeConnection step. + initReachables |= computeOomAdjIgnoringReachablesLSP(args); + // If target lowered in importance, reachables need to be reinited because this + // target may have been the source of a reachable's current importance. + initReachables |= selfImportanceLoweredLSP(target, prevProcState, prevAdj, + prevCapability, prevShouldNotFreeze); + + updateProcStateSlot(target, prevProcState); + updateAdjSlot(target, prevAdj); + } + + if (!initReachables) { + return; + } + + for (int size = reachables.size(); i < size; i++) { final ProcessRecord reachable = reachables.get(i); final int prevProcState = reachable.mState.getCurProcState(); final int prevAdj = reachable.mState.getCurRawAdj(); @@ -948,9 +996,11 @@ public class OomAdjusterModernImpl extends OomAdjuster { * Calculate initial importance states for {@code app}. * Processes not marked reachable cannot change as a part of this update, so connections from * those process can be calculated now. + * + * Returns true if any client connection was skipped due to a reachablity cycle. */ @GuardedBy({"mService", "mProcLock"}) - private void computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) { + private boolean computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) { final ProcessRecord app = args.mApp; final ProcessRecord topApp = args.mTopApp; final long now = args.mNow; @@ -958,8 +1008,9 @@ public class OomAdjusterModernImpl extends OomAdjuster { computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, false, now, false, false, oomAdjReason, false); - mComputeConnectionIgnoringReachableClientsConsumer.args = args; + mComputeConnectionIgnoringReachableClientsConsumer.init(args); forEachClientConnectionLSP(app, mComputeConnectionIgnoringReachableClientsConsumer); + return mComputeConnectionIgnoringReachableClientsConsumer.hasReachableClient; } /** @@ -1039,6 +1090,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { } else { client = cr.binding.client; } + if (client == null || client == app) continue; connectionConsumer.accept(cr, client); } } @@ -1053,4 +1105,66 @@ public class OomAdjusterModernImpl extends OomAdjuster { } } } + + /** + * Returns true if at least one the provided values is more important than those in {@code app}. + */ + @GuardedBy({"mService", "mProcLock"}) + private static boolean selfImportanceLoweredLSP(ProcessRecord app, int prevProcState, + int prevAdj, int prevCapability, boolean prevShouldNotFreeze) { + if (app.mState.getCurProcState() > prevProcState) { + return true; + } + if (app.mState.getCurRawAdj() > prevAdj) { + return true; + } + if ((app.mState.getCurCapability() & prevCapability) != prevCapability) { + return true; + } + if (!app.mOptRecord.shouldNotFreeze() && prevShouldNotFreeze) { + // No long marked as should not freeze. + return true; + } + return false; + } + + /** + * Returns whether a host connection evaluation can be skipped due to lack of importance. + * Note: the client and host need to be provided as well for the isolated and sandbox + * scenarios. + */ + @GuardedBy({"mService", "mProcLock"}) + private static boolean unimportantConnectionLSP(Connection conn, + ProcessRecord host, ProcessRecord client) { + if (!Flags.skipUnimportantConnections()) { + // Feature not enabled, just return false so the connection is evaluated. + return false; + } + if (host.mState.getCurProcState() > client.mState.getCurProcState()) { + return false; + } + if (host.mState.getCurRawAdj() > client.mState.getCurRawAdj()) { + return false; + } + final int serviceCapability = host.mState.getCurCapability(); + final int clientCapability = client.mState.getCurCapability(); + if ((serviceCapability & clientCapability) != clientCapability) { + // Client has a capability the host does not have. + if ((clientCapability & PROCESS_CAPABILITY_BFSL) == PROCESS_CAPABILITY_BFSL + && (serviceCapability & PROCESS_CAPABILITY_BFSL) == 0) { + // The BFSL capability does not need a flag to propagate. + return false; + } + if (conn.canAffectCapabilities()) { + // One of these bind flags may propagate that capability. + return false; + } + } + + if (!host.mOptRecord.shouldNotFreeze() && client.mOptRecord.shouldNotFreeze()) { + // If the client is marked as should not freeze, so should the host. + return false; + } + return true; + } } diff --git a/services/core/java/com/android/server/am/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 95206212c99d..7dcd95232374 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; @@ -185,6 +197,7 @@ public class SettingsToPropertiesMapper { "pmw", "power", "preload_safety", + "printing", "privacy_infra_policy", "resource_manager", "responsible_apis", @@ -224,6 +237,8 @@ public class SettingsToPropertiesMapper { public static final String NAMESPACE_REBOOT_STAGING = "staged"; public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*"; + public static final String NAMESPACE_LOCAL_OVERRIDES = "device_config_overrides"; + private final String[] mGlobalSettings; private final String[] mDeviceConfigScopes; @@ -329,6 +344,7 @@ public class SettingsToPropertiesMapper { HashMap<String, HashMap<String, String>> propsToStage = getStagedFlagsWithValueChange(properties); + // send prop stage request to sys prop for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) { String actualNamespace = entry.getKey(); HashMap<String, String> flagValuesToStage = entry.getValue(); @@ -349,7 +365,171 @@ public class SettingsToPropertiesMapper { } } - }); + // send prop stage request to new storage + if (enableAconfigStorageDaemon()) { + stageFlagsInNewStorage(propsToStage); + } + + }); + + // add prop sync callback for flag local overrides + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_LOCAL_OVERRIDES, + AsyncTask.THREAD_POOL_EXECUTOR, + (DeviceConfig.Properties properties) -> { + if (enableAconfigStorageDaemon()) { + setLocalOverridesInNewStorage(properties); + } + }); + } + + /** + * apply flag local override in aconfig new storage + * @param 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)); + log("connected to aconfigd socket"); + } catch (IOException ioe) { + log("failed to connect to aconfigd socket", ioe); + return null; + } + + DataInputStream inputStream = null; + DataOutputStream outputStream = null; + try { + inputStream = new DataInputStream(client.getInputStream()); + outputStream = new DataOutputStream(client.getOutputStream()); + } catch (IOException ioe) { + log("failed to get local socket iostreams", ioe); + return null; + } + + // send requests + try { + byte[] requests_bytes = requests.getBytes(); + outputStream.writeInt(requests_bytes.length); + outputStream.write(requests_bytes, 0, requests_bytes.length); + log("flag override requests sent to aconfigd"); + } catch (IOException ioe) { + log("failed to send requests to aconfigd", ioe); + return null; + } + + // read return + try { + int num_bytes = inputStream.readInt(); + ProtoInputStream returns = new ProtoInputStream(inputStream); + log("received " + num_bytes + " bytes back from aconfigd"); + return returns; + } catch (IOException ioe) { + log("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: + log("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); + log("override request failed: " + errmsg); + break; + case ProtoInputStream.NO_MORE_FIELDS: + break; + default: + log("invalid message type, expecting only flag override return or error message"); + break; + } + proto.end(msgsToken); + break; + case ProtoInputStream.NO_MORE_FIELDS: + return; + default: + log("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) { + log("invalid local flag override: " + flagName); + continue; + } + String actualNamespace = flagName.substring(0, idx); + String fullFlagName = flagName.substring(idx+1); + idx = fullFlagName.lastIndexOf("."); + if (idx == -1) { + log("invalid flag name: " + fullFlagName); + continue; + } + String packageName = fullFlagName.substring(0, idx); + String realFlagName = fullFlagName.substring(idx+1); + 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) { + log("failed to parse aconfigd return", ioe); + } } public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -420,6 +600,48 @@ public class SettingsToPropertiesMapper { return propertyName; } + + /** + * stage flags in aconfig new storage + * @param propsToStage + */ + @VisibleForTesting + static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) { + // write aconfigd requests proto to proto output stream + int num_requests = 0; + ProtoOutputStream requests = new ProtoOutputStream(); + for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) { + String actualNamespace = entry.getKey(); + HashMap<String, String> flagValuesToStage = entry.getValue(); + for (String fullFlagName : flagValuesToStage.keySet()) { + String stagedValue = flagValuesToStage.get(fullFlagName); + int idx = fullFlagName.lastIndexOf("."); + if (idx == -1) { + log("invalid flag name: " + fullFlagName); + continue; + } + String packageName = fullFlagName.substring(0, idx); + String flagName = fullFlagName.substring(idx+1); + writeFlagOverrideRequest(requests, packageName, flagName, stagedValue, 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) { + log("failed to parse aconfigd return", ioe); + } + } + /** * system property name constructing rule for aconfig flags: * "persist.device_config.aconfig_flags.[category_name].[flag_name]". @@ -483,10 +705,10 @@ public class SettingsToPropertiesMapper { for (String flagName : flagStagedValues.keySet()) { String stagedValue = flagStagedValues.get(flagName); String currentValue = flagCurrentValues.get(flagName); - if (currentValue == null) { - currentValue = new String("false"); + if (stagedValue == null) { + continue; } - if (stagedValue != null && !stagedValue.equalsIgnoreCase(currentValue)) { + if (currentValue == null || !stagedValue.equalsIgnoreCase(currentValue)) { flagsToStage.put(flagName, stagedValue); } } diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index fb63ec619918..b7108dfcbac3 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -116,3 +116,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "skip_unimportant_connections" + namespace: "backstage_power" + description: "Avoid OomAdjuster calculations for connections that won't change importance" + bug: "323376416" +} diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 63086525711a..ad93f6fc8d9f 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -55,6 +55,7 @@ import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS; import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE; +import static android.app.AppOpsManager.UID_STATE_NONEXISTENT; import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; @@ -70,7 +71,6 @@ import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; -import static android.permission.flags.Flags.runtimePermissionAppopsMappingEnabled; import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; @@ -130,6 +130,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.storage.StorageManagerInternal; import android.permission.PermissionManager; +import android.permission.flags.Flags; import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; @@ -140,6 +141,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.Xml; @@ -153,7 +155,6 @@ import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; -import com.android.internal.camera.flags.Flags; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.Clock; import com.android.internal.pm.pkg.component.ParsedAttribution; @@ -1255,7 +1256,9 @@ public class AppOpsService extends IAppOpsService.Stub { for (int uidIdx = mUidStates.size() - 1; uidIdx >= 0; uidIdx--) { int uid = mUidStates.keyAt(uidIdx); if (knownUids.get(uid, false)) { - if (uid >= Process.FIRST_APPLICATION_UID) { + int appId = UserHandle.getAppId(uid); + if (appId >= Process.FIRST_APPLICATION_UID + && appId <= Process.LAST_APPLICATION_UID) { ArrayMap<String, Ops> pkgOps = mUidStates.valueAt(uidIdx).pkgOps; for (int pkgIdx = pkgOps.size() - 1; pkgIdx >= 0; pkgIdx--) { String pkgName = pkgOps.keyAt(pkgIdx); @@ -1419,6 +1422,9 @@ public class AppOpsService extends IAppOpsService.Stub { // The callback method from AppOpsUidStateTracker private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) { synchronized (this) { + if (state == UID_STATE_NONEXISTENT) { + onUidProcessDeathLocked(uid); + } UidState uidState = getUidStateLocked(uid, false); boolean hasForegroundWatchers = false; @@ -1506,6 +1512,11 @@ public class AppOpsService extends IAppOpsService.Stub { } } + if (state == UID_STATE_NONEXISTENT) { + // For UID_STATE_NONEXISTENT, we don't call onUidStateChanged for AttributedOps + return; + } + if (uidState != null) { int numPkgs = uidState.pkgOps.size(); for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { @@ -1530,6 +1541,81 @@ public class AppOpsService extends IAppOpsService.Stub { } } + @GuardedBy("this") + private void onUidProcessDeathLocked(int uid) { + if (!mUidStates.contains(uid) || !Flags.finishRunningOpsForKilledPackages()) { + return; + } + final SparseLongArray chainsToFinish = new SparseLongArray(); + doForAllAttributedOpsInUidLocked(uid, (attributedOp) -> { + attributedOp.doForAllInProgressStartOpEvents((event) -> { + int chainId = event.getAttributionChainId(); + if (chainId != ATTRIBUTION_CHAIN_ID_NONE) { + long currentEarliestStartTime = + chainsToFinish.get(chainId, Long.MAX_VALUE); + if (event.getStartTime() < currentEarliestStartTime) { + // Store the earliest chain link we're finishing, so that we can go back + // and finish any links in the chain that started after this one + chainsToFinish.put(chainId, event.getStartTime()); + } + } + attributedOp.finished(event.getClientId()); + }); + }); + finishChainsLocked(chainsToFinish); + } + + @GuardedBy("this") + private void finishChainsLocked(SparseLongArray chainsToFinish) { + doForAllAttributedOpsLocked((attributedOp) -> { + attributedOp.doForAllInProgressStartOpEvents((event) -> { + int chainId = event.getAttributionChainId(); + // If this event is part of a chain, and this event started after the event in the + // chain we already finished, then finish this event, too + long earliestEventStart = chainsToFinish.get(chainId, Long.MAX_VALUE); + if (chainId != ATTRIBUTION_CHAIN_ID_NONE + && event.getStartTime() >= earliestEventStart) { + attributedOp.finished(event.getClientId()); + } + }); + }); + } + + @GuardedBy("this") + private void doForAllAttributedOpsLocked(Consumer<AttributedOp> action) { + int numUids = mUidStates.size(); + for (int uidNum = 0; uidNum < numUids; uidNum++) { + int uid = mUidStates.keyAt(uidNum); + doForAllAttributedOpsInUidLocked(uid, action); + } + } + + @GuardedBy("this") + private void doForAllAttributedOpsInUidLocked(int uid, Consumer<AttributedOp> action) { + UidState uidState = mUidStates.get(uid); + if (uidState == null) { + return; + } + + int numPkgs = uidState.pkgOps.size(); + for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { + Ops ops = uidState.pkgOps.valueAt(pkgNum); + int numOps = ops.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + Op op = ops.valueAt(opNum); + int numDevices = op.mDeviceAttributedOps.size(); + for (int deviceNum = 0; deviceNum < numDevices; deviceNum++) { + ArrayMap<String, AttributedOp> attrOps = + op.mDeviceAttributedOps.valueAt(deviceNum); + int numAttributions = attrOps.size(); + for (int attrNum = 0; attrNum < numAttributions; attrNum++) { + action.accept(attrOps.valueAt(attrNum)); + } + } + } + } + } + /** * Notify the proc state or capability has changed for a certain UID. */ @@ -2700,7 +2786,7 @@ public class AppOpsService extends IAppOpsService.Stub { * have information on them. */ private static boolean isOpAllowedForUid(int uid) { - return runtimePermissionAppopsMappingEnabled() + return Flags.runtimePermissionAppopsMappingEnabled() && (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID); } @@ -2910,10 +2996,12 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxyUid = attributionSource.getUid(); final String proxyPackageName = attributionSource.getPackageName(); final String proxyAttributionTag = attributionSource.getAttributionTag(); - final int proxiedUid = attributionSource.getNextUid(); final int proxyVirtualDeviceId = attributionSource.getDeviceId(); + + final int proxiedUid = attributionSource.getNextUid(); final String proxiedPackageName = attributionSource.getNextPackageName(); final String proxiedAttributionTag = attributionSource.getNextAttributionTag(); + final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId(); verifyIncomingProxyUid(attributionSource); verifyIncomingOp(code); @@ -2950,7 +3038,8 @@ public class AppOpsService extends IAppOpsService.Stub { final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid, resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId, - Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted, + Process.INVALID_UID, null, null, + Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage); if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) { return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag, @@ -2968,9 +3057,9 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED; return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName, - proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName, - proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message, - shouldCollectMessage); + proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName, + proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); } @Override @@ -3021,14 +3110,14 @@ public class AppOpsService extends IAppOpsService.Stub { } return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag, virtualDeviceId, Process.INVALID_UID, null, null, - AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message, - shouldCollectMessage); + Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); } private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId, int proxyUid, - String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags, - boolean shouldCollectAsyncNotedOp, @Nullable String message, + String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId, + @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage) { PackageVerificationResult pvr; try { @@ -3159,8 +3248,9 @@ public class AppOpsService extends IAppOpsService.Stub { } scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED); + attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, - uidState.getState(), flags); + getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags); if (shouldCollectAsyncNotedOp) { collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message, @@ -3526,9 +3616,9 @@ public class AppOpsService extends IAppOpsService.Stub { } return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, - virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF, - startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - attributionFlags, attributionChainId); + virtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, + OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, attributionFlags, attributionChainId); } /** @deprecated Use {@link #startProxyOperationWithState} instead. */ @@ -3566,18 +3656,32 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxyUid = attributionSource.getUid(); final String proxyPackageName = attributionSource.getPackageName(); final String proxyAttributionTag = attributionSource.getAttributionTag(); - final int proxiedUid = attributionSource.getNextUid(); final int proxyVirtualDeviceId = attributionSource.getDeviceId(); + + final int proxiedUid = attributionSource.getNextUid(); final String proxiedPackageName = attributionSource.getNextPackageName(); final String proxiedAttributionTag = attributionSource.getNextAttributionTag(); + final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId(); verifyIncomingProxyUid(attributionSource); verifyIncomingOp(code); if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) { - Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId " - + proxyVirtualDeviceId + " is invalid"); - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, - proxiedPackageName); + Slog.w( + TAG, + "startProxyOperationImpl returned MODE_IGNORED as proxyVirtualDeviceId " + + proxyVirtualDeviceId + + " is invalid"); + return new SyncNotedAppOp( + AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName); + } + if (!isValidVirtualDeviceId(proxiedVirtualDeviceId)) { + Slog.w( + TAG, + "startProxyOperationImpl returned MODE_IGNORED as proxiedVirtualDeviceId " + + proxiedVirtualDeviceId + + " is invalid"); + return new SyncNotedAppOp( + AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName); } if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid)) || !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) { @@ -3619,7 +3723,7 @@ public class AppOpsService extends IAppOpsService.Stub { // Test if the proxied operation will succeed before starting the proxy operation final SyncNotedAppOp testProxiedOp = startOperationDryRun(code, proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, - proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags, + proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags, startIfModeDefault); if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) { @@ -3631,7 +3735,7 @@ public class AppOpsService extends IAppOpsService.Stub { final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId, - Process.INVALID_UID, null, null, proxyFlags, + Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message, shouldCollectMessage, proxyAttributionFlags, attributionChainId); if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) { @@ -3640,9 +3744,10 @@ public class AppOpsService extends IAppOpsService.Stub { } return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, - proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName, - proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, - message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId); + proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName, + proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, startIfModeDefault, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, proxiedAttributionFlags, + attributionChainId); } private boolean shouldStartForMode(int mode, boolean startIfModeDefault) { @@ -3652,9 +3757,10 @@ public class AppOpsService extends IAppOpsService.Stub { private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId, int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag, - @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, - @Nullable String message, boolean shouldCollectMessage, - @AttributionFlags int attributionFlags, int attributionChainId) { + int proxyVirtualDeviceId, @OpFlags int flags, boolean startIfModeDefault, + boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId) { PackageVerificationResult pvr; try { pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); @@ -3749,13 +3855,13 @@ public class AppOpsService extends IAppOpsService.Stub { + " flags: " + AppOpsManager.flagsToString(flags)); try { if (isRestricted) { - attributedOp.createPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, virtualDeviceId, uidState.getState(), flags, - attributionFlags, attributionChainId); + attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, + proxyAttributionTag, getPersistentId(proxyVirtualDeviceId), + uidState.getState(), flags, attributionFlags, attributionChainId); } else { - attributedOp.started(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, virtualDeviceId, uidState.getState(), flags, - attributionFlags, attributionChainId); + attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName, + proxyAttributionTag, getPersistentId(proxyVirtualDeviceId), + uidState.getState(), flags, attributionFlags, attributionChainId); startType = START_TYPE_STARTED; } } catch (RemoteException e) { @@ -4753,8 +4859,8 @@ public class AppOpsService extends IAppOpsService.Stub { if ((code == OP_CAMERA) && isAutomotive()) { final long identity = Binder.clearCallingIdentity(); try { - if ((Flags.cameraPrivacyAllowlist()) - && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) { + if (com.android.internal.camera.flags.Flags.cameraPrivacyAllowlist() + && mSensorPrivacyManager.isCameraPrivacyEnabled(packageName)) { return true; } } finally { @@ -4944,7 +5050,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (accessTime > 0) { attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg, - proxyAttributionTag, uidState, opFlags); + proxyAttributionTag, PERSISTENT_DEVICE_ID_DEFAULT, uidState, opFlags); } if (rejectTime > 0) { attributedOp.rejected(rejectTime, uidState, opFlags); diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java index 18ea8cfc1386..268b286d8fe1 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java @@ -68,6 +68,7 @@ interface AppOpsUidStateTracker { return UID_STATE_BACKGROUND; } + // UID_STATE_NONEXISTENT is deliberately excluded here return UID_STATE_CACHED; } diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index bc6ef2005584..03c81560be89 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -34,7 +34,9 @@ import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS; import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; +import static android.app.AppOpsManager.UID_STATE_NONEXISTENT; import static android.app.AppOpsManager.UID_STATE_TOP; +import static android.permission.flags.Flags.finishRunningOpsForKilledPackages; import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; @@ -343,13 +345,14 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); boolean appWidgetVisible = mAppWidgetVisible.get(uid, false); + boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + || capability != pendingCapability + || appWidgetVisible != pendingAppWidgetVisible; + if (uidState != pendingUidState || capability != pendingCapability || appWidgetVisible != pendingAppWidgetVisible) { - boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED - != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED - || capability != pendingCapability - || appWidgetVisible != pendingAppWidgetVisible; if (foregroundChange) { // To save on memory usage, log only interesting changes. @@ -372,6 +375,16 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { mCapability.delete(uid); mAppWidgetVisible.delete(uid); mPendingGone.delete(uid); + if (finishRunningOpsForKilledPackages()) { + for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) { + UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i); + Executor executor = mUidStateChangedCallbacks.valueAt(i); + + executor.execute(PooledLambda.obtainRunnable( + UidStateChangedCallback::onUidStateChanged, cb, uid, + UID_STATE_NONEXISTENT, foregroundChange)); + } + } } else { mUidStates.put(uid, pendingUidState); mCapability.put(uid, pendingCapability); diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index 2285826c0b58..02fc9938c02c 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -24,7 +24,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; -import android.companion.virtual.VirtualDeviceManager; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; @@ -39,6 +38,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.Consumer; final class AttributedOp { private final @NonNull AppOpsService mAppOpsService; @@ -95,16 +95,17 @@ final class AttributedOp { * * @param proxyUid The uid of the proxy * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package + * @param proxyAttributionTag The attributionTag in the proxies package + * @param proxyDeviceId The device Id of the proxy * @param uidState UID state of the app noteOp/startOp was called for * @param flags OpFlags of the call */ public void accessed(int proxyUid, @Nullable String proxyPackageName, - @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int flags) { + @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { long accessTime = System.currentTimeMillis(); - accessed(accessTime, -1, proxyUid, proxyPackageName, - proxyAttributionTag, uidState, flags); + accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, + uidState, flags); mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, tag, uidState, flags, accessTime, @@ -118,14 +119,16 @@ final class AttributedOp { * @param duration The duration of the event * @param proxyUid The uid of the proxy * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package + * @param proxyAttributionTag The attributionTag in the proxies package + * @param proxyDeviceId The device Id of the proxy * @param uidState UID state of the app noteOp/startOp was called for * @param flags OpFlags of the call */ @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService public void accessed(long noteTime, long duration, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags) { long key = makeKey(uidState, flags); if (mAccessEvents == null) { @@ -135,7 +138,7 @@ final class AttributedOp { AppOpsManager.OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + proxyAttributionTag, proxyDeviceId); } AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key); @@ -189,35 +192,36 @@ final class AttributedOp { * Update state when start was called * * @param clientId Id of the startOp caller + * @param virtualDeviceId The virtual device id of the startOp caller * @param proxyUid The UID of the proxy app * @param proxyPackageName The package name of the proxy app * @param proxyAttributionTag The attribution tag of the proxy app + * @param proxyDeviceId The device id of the proxy app * @param uidState UID state of the app startOp is called for * @param flags The proxy flags * @param attributionFlags The attribution flags associated with this operation. - * @param attributionChainId The if of the attribution chain this operations is a part of. + * @param attributionChainId The if of the attribution chain this operations is a part of */ - public void started(@NonNull IBinder clientId, int proxyUid, + public void started(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState, + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, proxyVirtualDeviceId, uidState, flags, - /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags, - attributionChainId); + startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag, + proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false, + true); } @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService - private void startedOrPaused(@NonNull IBinder clientId, int proxyUid, + private void startedOrPaused(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange, - boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags, - int attributionChainId) throws RemoteException { + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId, boolean triggeredByUidStateChange, boolean isStarted) + throws RemoteException { if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags, + parent.packageName, tag, virtualDeviceId, true, attributionFlags, attributionChainId); } @@ -233,9 +237,9 @@ final class AttributedOp { InProgressStartOpEvent event = events.get(clientId); if (event == null) { event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, - SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId, + SystemClock.elapsedRealtime(), clientId, tag, virtualDeviceId, PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), - proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, + proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, uidState, flags, attributionFlags, attributionChainId); events.put(clientId, event); } else { @@ -253,6 +257,19 @@ final class AttributedOp { } } + public void doForAllInProgressStartOpEvents(Consumer<InProgressStartOpEvent> action) { + ArrayMap<IBinder, AttributedOp.InProgressStartOpEvent> events = isPaused() + ? mPausedInProgressEvents : mInProgressEvents; + if (events == null) { + return; + } + + int numStartedOps = events.size(); + for (int i = 0; i < numStartedOps; i++) { + action.accept(events.valueAt(i)); + } + } + /** * Update state when finishOp was called. Will finish started ops, and delete paused ops. * @@ -366,15 +383,14 @@ final class AttributedOp { /** * Create an event that will be started, if the op is unpaused. */ - public void createPaused(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int flags, - @AppOpsManager.AttributionFlags int attributionFlags, + public void createPaused(@NonNull IBinder clientId, int virtualDeviceId, + int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag, - proxyVirtualDeviceId, uidState, flags, false, false, - attributionFlags, attributionChainId); + startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag, + proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false, + false); } /** @@ -496,16 +512,16 @@ final class AttributedOp { // Call started() to add a new start event object and then add the // previously removed unfinished start counts back if (proxy != null) { - startedOrPaused(event.getClientId(), proxy.getUid(), - proxy.getPackageName(), proxy.getAttributionTag(), - event.getVirtualDeviceId(), newState, event.getFlags(), - true, isRunning, - event.getAttributionFlags(), event.getAttributionChainId()); + startedOrPaused(event.getClientId(), event.getVirtualDeviceId(), + proxy.getUid(), proxy.getPackageName(), proxy.getAttributionTag(), + proxy.getDeviceId(), newState, event.getFlags(), + event.getAttributionFlags(), event.getAttributionChainId(), true, + isRunning); } else { - startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null, - event.getVirtualDeviceId(), newState, event.getFlags(), true, - isRunning, event.getAttributionFlags(), - event.getAttributionChainId()); + startedOrPaused(event.getClientId(), event.getVirtualDeviceId(), + Process.INVALID_UID, null, null, null, + newState, event.getFlags(), event.getAttributionFlags(), + event.getAttributionChainId(), true, isRunning); } events = isRunning ? mInProgressEvents : mPausedInProgressEvents; @@ -847,7 +863,8 @@ final class AttributedOp { InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath, int proxyUid, @Nullable String proxyPackageName, - @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, + @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { @@ -856,7 +873,7 @@ final class AttributedOp { AppOpsManager.OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + proxyAttributionTag, proxyDeviceId); } if (recycled != null) { @@ -880,7 +897,8 @@ final class AttributedOp { super(maxUnusedPooledObjects); } - AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid, + AppOpsManager.OpEventProxyInfo acquire( + @IntRange(from = 0) int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String deviceId) { @@ -890,7 +908,7 @@ final class AttributedOp { return recycled; } - return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag); + return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag, deviceId); } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index add84910bf48..15c5c1077803 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4440,7 +4440,8 @@ public class AudioService extends IAudioService.Stub || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) { voiceActive = true; } - if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) { + if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME + || usage == AudioAttributes.USAGE_UNKNOWN) { mediaActive = true; } } @@ -9778,9 +9779,9 @@ public class AudioService extends IAudioService.Stub mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this); mContentResolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.MASTER_MONO), false, this); + Settings.System.MASTER_MONO), false, this, UserHandle.USER_ALL); mContentResolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.MASTER_BALANCE), false, this); + Settings.System.MASTER_BALANCE), false, this, UserHandle.USER_ALL); mEncodedSurroundMode = mSettings.getGlobalInt( mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT, diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java index ba453101046f..cf0b2ae15618 100644 --- a/services/core/java/com/android/server/audio/MusicFxHelper.java +++ b/services/core/java/com/android/server/audio/MusicFxHelper.java @@ -70,6 +70,8 @@ public class MusicFxHelper { // The binder token identifying the UidObserver registration. private IBinder mUidObserverToken = null; + private boolean mIsBound; + // Package name and list of open audio sessions for this package private static class PackageSessions { String mPackageName; @@ -109,6 +111,7 @@ public class MusicFxHelper { if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { Intent bindIntent = new Intent().setClassName(mMusicFxPackageName, "com.android.musicfx.KeepAliveService"); + mIsBound = true; mContext.bindServiceAsUser( bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE, UserHandle.of(getCurrentUserId())); @@ -157,9 +160,12 @@ public class MusicFxHelper { Log.e(TAG, "RemoteException with unregisterUidObserver: " + e); } mUidObserverToken = null; - mContext.unbindService(mMusicFxBindConnection); - Log.i(TAG, "last session closed, unregister UID observer, and unbind " - + mMusicFxPackageName); + if (mIsBound) { + mContext.unbindService(mMusicFxBindConnection); + mIsBound = false; + Log.i(TAG, "last session closed, unregister UID observer, and unbind " + + mMusicFxPackageName); + } } } } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index e2c4b4638207..cae169550d9a 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -347,9 +347,6 @@ public class SpatializerHelper { //------------------------------------------------------ // routing monitoring synchronized void onRoutingUpdated() { - if (!mFeatureEnabled) { - return; - } switch (mState) { case STATE_UNINITIALIZED: case STATE_NOT_SUPPORTED: @@ -393,7 +390,7 @@ public class SpatializerHelper { setDispatchAvailableState(false); } - boolean enabled = able && enabledAvailable.first; + boolean enabled = mFeatureEnabled && able && enabledAvailable.first; if (enabled) { loglogi("Enabling Spatial Audio since enabled for media device:" + currentDevice); diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 11cca66318d6..2a1687209aad 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -298,7 +298,7 @@ public class AuthService extends SystemService { return -1; } - if (promptInfo.containsTestConfigurations()) { + if (promptInfo.requiresTestOrInternalPermission()) { if (getContext().checkCallingOrSelfPermission(TEST_BIOMETRIC) != PackageManager.PERMISSION_GRANTED) { checkInternalPermission(); @@ -306,10 +306,10 @@ public class AuthService extends SystemService { } // Only allow internal clients to enable non-public options. - if (promptInfo.containsPrivateApiConfigurations()) { + if (promptInfo.requiresInternalPermission()) { checkInternalPermission(); } - if (promptInfo.containsAdvancedApiConfigurations()) { + if (promptInfo.requiresAdvancedPermission()) { checkBiometricAdvancedPermission(); } diff --git a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java index 3e8acee26e81..7cf2d3028aef 100644 --- a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java +++ b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java @@ -16,6 +16,7 @@ package com.android.server.biometrics; import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import android.annotation.NonNull; import android.content.BroadcastReceiver; @@ -63,7 +64,7 @@ public class BiometricDanglingReceiver extends BroadcastReceiver { intentFilter.addAction(ACTION_FACE_RE_ENROLL_LAUNCH); intentFilter.addAction(ACTION_FACE_RE_ENROLL_DISMISS); } - context.registerReceiver(this, intentFilter); + context.registerReceiver(this, intentFilter, Context.RECEIVER_NOT_EXPORTED); } @Override @@ -84,7 +85,8 @@ public class BiometricDanglingReceiver extends BroadcastReceiver { } private void launchBiometricEnrollActivity(Context context, String action) { - context.sendBroadcast(new Intent(ACTION_CLOSE_SYSTEM_DIALOGS)); + context.sendBroadcast( + new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND)); final Intent intent = new Intent(action); intent.setPackage(SETTINGS_PACKAGE); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 645a3664681b..390ee96a3417 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -37,6 +37,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; +import android.graphics.ImageFormat; import android.graphics.Rect; import android.hardware.CameraExtensionSessionStats; import android.hardware.CameraSessionStats; @@ -906,6 +907,7 @@ public class CameraServiceProxy extends SystemService int extensionType = FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_NONE; boolean extensionIsAdvanced = false; + int extensionCaptureFormat = ImageFormat.UNKNOWN; if (e.mExtSessionStats != null) { switch (e.mExtSessionStats.type) { case CameraExtensionSessionStats.Type.EXTENSION_AUTOMATIC: @@ -932,6 +934,9 @@ public class CameraServiceProxy extends SystemService Slog.w(TAG, "Unknown extension type: " + e.mExtSessionStats.type); } extensionIsAdvanced = e.mExtSessionStats.isAdvanced; + if (Flags.analytics24q3()) { + extensionCaptureFormat = e.mExtSessionStats.captureFormat; + } } int streamCount = 0; @@ -945,10 +950,13 @@ public class CameraServiceProxy extends SystemService String zoomOverrideDebug = Flags.logZoomOverrideUsage() ? ", zoomOverrideUsage " + e.mUsedZoomOverride : ""; - String mostRequestedFpsRangeDebug = Flags.analytics24q3() ? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange : ""; + String extensionCaptureFormatDebug = Flags.analytics24q3() + ? " extensionCaptureFormat " + e.mExtSessionStats.captureFormat + : ""; + Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction + " clientName " + e.mClientName + ", duration " + e.getDuration() @@ -971,8 +979,10 @@ public class CameraServiceProxy extends SystemService + ", logId " + e.mLogId + ", sessionIndex " + e.mSessionIndex + ", mExtSessionStats {type " + extensionType - + " isAdvanced " + extensionIsAdvanced + "}"); + + " isAdvanced " + extensionIsAdvanced + + extensionCaptureFormatDebug + "}"); } + // Convert from CameraStreamStats to CameraStreamProto CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS]; for (int i = 0; i < MAX_STREAM_STATISTICS; i++) { @@ -1035,7 +1045,8 @@ public class CameraServiceProxy extends SystemService e.mLogId, e.mSessionIndex, extensionType, extensionIsAdvanced, e.mUsedUltraWide, e.mUsedZoomOverride, - e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper()); + e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper(), + extensionCaptureFormat); } } diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index d9c3ab806888..30d12e670a6c 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -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..fa8299bd45fd 100644 --- a/services/core/java/com/android/server/display/DisplayControl.java +++ b/services/core/java/com/android/server/display/DisplayControl.java @@ -29,7 +29,7 @@ import java.util.Objects; */ public class DisplayControl { private static native IBinder nativeCreateDisplay(String name, boolean secure, - float requestedRefreshRate); + String uniqueId, float requestedRefreshRate); private static native void nativeDestroyDisplay(IBinder displayToken); private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); private static native long[] nativeGetPhysicalDisplayIds(); @@ -43,20 +43,21 @@ public class DisplayControl { /** * Create a display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the display. * @param secure Whether this display is secure. * @return The token reference for the display in SurfaceFlinger. */ public static IBinder createDisplay(String name, boolean secure) { Objects.requireNonNull(name, "name must not be null"); - return nativeCreateDisplay(name, secure, 0.0f); + return nativeCreateDisplay(name, secure, "", 0.0f); } /** * Create a display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the display. * @param secure Whether this display is secure. + * @param uniqueId The unique ID for the display. * @param requestedRefreshRate The requested refresh rate in frames per second. * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded @@ -65,9 +66,10 @@ public class DisplayControl { * @return The token reference for the display in SurfaceFlinger. */ public static IBinder createDisplay(String name, boolean secure, - float requestedRefreshRate) { + String uniqueId, float requestedRefreshRate) { Objects.requireNonNull(name, "name must not be null"); - return nativeCreateDisplay(name, secure, requestedRefreshRate); + Objects.requireNonNull(uniqueId, "uniqueId must not be null"); + return nativeCreateDisplay(name, secure, uniqueId, requestedRefreshRate); } /** @@ -79,7 +81,6 @@ public class DisplayControl { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); } - nativeDestroyDisplay(displayToken); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index d7a7dd493903..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()); } @@ -1333,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); @@ -1372,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, @@ -1440,35 +1442,54 @@ 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 - .getAutomaticScreenBrightnessBasedOnLastUsedLux( - 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); - } - if (!mFlags.isRefactorDisplayPowerControllerEnabled()) { // The ALS is not available yet - use the screen off sensor to determine the initial // brightness @@ -1490,7 +1511,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } // 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/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index bcdb442c3ad3..a29e8523952d 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -92,8 +92,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) { this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() { @Override - public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) { - return DisplayControl.createDisplay(name, secure, requestedRefreshRate); + public IBinder createDisplay(String name, boolean secure, String uniqueId, + float requestedRefreshRate) { + return DisplayControl.createDisplay(name, secure, uniqueId, requestedRefreshRate); } @Override @@ -126,7 +127,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { String name = virtualDisplayConfig.getName(); boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; - IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, + IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, uniqueId, virtualDisplayConfig.getRequestedRefreshRate()); MediaProjectionCallback mediaProjectionCallback = null; if (projection != null) { @@ -653,8 +654,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { /** * Create a virtual display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the display. * @param secure Whether this display is secure. + * @param uniqueId The unique ID for the display. * @param requestedRefreshRate * The refresh rate, frames per second, to request on the virtual display. * It should be a divisor of refresh rate of the leader physical display @@ -663,8 +665,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { * the refresh rate of the leader physical display. * @return The token reference for the display in SurfaceFlinger. */ - IBinder createDisplay(String name, boolean secure, float requestedRefreshRate); - + IBinder createDisplay(String name, boolean secure, String uniqueId, + float requestedRefreshRate); + /** * Destroy a display in SurfaceFlinger. * diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index 22a21a6c113c..feec4e6b2259 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -32,6 +32,7 @@ import com.android.server.display.brightness.strategy.AutomaticBrightnessStrateg 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; @@ -85,6 +86,9 @@ public class DisplayBrightnessStrategySelector { @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 @@ -118,7 +122,8 @@ 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); @@ -134,11 +139,14 @@ public class DisplayBrightnessStrategySelector { } else { mOffloadBrightnessStrategy = null; } + mFallbackBrightnessStrategy = (mDisplayManagerFlags + .isRefactorDisplayPowerControllerEnabled()) + ? injector.getFallbackBrightnessStrategy() : null; mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy, mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy, mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy, mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy, - mAutoBrightnessFallbackStrategy}; + mAutoBrightnessFallbackStrategy, mFallbackBrightnessStrategy}; mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean( R.bool.config_allowAutoBrightnessWhileDozing); mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName(); @@ -179,6 +187,12 @@ public class DisplayBrightnessStrategySelector { 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()) { @@ -330,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, @@ -347,5 +361,9 @@ public class DisplayBrightnessStrategySelector { AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() { return new AutoBrightnessFallbackStrategy(/* injector= */ null); } + + FallbackBrightnessStrategy getFallbackBrightnessStrategy() { + return new FallbackBrightnessStrategy(); + } } } 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 23052286d9ec..f809a49fd3d3 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() @@ -479,6 +489,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/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/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/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index bc28b22fe1ed..05c4aa67a480 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; @@ -4399,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 @@ -4409,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 diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java index 2703a2c01848..7e18d8412aae 100644 --- a/services/core/java/com/android/server/hdmi/SendKeyAction.java +++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java @@ -158,9 +158,11 @@ final class SendKeyAction extends HdmiCecFeatureAction { mTargetAddress, cecKeycodeAndParams), new SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error != SendMessageResult.SUCCESS) { + // Disable System Audio Mode, if the AVR doesn't acknowledge + // a <User Control Pressed> message. + if (error == SendMessageResult.NACK) { HdmiLogger.debug( - "AVR did not respond to <User Control Pressed>"); + "AVR did not acknowledge <User Control Pressed>"); localDevice().mService.setSystemAudioActivated(false); } } diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index b47631c35e38..d32a5ed60094 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -218,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 cbd309e1f957..8685d2c45762 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -156,7 +156,6 @@ public class InputManagerService extends IInputManager.Stub private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1; private static final int MSG_RELOAD_DEVICE_ALIASES = 2; private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3; - private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 4; private static final int DEFAULT_VIBRATION_MAGNITUDE = 192; private static final AdditionalDisplayInputProperties @@ -255,7 +254,7 @@ public class InputManagerService extends IInputManager.Stub // to {DisplayInfo#uniqueId} (String) so that events from the Input Device go to a // specific display. @GuardedBy("mAssociationsLock") - private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>(); + private final Map<String, String> mUniqueIdAssociationsByPort = new ArrayMap<>(); // The associations of input devices to displays by descriptor. Maps from // {InputDevice#mDescriptor} to {DisplayInfo#uniqueId} (String) so that events from the @@ -1324,11 +1323,6 @@ public class InputManagerService extends IInputManager.Stub properties -> properties.pointerIconVisible = visible); } - private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) { - mWindowManagerCallbacks.notifyPointerDisplayIdChanged( - args.mPointerDisplayId, args.mXPosition, args.mYPosition); - } - private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible); } @@ -1612,18 +1606,6 @@ public class InputManagerService extends IInputManager.Stub // Binder call @Override - public void setPointerIconType(int iconType) { - // TODO(b/311416205): Remove. - } - - // Binder call - @Override - public void setCustomPointerIcon(PointerIcon icon) { - // TODO(b/311416205): Remove. - } - - // Binder call - @Override public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId, IBinder inputToken) { Objects.requireNonNull(icon); @@ -1674,7 +1656,8 @@ public class InputManagerService extends IInputManager.Stub } @Override // Binder call - public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) { + public void addUniqueIdAssociationByPort(@NonNull String inputPort, + @NonNull String displayUniqueId) { if (!checkCallingPermission( android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY, "addUniqueIdAssociation()")) { @@ -1685,13 +1668,13 @@ public class InputManagerService extends IInputManager.Stub Objects.requireNonNull(inputPort); Objects.requireNonNull(displayUniqueId); synchronized (mAssociationsLock) { - mUniqueIdAssociations.put(inputPort, displayUniqueId); + mUniqueIdAssociationsByPort.put(inputPort, displayUniqueId); } mNative.changeUniqueIdAssociation(); } @Override // Binder call - public void removeUniqueIdAssociation(@NonNull String inputPort) { + public void removeUniqueIdAssociationByPort(@NonNull String inputPort) { if (!checkCallingPermission( android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY, "removeUniqueIdAssociation()")) { @@ -1700,7 +1683,7 @@ public class InputManagerService extends IInputManager.Stub Objects.requireNonNull(inputPort); synchronized (mAssociationsLock) { - mUniqueIdAssociations.remove(inputPort); + mUniqueIdAssociationsByPort.remove(inputPort); } mNative.changeUniqueIdAssociation(); } @@ -2119,9 +2102,9 @@ public class InputManagerService extends IInputManager.Stub pw.println(" display: " + v); }); } - if (!mUniqueIdAssociations.isEmpty()) { + if (!mUniqueIdAssociationsByPort.isEmpty()) { pw.println("Unique Id Associations:"); - mUniqueIdAssociations.forEach((k, v) -> { + mUniqueIdAssociationsByPort.forEach((k, v) -> { pw.print(" port: " + k); pw.println(" uniqueId: " + v); }); @@ -2548,10 +2531,10 @@ public class InputManagerService extends IInputManager.Stub // Native callback @SuppressWarnings("unused") - private String[] getInputUniqueIdAssociations() { + private String[] getInputUniqueIdAssociationsByPort() { final Map<String, String> associations; synchronized (mAssociationsLock) { - associations = new HashMap<>(mUniqueIdAssociations); + associations = new HashMap<>(mUniqueIdAssociationsByPort); } return flatten(associations); @@ -2703,9 +2686,7 @@ public class InputManagerService extends IInputManager.Stub @SuppressWarnings("unused") @VisibleForTesting void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) { - mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED, - new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition, - yPosition)).sendToTarget(); + // TODO(b/311416205): Remove. } @Override @@ -2860,14 +2841,6 @@ public class InputManagerService extends IInputManager.Stub */ @Nullable SurfaceControl createSurfaceForGestureMonitor(String name, int displayId); - - /** - * Notify WindowManagerService when the display of the mouse pointer changes. - * @param displayId The display on which the mouse pointer is shown. - * @param x The x coordinate of the mouse pointer. - * @param y The y coordinate of the mouse pointer. - */ - void notifyPointerDisplayIdChanged(int displayId, float x, float y); } /** @@ -2911,9 +2884,6 @@ public class InputManagerService extends IInputManager.Stub boolean inTabletMode = (boolean) args.arg1; deliverTabletModeChanged(whenNanos, inTabletMode); break; - case MSG_POINTER_DISPLAY_ID_CHANGED: - handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj); - break; } } } @@ -3234,6 +3204,11 @@ public class InputManagerService extends IInputManager.Stub public void setStylusButtonMotionEventsEnabled(boolean enabled) { mNative.setStylusButtonMotionEventsEnabled(enabled); } + + @Override + public int getLastUsedInputDeviceId() { + return mNative.getLastUsedInputDeviceId(); + } } @Override diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index f742360484f5..0208a325a1d5 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -271,6 +271,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. */ @@ -544,5 +553,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..ad98b4a8db13 100644 --- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java +++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java @@ -29,6 +29,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; /** @@ -40,14 +41,21 @@ final class AutofillSuggestionsController { @NonNull private final InputMethodManagerService mService; + /** + * The host input token of the input method that is currently associated with this controller. + */ + @GuardedBy("ImfLock.class") + @Nullable + private IBinder mCurHostInputToken; + private static final class CreateInlineSuggestionsRequest { @NonNull final InlineSuggestionsRequestInfo mRequestInfo; - @NonNull final IInlineSuggestionsRequestCallback mCallback; + @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 +79,49 @@ final class AutofillSuggestionsController { */ @GuardedBy("ImfLock.class") @Nullable - private IInlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback; + private InlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback; AutofillSuggestionsController(@NonNull InputMethodManagerService service) { mService = service; } @GuardedBy("ImfLock.class") + void onResetSystemUi() { + mCurHostInputToken = null; + } + + @Nullable + @GuardedBy("ImfLock.class") + IBinder getCurHostInputToken() { + return mCurHostInputToken; + } + + @GuardedBy("ImfLock.class") void onCreateInlineSuggestionsRequest(@UserIdInt int userId, - InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback, + 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."); - } - } + + 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 { - callback.onInlineSuggestionsUnsupported(); + // 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); + } else { + callback.onInlineSuggestionsUnsupported(); } } @@ -124,8 +140,7 @@ final class AutofillSuggestionsController { mPendingInlineSuggestionsRequest.mCallback, mPendingInlineSuggestionsRequest.mPackageName, mService.getCurTokenDisplayIdLocked(), - mService.getCurTokenLocked(), - mService); + mService.getCurTokenLocked()); 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 = mService.getCurTokenLocked(); + 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/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 0fde760fd02a..ac72692c8e6f 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; @@ -262,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 @@ -285,6 +286,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final Resources mRes; private final Handler mHandler; + @NonNull + private final Handler mPackageMonitorHandler; + @MultiUserUnawareField @UserIdInt @GuardedBy("ImfLock.class") @@ -485,26 +489,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return userData.mBindingController.getSelectedMethodId(); } - /** - * The current binding sequence number, incremented every time there is - * a new bind performed. - */ - @GuardedBy("ImfLock.class") - private int getSequenceNumberLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.getSequenceNumber(); - } - - /** - * Increase the current binding sequence number by one. - * Reset to 1 on overflow. - */ - @GuardedBy("ImfLock.class") - private void advanceSequenceNumberLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - userData.mBindingController.advanceSequenceNumber(); - } - @GuardedBy("ImfLock.class") @Nullable InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) { @@ -553,21 +537,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() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.getCurId(); - } - - /** * The current subtype of the current input method. */ @MultiUserUnawareField @@ -583,16 +552,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>(); /** - * Set to true if our ServiceConnection is currently actively bound to - * a service (whether or not we have gotten its IBinder back yet). - */ - @GuardedBy("ImfLock.class") - private boolean hasConnectionLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.hasMainConnection(); - } - - /** * The token tracking the current IME show request that is waiting for a connection to an IME, * otherwise {@code null}. */ @@ -607,16 +566,6 @@ 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() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.getCurIntent(); - } - - /** * The token we have made for the currently active input method, to * identify it in the future. */ @@ -645,14 +594,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private int mCurTokenDisplayId = INVALID_DISPLAY; /** - * The host input token of the current active input method. - */ - @GuardedBy("ImfLock.class") - @Nullable - @MultiUserUnawareField - private IBinder mCurHostInputToken; - - /** * The display ID of the input method indicates the fallback display which returned by * {@link #computeImeDisplayIdForTarget}. */ @@ -670,25 +611,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } /** - * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntentLocked()}. - */ - @GuardedBy("ImfLock.class") - private int getCurMethodUidLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.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() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.getLastBindTime(); - } - - /** * Have we called mCurMethod.bindInput()? */ @MultiUserUnawareField @@ -1106,8 +1028,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } private void onFinishPackageChangesInternal() { + final int userId = getChangingUserId(); + + // 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(); + synchronized (ImfLock.class) { - final int userId = getChangingUserId(); final boolean isCurrentUser = (userId == mCurrentUserId); final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); @@ -1159,9 +1089,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) { return; } - - final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, - userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO); + final var newMethodMap = newMethodMapWithoutAdditionalSubtypes + .applyAdditionalSubtypes(newAdditionalSubtypeMap); + final InputMethodSettings newSettings = + InputMethodSettings.create(newMethodMap, userId); InputMethodSettingsRepository.put(userId, newSettings); if (!isCurrentUser) { return; @@ -1361,13 +1292,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } public InputMethodManagerService(Context context) { - this(context, null, null); + this(context, null, null, null); } @VisibleForTesting InputMethodManagerService( Context context, @Nullable ServiceThread serviceThreadForTesting, + @Nullable ServiceThread packageMonitorThreadForTesting, @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) { synchronized (ImfLock.class) { mContext = context; @@ -1385,6 +1317,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()); @@ -1658,7 +1601,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(); @@ -1840,21 +1783,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } /** - * Sets current host input token. - * - * @param callerImeToken the token has been made for the current active input method - * @param hostInputToken the host input token of the current active input method - */ - void setCurHostInputToken(@NonNull IBinder callerImeToken, @Nullable IBinder hostInputToken) { - synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(callerImeToken)) { - return; - } - mCurHostInputToken = hostInputToken; - } - } - - /** * Gets enabled subtypes of the specified {@link InputMethodInfo}. * * @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}. @@ -1968,7 +1896,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return mClientController.getClient(client.asBinder()); } - // TODO(b/314150112): Move this to ClientController. @GuardedBy("ImfLock.class") void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) { if (mCurClient != null) { @@ -1988,7 +1915,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // all accessibility too. That means, when input method get disconnected (including // switching ime), we also unbind accessibility mCurClient.mClient.setActive(false /* active */, false /* fullscreen */); - mCurClient.mClient.onUnbindMethod(getSequenceNumberLocked(), unbindClientReason); + + // TODO(b/325515685): make binding controller user independent. Before this change, the + // following dependencies also need to be user independent: mCurClient, mBoundToMethod, + // getCurMethodLocked(), and mMenuController. + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + mCurClient.mClient.onUnbindMethod(userData.mBindingController.getSequenceNumber(), + unbindClientReason); mCurClient.mSessionRequested = false; mCurClient.mSessionRequestedForAccessibility = false; mCurClient = null; @@ -2066,12 +1999,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean restarting = !initial; final Binder startInputToken = new Binder(); + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); final StartInputInfo info = new StartInputInfo(mCurrentUserId, getCurTokenLocked(), - mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, - UserHandle.getUserId(mCurClient.mUid), + mCurTokenDisplayId, userData.mBindingController.getCurId(), startInputReason, + restarting, UserHandle.getUserId(mCurClient.mUid), mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo, - mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked()); + mImeBindingState.mFocusedWindowSoftInputMode, + userData.mBindingController.getSequenceNumber()); mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow); mStartInputHistory.addEntry(info); @@ -2082,8 +2017,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(userData.mBindingController.getCurMethodUid()), mCurClient.mUid, true /* direct */); } @@ -2104,21 +2039,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } - String curId = getCurIdLocked(); + final var curId = userData.mBindingController.getCurId(); final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId) .getMethodMap().get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) { mHwController.setInkWindowInitializer(new InkWindowInitializer()); } return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, session.mSession, accessibilityInputMethodSessions, (session.mChannel != null ? session.mChannel.dup() : null), - curId, getSequenceNumberLocked(), suppressesSpellChecker); + curId, userData.mBindingController.getSequenceNumber(), suppressesSpellChecker); } @GuardedBy("ImfLock.class") @@ -2170,7 +2104,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, int unverifiedTargetSdkVersion, - @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { + @NonNull ImeOnBackInvokedDispatcher imeDispatcher, + @NonNull UserDataRepository.UserData userData) { // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. @@ -2211,7 +2146,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean connectionWasActive = mCurInputConnection != null; // Bump up the sequence for this client and attach it. - advanceSequenceNumberLocked(); + userData.mBindingController.advanceSequenceNumber(); + mCurClient = cs; mCurInputConnection = inputConnection; mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection; @@ -2233,7 +2169,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (connectionIsActive != connectionWasActive) { mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive); } - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); final var bindingController = userData.mBindingController; // If configured, we want to avoid starting up the IME if it is not supposed to be showing @@ -2250,7 +2185,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Check if the input method is changing. // We expect the caller has already verified that the client is allowed to access this // display ID. - if (isSelectedMethodBoundLocked()) { + final String curId = bindingController.getCurId(); + if (curId != null && curId.equals(bindingController.getSelectedMethodId()) + && mDisplayIdToShowIme == mCurTokenDisplayId) { if (cs.mCurSession != null) { // Fast case: if we are already connected to the input method, // then just return it. @@ -2269,7 +2206,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0); } - InputBindResult bindResult = tryReuseConnectionLocked(cs); + InputBindResult bindResult = tryReuseConnectionLocked(userData, cs); if (bindResult != null) { return bindResult; } @@ -2369,13 +2306,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private boolean isSelectedMethodBoundLocked() { - String curId = getCurIdLocked(); - return curId != null && curId.equals(getSelectedMethodIdLocked()) - && mDisplayIdToShowIme == mCurTokenDisplayId; - } - - @GuardedBy("ImfLock.class") private void prepareClientSwitchLocked(ClientState cs) { // If the client is changing, we need to switch over to the new // one. @@ -2388,8 +2318,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @Nullable - private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) { - if (hasConnectionLocked()) { + private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData, + @NonNull ClientState cs) { + if (userData.mBindingController.hasMainConnection()) { if (getCurMethodLocked() != null) { // Return to client, and we will get back with it when // we have had a session made for it. @@ -2397,9 +2328,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, + userData.mBindingController.getCurId(), + userData.mBindingController.getSequenceNumber(), false); } else { - long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked(); + final long lastBindTime = userData.mBindingController.getLastBindTime(); + long bindingDuration = SystemClock.uptimeMillis() - lastBindTime; if (bindingDuration < TIME_TO_RECONNECT) { // In this case we have connected to the service, but // don't yet have its interface. If it hasn't been too @@ -2410,7 +2344,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, + userData.mBindingController.getCurId(), + userData.mBindingController.getSequenceNumber(), false); } else { EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodIdLocked(), bindingDuration, 0); @@ -2532,7 +2468,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; updateSystemUiLocked(mImeWindowVis, mBackDisposition); mCurTokenDisplayId = INVALID_DISPLAY; - mCurHostInputToken = null; + mAutofillController.onResetSystemUi(); } @GuardedBy("ImfLock.class") @@ -2764,7 +2700,8 @@ 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 userData = mUserDataRepository.getOrCreate(mCurrentUserId); + if (!Objects.equals(userData.mBindingController.getCurId(), getSelectedMethodIdLocked())) { return false; } if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() @@ -2926,8 +2863,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } else { vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; } + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var curId = userData.mBindingController.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; @@ -3622,12 +3561,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper); final InputBindResult result; synchronized (ImfLock.class) { + final var userData = mUserDataRepository.getOrCreate(userId); // If the system is not yet ready, we shouldn't be running third party code. if (!mSystemReady) { return new InputBindResult( InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, null /* method */, null /* accessibilitySessions */, null /* channel */, - getSelectedMethodIdLocked(), getSequenceNumberLocked(), + getSelectedMethodIdLocked(), + userData.mBindingController.getSequenceNumber(), false /* isInputMethodSuppressingSpellChecker */); } final ClientState cs = mClientController.getClient(client.asBinder()); @@ -3719,7 +3660,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection, - unverifiedTargetSdkVersion, userId, imeDispatcher, cs); + unverifiedTargetSdkVersion, userData, imeDispatcher, cs); } finally { Binder.restoreCallingIdentity(ident); } @@ -3747,7 +3688,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo, IRemoteInputConnection inputContext, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, - int unverifiedTargetSdkVersion, @UserIdInt int userId, + int unverifiedTargetSdkVersion, @NonNull UserDataRepository.UserData userData, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) { if (DEBUG) { Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason=" @@ -3760,7 +3701,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode) + " windowFlags=#" + Integer.toHexString(windowFlags) + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion - + " userId=" + userId + + " userData=" + userData + " imeDispatcher=" + imeDispatcher + " cs=" + cs); } @@ -3789,14 +3730,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (editorInfo != null) { return startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, - startInputReason, unverifiedTargetSdkVersion, imeDispatcher); + startInputReason, unverifiedTargetSdkVersion, imeDispatcher, userData); } return new InputBindResult( InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, null, null, null, null, -1, false); } - mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo); + mImeBindingState = new ImeBindingState(userData.mUserId, windowToken, softInputMode, cs, + editorInfo); mFocusedWindowPerceptible.put(windowToken, true); // We want to start input before showing the IME, but after closing @@ -3821,7 +3763,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); + imeDispatcher, userData); didStart = true; } break; @@ -3836,7 +3778,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Note that we can trust client's display ID as long as it matches // to the display ID obtained from the window. if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) { - final var userData = mUserDataRepository.getOrCreate(userId); userData.mBindingController.unbindCurrentMethod(); } } @@ -3846,7 +3787,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); + imeDispatcher, userData); } else { res = InputBindResult.NULL_EDITOR_INFO; } @@ -3887,10 +3828,10 @@ 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 userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var curIntent = userData.mBindingController.getCurIntent(); + if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid( + mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) { return true; } return false; @@ -4481,9 +4422,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (ImfLock.class) { + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); final long token = proto.start(fieldId); proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked()); - proto.write(CUR_SEQ, getSequenceNumberLocked()); + proto.write(CUR_SEQ, userData.mBindingController.getSequenceNumber()); proto.write(CUR_CLIENT, Objects.toString(mCurClient)); mImeBindingState.dumpDebug(proto, mWindowManagerInternal); proto.write(LAST_IME_TARGET_WINDOW_NAME, @@ -4493,13 +4435,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mCurEditorInfo != null) { mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); } - proto.write(CUR_ID, getCurIdLocked()); + proto.write(CUR_ID, userData.mBindingController.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(SYSTEM_READY, mSystemReady); - proto.write(HAVE_CONNECTION, hasConnectionLocked()); + proto.write(HAVE_CONNECTION, userData.mBindingController.hasMainConnection()); proto.write(BOUND_TO_METHOD, mBoundToMethod); proto.write(IS_INTERACTIVE, mIsInteractive); proto.write(BACK_DISPOSITION, mBackDisposition); @@ -4950,7 +4892,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 userData = mUserDataRepository.getOrCreate(mCurrentUserId); + if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol( + userData.mBindingController.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( @@ -5557,7 +5502,7 @@ 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); @@ -5625,14 +5570,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, - int displayId) { + int displayId, @UserIdInt int userId) { //TODO(b/150843766): Check if Input Token is valid. final IBinder curHostInputToken; synchronized (ImfLock.class) { - if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) { + if (displayId != mCurTokenDisplayId) { + return false; + } + curHostInputToken = mAutofillController.getCurHostInputToken(); + if (curHostInputToken == null) { return false; } - curHostInputToken = mCurHostInputToken; } return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken); } @@ -5677,6 +5625,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void onSessionForAccessibilityCreated(int accessibilityConnectionId, IAccessibilityInputMethodSession session, @UserIdInt int userId) { synchronized (ImfLock.class) { + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId); @@ -5698,8 +5647,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mCurClient.mAccessibilitySessions); final InputBindResult res = new InputBindResult( InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION, - imeSession, accessibilityInputMethodSessions, null, getCurIdLocked(), - getSequenceNumberLocked(), false); + imeSession, accessibilityInputMethodSessions, /* channel= */ null, + userData.mBindingController.getCurId(), + userData.mBindingController.getSequenceNumber(), + /* isInputMethodSuppressingSpellChecker= */ false); mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId); } } @@ -5709,6 +5660,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, @UserIdInt int userId) { synchronized (ImfLock.class) { + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { if (DEBUG) { @@ -5718,7 +5670,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // A11yManagerService unbinds the disabled accessibility service. We don't need // to do it here. mCurClient.mClient.onUnbindAccessibilityService( - getSequenceNumberLocked(), + userData.mBindingController.getSequenceNumber(), accessibilityConnectionId); } // We only have sessions when we bound to an input method. Remove this session @@ -5928,13 +5880,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> { p.println(" " + c + ":"); p.println(" client=" + c.mClient); - p.println(" fallbackInputConnection=" + c.mFallbackInputConnection); p.println(" sessionRequested=" + c.mSessionRequested); - p.println( - " sessionRequestedForAccessibility=" + p.println(" sessionRequestedForAccessibility=" + c.mSessionRequestedForAccessibility); p.println(" curSession=" + c.mCurSession); p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId); @@ -5942,20 +5892,35 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. p.println(" pid=" + c.mPid); }; mClientController.forAllClients(clientControllerDump); + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); p.println(" mCurrentUserId=" + mCurrentUserId); p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); client = mCurClient; - p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); + p.println(" mCurClient=" + client + " mCurSeq=" + + userData.mBindingController.getSequenceNumber()); p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); - mImeBindingState.dump(" ", p); - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked() + mImeBindingState.dump(/* prefix= */ " ", p); + + p.println(" mCurId=" + userData.mBindingController.getCurId() + + " mHaveConnection=" + userData.mBindingController.hasMainConnection() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + userData.mBindingController.isVisibleBound()); + + p.println(" mUserDataRepository="); + // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. + @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> userDataDump = + u -> { + p.println(" mUserId=" + u.mUserId); + p.println(" hasMainConnection=" + + u.mBindingController.hasMainConnection()); + p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound()); + }; + mUserDataRepository.forAllUserData(userDataDump); + p.println(" mCurToken=" + getCurTokenLocked()); p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId); - p.println(" mCurHostInputToken=" + mCurHostInputToken); - p.println(" mCurIntent=" + getCurIntentLocked()); + p.println(" mCurHostInputToken=" + mAutofillController.getCurHostInputToken()); + p.println(" mCurIntent=" + userData.mBindingController.getCurIntent()); method = getCurMethodLocked(); p.println(" mCurMethod=" + getCurMethodLocked()); p.println(" mEnabledSession=" + mEnabledSession); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java index a8e5e2ef4f72..f06643df7491 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 equals(@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 825cfcbdf505..2b19d3e0a2ea 100644 --- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java +++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java @@ -96,5 +96,10 @@ final class UserDataRepository { mUserId = userId; mBindingController = bindingController; } + + @Override + public String toString() { + return "UserData{" + "mUserId=" + mUserId + '}'; + } } } diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java index 0049213cbf55..d932bd4e6d20 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java +++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java @@ -32,6 +32,7 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.os.Bundle; import android.os.Environment; import android.os.HandlerThread; import android.os.LocaleList; @@ -101,6 +102,11 @@ class LocaleManagerBackupHelper { // the application setting the app-locale itself. private final SharedPreferences mDelegateAppLocalePackages; private final BroadcastReceiver mUserMonitor; + // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving + // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data + // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the + // app is installed. + private final Set<String> mPkgsToRestore; LocaleManagerBackupHelper(LocaleManagerService localeManagerService, PackageManager packageManager, HandlerThread broadcastHandlerThread) { @@ -119,6 +125,7 @@ class LocaleManagerBackupHelper { mStagedData = stagedData; mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages : createPersistedInfo(); + mPkgsToRestore = new ArraySet<>(); mUserMonitor = new UserMonitor(); IntentFilter filter = new IntentFilter(); @@ -251,6 +258,9 @@ class LocaleManagerBackupHelper { LocalesInfo localesInfo = pkgStates.get(pkgName); // Check if the application is already installed for the concerned user. if (isPackageInstalledForUser(pkgName, userId)) { + if (mPkgsToRestore != null) { + mPkgsToRestore.remove(pkgName); + } // Don't apply the restore if the locales have already been set for the app. checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId); } else { @@ -279,23 +289,18 @@ class LocaleManagerBackupHelper { /** * <p><b>Note:</b> This is invoked by service's common monitor - * {@link LocaleManagerServicePackageMonitor#onPackageAdded} when a new package is + * {@link LocaleManagerServicePackageMonitor#onPackageAddedWithExtras} when a new package is * added on device. */ - void onPackageAdded(String packageName, int uid) { - try { - synchronized (mStagedDataLock) { - cleanStagedDataForOldEntriesLocked(); - - int userId = UserHandle.getUserId(uid); - if (mStagedData.contains(userId)) { - // Perform lazy restore only if the staged data exists. - doLazyRestoreLocked(packageName, userId); - } + void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) { + boolean archived = false; + if (extras != null) { + archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false); + if (archived && mPkgsToRestore != null) { + mPkgsToRestore.add(packageName); } - } catch (Exception e) { - Slog.e(TAG, "Exception in onPackageAdded.", e); } + checkStageDataAndApplyRestore(packageName, uid); } /** @@ -305,6 +310,10 @@ class LocaleManagerBackupHelper { */ void onPackageUpdateFinished(String packageName, int uid) { int userId = UserHandle.getUserId(uid); + if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) { + mPkgsToRestore.remove(packageName); + checkStageDataAndApplyRestore(packageName, uid); + } cleanApplicationLocalesIfNeeded(packageName, userId); } @@ -338,6 +347,25 @@ class LocaleManagerBackupHelper { } } + private void checkStageDataAndApplyRestore(String packageName, int uid) { + try { + synchronized (mStagedDataLock) { + cleanStagedDataForOldEntriesLocked(); + + int userId = UserHandle.getUserId(uid); + if (mStagedData.contains(userId)) { + if (mPkgsToRestore != null) { + mPkgsToRestore.remove(packageName); + } + // Perform lazy restore only if the staged data exists. + doLazyRestoreLocked(packageName, userId); + } + } + } catch (Exception e) { + Slog.e(TAG, "Exception in onPackageAdded.", e); + } + } + private boolean isPackageInstalledForUser(String packageName, int userId) { PackageInfo pkgInfo = null; try { diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java index ecd3614b4ff4..e0a050fbf157 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java +++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java @@ -17,6 +17,7 @@ package com.android.server.locales; import android.annotation.NonNull; +import android.os.Bundle; import android.os.UserHandle; import com.android.internal.content.PackageMonitor; @@ -48,8 +49,8 @@ final class LocaleManagerServicePackageMonitor extends PackageMonitor { } @Override - public void onPackageAdded(String packageName, int uid) { - mBackupHelper.onPackageAdded(packageName, uid); + public void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) { + mBackupHelper.onPackageAddedWithExtras(packageName, uid, extras); } @Override diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index d6d134d892f5..17f8abe14ea0 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -333,7 +333,12 @@ public class ContextHubService extends IContextHubService.Stub { return false; } - if (didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) { + if (Flags.reliableMessageDuplicateDetectionService() + && didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) { + Log.i(TAG, "[TEST MODE] Duplicating message (" + + NUM_MESSAGES_TO_DUPLICATE + + " sends) with message sequence number: " + + message.getMessageSequenceNumber()); for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) { handleClientMessageCallback(contextHubId, hostEndpointId, message, nanoappPermissions, messagePermissions); diff --git a/services/core/java/com/android/server/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/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/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 73647dbbe978..e1f893953d66 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -2800,6 +2800,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/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/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index bf49671e2d82..a78b3a2e0c61 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -135,7 +135,7 @@ public final class NotificationAttentionHelper { private LogicalLight mAttentionLight; private final boolean mUseAttentionLight; - boolean mHasLight = true; + boolean mHasLight; private final SettingsObserver mSettingsObserver; @@ -149,7 +149,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; @@ -305,6 +305,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 +881,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; @@ -1721,8 +1731,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 42ec1c3ad4ed..44e76941c09b 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -593,6 +593,8 @@ public class NotificationManagerService extends SystemService { static final long NOTIFICATION_TTL = Duration.ofDays(3).toMillis(); + static final long NOTIFICATION_MAX_AGE_AT_POST = Duration.ofDays(14).toMillis(); + private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; @@ -2637,27 +2639,48 @@ public class NotificationManagerService extends SystemService { * Cleanup broadcast receivers change listeners. */ public void onDestroy() { - getContext().unregisterReceiver(mIntentReceiver); - getContext().unregisterReceiver(mPackageIntentReceiver); + if (mIntentReceiver != null) { + getContext().unregisterReceiver(mIntentReceiver); + } + if (mPackageIntentReceiver != null) { + getContext().unregisterReceiver(mPackageIntentReceiver); + } if (Flags.allNotifsNeedTtl()) { - mTtlHelper.destroy(); + if (mTtlHelper != null) { + mTtlHelper.destroy(); + } } else { - getContext().unregisterReceiver(mNotificationTimeoutReceiver); + if (mNotificationTimeoutReceiver != null) { + getContext().unregisterReceiver(mNotificationTimeoutReceiver); + } + } + if (mRestoreReceiver != null) { + getContext().unregisterReceiver(mRestoreReceiver); + } + if (mLocaleChangeReceiver != null) { + getContext().unregisterReceiver(mLocaleChangeReceiver); + } + if (mSettingsObserver != null) { + mSettingsObserver.destroy(); + } + if (mRoleObserver != null) { + mRoleObserver.destroy(); } - getContext().unregisterReceiver(mRestoreReceiver); - getContext().unregisterReceiver(mLocaleChangeReceiver); - - mSettingsObserver.destroy(); - mRoleObserver.destroy(); if (mShortcutHelper != null) { mShortcutHelper.destroy(); } - mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES); - mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); - mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); - mStatsManager.clearPullAtomCallback(DND_MODE_RULE); - mAppOps.stopWatchingMode(mAppOpsListener); - mAlarmManager.cancelAll(); + if (mStatsManager != null) { + mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES); + mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); + mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); + mStatsManager.clearPullAtomCallback(DND_MODE_RULE); + } + if (mAppOps != null) { + mAppOps.stopWatchingMode(mAppOpsListener); + } + if (mAlarmManager != null) { + mAlarmManager.cancelAll(); + } } protected String[] getStringArrayResource(int key) { @@ -5809,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 @@ -7202,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 " @@ -7296,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 @@ -7722,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; @@ -7850,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; } @@ -7861,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( @@ -8016,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; } @@ -11271,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/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 9dcca494ca24..bf6b6521c19a 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -135,3 +135,10 @@ flag { description: "This flag controls which signal is used to handle a user switch system event" bug: "337077643" } + +flag { + name: "reject_old_notifications" + namespace: "systemui" + description: "This flag does not allow notifications older than 2 weeks old to be posted" + bug: "339833083" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/ondeviceintelligence/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..b2e861cf2876 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -16,6 +16,10 @@ package com.android.server.ondeviceintelligence; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY; + import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams; import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly; import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams; @@ -29,6 +33,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 +46,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 +65,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 +84,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 @@ -105,12 +116,20 @@ public class OnDeviceIntelligenceManagerService extends SystemService { /** Handler message to {@link #resetTemporaryServices()} */ private static final int MSG_RESET_TEMPORARY_SERVICE = 0; + /** Handler message to clean up temporary broadcast keys. */ + private static final int MSG_RESET_BROADCAST_KEYS = 1; + /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_SERVICE_ENABLED = true; private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence"; + private static final String SYSTEM_PACKAGE = "android"; + + private final Executor resourceClosingExecutor = Executors.newCachedThreadPool(); private final Executor callbackExecutor = Executors.newCachedThreadPool(); + private final Executor broadcastExecutor = Executors.newCachedThreadPool(); + private final Context mContext; protected final Object mLock = new Object(); @@ -122,12 +141,17 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @GuardedBy("mLock") private String[] mTemporaryServiceNames; + @GuardedBy("mLock") + private String[] mTemporaryBroadcastKeys; + @GuardedBy("mLock") + private String mBroadcastPackageName; /** * Handler used to reset the temporary service names. */ - @GuardedBy("mLock") private Handler mTemporaryHandler; + private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper()); + public OnDeviceIntelligenceManagerService(Context context) { super(context); @@ -187,8 +211,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 +240,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 +278,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 +322,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 +365,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 +405,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 +448,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 +493,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 { @@ -482,6 +595,8 @@ public class OnDeviceIntelligenceManagerService extends SystemService { ensureRemoteIntelligenceServiceInitialized(); mRemoteOnDeviceIntelligenceService.run( IOnDeviceIntelligenceService::notifyInferenceServiceConnected); + broadcastExecutor.execute( + () -> registerModelLoadingBroadcasts(service)); service.registerRemoteStorageService( getIRemoteStorageService()); } catch (RemoteException ex) { @@ -493,6 +608,56 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } } + private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) { + String[] modelBroadcastKeys; + try { + modelBroadcastKeys = getBroadcastKeys(); + } catch (Resources.NotFoundException e) { + Slog.d(TAG, "Skipping model broadcasts as broadcast intents configured."); + return; + } + + Bundle bundle = new Bundle(); + bundle.putBoolean(REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY, true); + try { + service.updateProcessingState(bundle, new IProcessingUpdateStatusCallback.Stub() { + @Override + public void onSuccess(PersistableBundle statusParams) { + Binder.clearCallingIdentity(); + synchronized (mLock) { + if (statusParams.containsKey(MODEL_LOADED_BUNDLE_KEY)) { + String modelLoadedBroadcastKey = modelBroadcastKeys[0]; + if (modelLoadedBroadcastKey != null + && !modelLoadedBroadcastKey.isEmpty()) { + final Intent intent = new Intent(modelLoadedBroadcastKey); + intent.setPackage(mBroadcastPackageName); + mContext.sendBroadcast(intent, + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE); + } + } else if (statusParams.containsKey(MODEL_UNLOADED_BUNDLE_KEY)) { + String modelUnloadedBroadcastKey = modelBroadcastKeys[1]; + if (modelUnloadedBroadcastKey != null + && !modelUnloadedBroadcastKey.isEmpty()) { + final Intent intent = new Intent(modelUnloadedBroadcastKey); + intent.setPackage(mBroadcastPackageName); + mContext.sendBroadcast(intent, + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE); + } + } + } + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + Slog.e(TAG, "Failed to register model loading callback with status code", + new OnDeviceIntelligenceException(errorCode, errorMessage)); + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register model loading callback with status code", e); + } + } + @NonNull private IRemoteStorageService.Stub getIRemoteStorageService() { return new IRemoteStorageService.Stub() { @@ -629,6 +794,20 @@ public class OnDeviceIntelligenceManagerService extends SystemService { R.string.config_defaultOnDeviceSandboxedInferenceService)}; } + protected String[] getBroadcastKeys() throws Resources.NotFoundException { + // TODO 329240495 : Consider a small class with explicit field names for the two services + synchronized (mLock) { + if (mTemporaryBroadcastKeys != null && mTemporaryBroadcastKeys.length == 2) { + return mTemporaryBroadcastKeys; + } + } + + return new String[]{mContext.getResources().getString( + R.string.config_onDeviceIntelligenceModelLoadedBroadcastKey), + mContext.getResources().getString( + R.string.config_onDeviceIntelligenceModelUnloadedBroadcastKey)}; + } + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) { Objects.requireNonNull(componentNames); @@ -645,25 +824,26 @@ public class OnDeviceIntelligenceManagerService extends SystemService { mRemoteOnDeviceIntelligenceService.unbind(); mRemoteOnDeviceIntelligenceService = null; } - if (mTemporaryHandler == null) { - mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { - synchronized (mLock) { - resetTemporaryServices(); - } - } else { - Slog.wtf(TAG, "invalid handler msg: " + msg); - } - } - }; - } else { - mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE); + + if (durationMs != -1) { + getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, + durationMs); } + } + } + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void setModelBroadcastKeys(@NonNull String[] broadcastKeys, String receiverPackageName, + int durationMs) { + Objects.requireNonNull(broadcastKeys); + enforceShellOnly(Binder.getCallingUid(), "setModelBroadcastKeys"); + mContext.enforceCallingPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + synchronized (mLock) { + mTemporaryBroadcastKeys = broadcastKeys; + mBroadcastPackageName = receiverPackageName; if (durationMs != -1) { - mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs); + getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_BROADCAST_KEYS, durationMs); } } } @@ -751,4 +931,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) { + if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { + synchronized (mLock) { + resetTemporaryServices(); + } + } else if (msg.what == MSG_RESET_BROADCAST_KEYS) { + synchronized (mLock) { + mTemporaryBroadcastKeys = null; + mBroadcastPackageName = SYSTEM_PACKAGE; + } + } else { + Slog.wtf(TAG, "invalid handler msg: " + msg); + } + } + }; + } + + return mTemporaryHandler; + } + + 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..5744b5c3c2c4 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java @@ -43,6 +43,8 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { return setTemporaryServices(); case "get-services": return getConfiguredServices(); + case "set-model-broadcasts": + return setBroadcastKeys(); default: return handleDefaultCommands(cmd); } @@ -62,12 +64,18 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { pw.println(" To reset, call without any arguments."); pw.println(" get-services To get the names of services that are currently being used."); + pw.println( + " set-model-broadcasts [ModelLoadedBroadcastKey] [ModelUnloadedBroadcastKey] " + + "[ReceiverPackageName] " + + "[DURATION] To set the names of broadcast intent keys that are to be " + + "emitted for cts tests."); } private int setTemporaryServices() { final PrintWriter out = getOutPrintWriter(); final String intelligenceServiceName = getNextArg(); final String inferenceServiceName = getNextArg(); + if (getRemainingArgsCount() == 0 && intelligenceServiceName == null && inferenceServiceName == null) { mService.resetTemporaryServices(); @@ -79,7 +87,8 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { Objects.requireNonNull(inferenceServiceName); final int duration = Integer.parseInt(getNextArgRequired()); mService.setTemporaryServices( - new String[]{intelligenceServiceName, inferenceServiceName}, duration); + new String[]{intelligenceServiceName, inferenceServiceName}, + duration); out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName + " for " + duration + "ms"); @@ -93,4 +102,22 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]); return 0; } + + private int setBroadcastKeys() { + final PrintWriter out = getOutPrintWriter(); + final String modelLoadedKey = getNextArgRequired(); + final String modelUnloadedKey = getNextArgRequired(); + final String receiverPackageName = getNextArg(); + + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setModelBroadcastKeys( + new String[]{modelLoadedKey, modelUnloadedKey}, receiverPackageName, duration); + out.println("OnDeviceIntelligence Model Loading broadcast keys temporarily set to " + + modelLoadedKey + + " \n and \n OnDeviceTrustedInferenceService set to " + modelUnloadedKey + + "\n and Package name set to : " + receiverPackageName + + " for " + duration + "ms"); + return 0; + } + }
\ No newline at end of file diff --git a/services/core/java/com/android/server/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/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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 19a0ba796343..472f228b689f 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,6 +505,7 @@ 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 @@ -523,7 +524,7 @@ final class InstallPackageHelper { } } 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.setAppMetadataSource(APP_METADATA_SOURCE_APK); @@ -985,13 +986,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 +2591,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( diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ae485ede1bec..121cf3f231b0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -626,7 +626,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 +1692,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*/); 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 b369f03d002f..23ae98325cb9 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. @@ -1668,11 +1672,11 @@ public class PackageManagerServiceUtils { return true; } Map<String, Property> properties = pkg.getProperties(); - if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) { + if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) { return false; } - Property fileInAPkPathProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL_PATH); - if (!fileInAPkPathProperty.isString()) { + Property fileInApkProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL); + if (!fileInApkProperty.isResourceId()) { return false; } if (isSystem && !appMetadataFile.getParentFile().exists()) { @@ -1684,28 +1688,46 @@ public class PackageManagerServiceUtils { 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/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/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 3a0f7fb4b432..02bd3cca5eb4 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -376,11 +376,14 @@ final class RemovePackageHelper { @NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) { String packageName = deletedPs.getPackageName(); if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs); + final boolean shouldDeletePackageSetting = + shouldDeletePackageSetting(deletedPs, targetUserId, allUserHandles, flags); // Retrieve object to delete permissions for shared user later on final AndroidPackage deletedPkg = deletedPs.getPkg(); // Delete all the data and states related to this package. - clearPackageStateForUserLIF(deletedPs, targetUserId, flags); + clearPackageStateForUserLIF(deletedPs, + shouldDeletePackageSetting ? UserHandle.USER_ALL : targetUserId, flags); // Delete from mPackages removePackageLI(packageName, (flags & PackageManager.DELETE_CHATTY) != 0); @@ -392,7 +395,7 @@ final class RemovePackageHelper { deletedPs.setPkg(null); } - if (shouldDeletePackageSetting(deletedPs, targetUserId, allUserHandles, flags)) { + if (shouldDeletePackageSetting) { // Delete from mSettings final SparseBooleanArray changedUsers = new SparseBooleanArray(); synchronized (mPm.mLock) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 1309e44af6d3..41d6288d4411 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2139,10 +2139,17 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile continue; } + ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString( + originalComponentName); + if (unflattenOriginalComponentName == null) { + Slog.d(TAG, "Incorrect component name from the attributes"); + continue; + } + activityInfos.add( new ArchiveState.ArchiveActivityInfo( title, - ComponentName.unflattenFromString(originalComponentName), + unflattenOriginalComponentName, iconPath, monochromeIconPath)); } diff --git a/services/core/java/com/android/server/pm/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 f6487ceea43b..4ff345fbedf9 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<>(); @@ -1371,7 +1360,7 @@ public class UserManagerService extends IUserManager.Stub { for (int i = 0; i < userSize; i++) { UserInfo ui = mUsers.valueAt(i).info; if ((excludePartial && ui.partial) - || (excludeDying && isDyingLU(ui)) + || (excludeDying && mRemovingUserIds.get(ui.id)) || (excludePreCreated && ui.preCreated)) { continue; } @@ -1381,17 +1370,6 @@ public class UserManagerService extends IUserManager.Stub { } } - @GuardedBy("mUsersLock") - private boolean isDyingLU(UserInfo ui) { - if (mRemovingUserIds.get(ui.id)) { - return true; - } - if (ui.isEphemeral() && ui.isInitialized() && ui.id != getCurrentUserId()) { - return true; - } - return false; - } - @Override public List<UserInfo> getProfiles(@UserIdInt int userId, boolean enabledOnly) { boolean returnFullInfo; @@ -4234,6 +4212,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)) { 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/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/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 49c4000d7308..9a4155122402 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -463,11 +463,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 +484,7 @@ public class BatteryStatsImpl extends BatteryStats { flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG; } mFlags = flags; + mDefaultPowerStatsThrottlePeriod = builder.mDefaultPowerStatsThrottlePeriod; mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods; } @@ -485,7 +492,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 +501,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 +521,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 +565,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 +1607,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; @@ -1933,6 +1953,11 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return mBatteryStatsConfig.getPowerStatsThrottlePeriod(powerComponentName); + } + + @Override public PowerStatsUidResolver getUidResolver() { return mPowerStatsUidResolver; } @@ -11167,19 +11192,14 @@ 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); mStartCount++; 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..3b9ad1915478 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,7 +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) { + if (true) { initNetworkStatsManager(); } BackgroundThread.getHandler().post(() -> { @@ -863,7 +859,7 @@ 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) { + if (false) { initNetworkStatsManager(); } @@ -1047,7 +1043,7 @@ public class StatsPullAtomService extends SystemService { */ @NonNull private NetworkStatsManager getNetworkStatsManager() { - if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) { + if (true) { if (mNetworkStatsManager == null) { throw new IllegalStateException("NetworkStatsManager is not ready"); } @@ -4774,7 +4770,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 +4781,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 +4798,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 +5152,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 +5181,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/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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e814f17bedef..2b32a30c31b4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3272,8 +3272,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 +4270,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(); @@ -10862,8 +10872,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 +11121,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/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..08aeedebf77d 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1768,6 +1768,7 @@ class ActivityStarter { if (!avoidMoveToFront() && (mService.mHomeProcess == null || mService.mHomeProcess.mUid != realCallingUid) && (prevTopTask != null && prevTopTask.isActivityTypeHomeOrRecents()) + && !targetTask.isActivityTypeHomeOrRecents() && r.mTransitionController.isTransientHide(targetTask)) { mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY; } @@ -2113,7 +2114,6 @@ class ActivityStarter { if (hostTask == null || targetTask != hostTask) { return EMBEDDING_DISALLOWED_NEW_TASK; } - return taskFragment.isAllowedToEmbedActivity(starting); } @@ -2167,7 +2167,7 @@ class ActivityStarter { // We don't need to start a new activity, and the client said not to do anything // if that is the case, so this is it! And for paranoia, make sure we have // correctly resumed the top activity. - if (!mMovedToFront && mDoResume) { + if (!mMovedToFront && mDoResume && !avoidMoveToFront()) { ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetRootTask, targetTaskTop); mTargetRootTask.moveToFront("intentActivityFound"); @@ -2196,7 +2196,7 @@ class ActivityStarter { if (mMovedToFront) { // We moved the task to front, use starting window to hide initial drawn delay. targetTaskTop.showStartingWindow(true /* taskSwitch */); - } else if (mDoResume) { + } else if (mDoResume && !avoidMoveToFront()) { // Make sure the root task and its belonging display are moved to topmost. mTargetRootTask.moveToFront("intentActivityFound"); } @@ -2961,23 +2961,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 +2981,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 5e95a4b79d00..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; @@ -7400,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/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 eb1f3b402364..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; @@ -105,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 @@ -303,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; @@ -1695,6 +1701,7 @@ public class BackgroundActivityStartController { return false; } if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts() + && state.mResultForRealCaller != null && state.mResultForRealCaller.getRawCode() == BAL_ALLOW_VISIBLE_WINDOW) { return false; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 414724963c76..5079ec18bb0b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -550,15 +550,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // TODO(multi-display): remove some of the usages. boolean isDefaultDisplay; - /** Detect user tapping outside of current focused task bounds .*/ - // TODO(b/315321016): Remove once pointer event detection is removed from WM. - @VisibleForTesting - final TaskTapPointerEventListener mTapDetector; - - /** Detect user tapping outside of current focused root task bounds .*/ - // TODO(b/315321016): Remove once pointer event detection is removed from WM. - private Region mTouchExcludeRegion = new Region(); - /** Save allocating when calculating rects */ private final Rect mTmpRect = new Rect(); private final Rect mTmpRect2 = new Rect(); @@ -571,10 +562,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final PinnedTaskController mPinnedTaskController; - final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>(); - /** A collection of windows that provide tap exclude regions inside of them. */ - final ArraySet<WindowState> mTapExcludeProvidingWindows = new ArraySet<>(); - private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList(); private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult = @@ -1193,18 +1180,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp "PointerEventDispatcher" + mDisplayId, mDisplayId); mPointerEventDispatcher = new PointerEventDispatcher(inputChannel); - if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) { - mTapDetector = null; - } else { - // Tap Listeners are supported for: - // 1. All physical displays (multi-display). - // 2. VirtualDisplays on VR, AA (and everything else). - mTapDetector = new TaskTapPointerEventListener(mWmService, this); - registerPointerEventListener(mTapDetector); - } - if (mWmService.mMousePositionTracker != null) { - registerPointerEventListener(mWmService.mMousePositionTracker); - } if (mWmService.mAtmService.getRecentTasks() != null) { registerPointerEventListener( mWmService.mAtmService.getRecentTasks().getInputListener()); @@ -2780,7 +2755,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Nullable Task getTopRootTask() { - return getRootTask(t -> true); + return getRootTask(alwaysTruePredicate()); } /** @@ -3304,117 +3279,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mTmpTaskForResizePointSearchResult.process(taskDisplayArea, x, y, delta)); } - void updateTouchExcludeRegion() { - if (mTapDetector == null) { - // The touch exclude region is used to detect the region outside of the focused task - // so that the tap detector can detect outside touches. Don't calculate the exclude - // region when the tap detector is disabled. - return; - } - final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null); - if (focusedTask == null) { - mTouchExcludeRegion.setEmpty(); - } else { - mTouchExcludeRegion.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); - final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); - mTmpRect.setEmpty(); - mTmpRect2.setEmpty(); - - forAllTasks(t -> { processTaskForTouchExcludeRegion(t, focusedTask, delta); }); - - // If we removed the focused task above, add it back and only leave its - // outside touch area in the exclusion. TapDetector is not interested in - // any touch inside the focused task itself. - if (!mTmpRect2.isEmpty()) { - mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION); - } - } - if (mInputMethodWindow != null && mInputMethodWindow.isVisible()) { - // If the input method is visible and the user is typing, we don't want these touch - // events to be intercepted and used to change focus. This would likely cause a - // disappearance of the input method. - mInputMethodWindow.getTouchableRegion(mTmpRegion); - mTouchExcludeRegion.op(mTmpRegion, Op.UNION); - } - for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) { - final WindowState win = mTapExcludedWindows.get(i); - if (!win.isVisible()) { - continue; - } - win.getTouchableRegion(mTmpRegion); - mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION); - } - amendWindowTapExcludeRegion(mTouchExcludeRegion); - mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion); - } - - private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) { - if (mTapDetector == null) { - // The touch exclude region is used to detect the region outside of the focused task - // so that the tap detector can detect outside touches. Don't calculate the exclude - // region when the tap detector is disabled. - } - final ActivityRecord topVisibleActivity = task.getTopVisibleActivity(); - - if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) { - return; - } - - // Exclusion region is the region that TapDetector doesn't care about. - // Here we want to remove all non-focused tasks from the exclusion region. - // We also remove the outside touch area for resizing for all freeform - // tasks (including the focused). - // We save the focused task region once we find it, and add it back at the end. - // If the task is root home task and it is resizable and visible (top of its root task), - // we want to exclude the root docked task from touch so we need the entire screen area - // and not just a small portion which the root home task currently is resized to. - if (task.isActivityTypeHome() && task.isVisible() && task.isResizeable()) { - task.getDisplayArea().getBounds(mTmpRect); - } else { - task.getDimBounds(mTmpRect); - } - - if (task == focusedTask) { - // Add the focused task rect back into the exclude region once we are done - // processing root tasks. - // NOTE: this *looks* like a no-op, but this usage of mTmpRect2 is expected by - // updateTouchExcludeRegion. - mTmpRect2.set(mTmpRect); - } - - final boolean isFreeformed = task.inFreeformWindowingMode(); - if (task != focusedTask || isFreeformed) { - if (isFreeformed) { - // If the task is freeformed, enlarge the area to account for outside - // touch area for resize. - mTmpRect.inset(-delta, -delta); - // Intersect with display content frame. If we have system decor (status bar/ - // navigation bar), we want to exclude that from the tap detection. - // Otherwise, if the app is partially placed under some system button (eg. - // Recents, Home), pressing that button would cause a full series of - // unwanted transfer focus/resume/pause, before we could go home. - mTmpRect.inset(getInsetsStateController().getRawInsetsState().calculateInsets( - mTmpRect, systemBars() | ime(), false /* ignoreVisibility */)); - } - mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE); - } - } - - /** - * Union the region with all the tap exclude region provided by windows on this display. - * - * @param inOutRegion The region to be amended. - */ - private void amendWindowTapExcludeRegion(Region inOutRegion) { - final Region region = Region.obtain(); - for (int i = mTapExcludeProvidingWindows.size() - 1; i >= 0; i--) { - final WindowState win = mTapExcludeProvidingWindows.valueAt(i); - win.getTapExcludeRegion(region); - inOutRegion.op(region, Op.UNION); - } - region.recycle(); - } - @Override void switchUser(int userId) { super.switchUser(userId); @@ -3771,7 +3635,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight); pw.print(subPrefix + "deferred=" + mDeferredRemoval + " mLayoutNeeded=" + mLayoutNeeded); - pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion); pw.println(); super.dump(pw, prefix, dumpAll); @@ -4120,7 +3983,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } getInputMonitor().setFocusedAppLw(newFocus); - updateTouchExcludeRegion(); return true; } @@ -5147,7 +5009,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/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 119fafde6f77..ae6e72464555 100644 --- a/services/core/java/com/android/server/wm/InputConfigAdapter.java +++ b/services/core/java/com/android/server/wm/InputConfigAdapter.java @@ -20,8 +20,6 @@ import android.os.InputConfig; import android.view.InputWindowHandle.InputConfigFlags; import android.view.WindowManager.LayoutParams; -import java.util.List; - /** * A helper to determine the {@link InputConfigFlags} that control the behavior of an input window * from several WM attributes. @@ -47,7 +45,7 @@ class InputConfigAdapter { * input configurations that can be mapped directly from a corresponding LayoutParams input * feature. */ - private static final List<FlagMapping> INPUT_FEATURE_TO_CONFIG_MAP = List.of( + private static final FlagMapping[] INPUT_FEATURE_TO_CONFIG_MAP = { new FlagMapping( LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL, InputConfig.NO_INPUT_CHANNEL, false /* inverted */), @@ -59,7 +57,8 @@ class InputConfigAdapter { InputConfig.SPY, false /* inverted */), new FlagMapping( LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY, - InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */)); + InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */) + }; @InputConfigFlags private static final int INPUT_FEATURE_TO_CONFIG_MASK = @@ -72,7 +71,7 @@ class InputConfigAdapter { * NOTE: The layout params flag {@link LayoutParams#FLAG_NOT_FOCUSABLE} is not handled by this * adapter, and must be handled explicitly. */ - private static final List<FlagMapping> LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = List.of( + private static final FlagMapping[] LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = { new FlagMapping( LayoutParams.FLAG_NOT_TOUCHABLE, InputConfig.NOT_TOUCHABLE, false /* inverted */), @@ -84,7 +83,8 @@ class InputConfigAdapter { InputConfig.WATCH_OUTSIDE_TOUCH, false /* inverted */), new FlagMapping( LayoutParams.FLAG_SLIPPERY, - InputConfig.SLIPPERY, false /* inverted */)); + InputConfig.SLIPPERY, false /* inverted */) + }; @InputConfigFlags private static final int LAYOUT_PARAM_FLAG_TO_CONFIG_MASK = @@ -119,7 +119,7 @@ class InputConfigAdapter { } @InputConfigFlags - private static int applyMapping(int flags, List<FlagMapping> flagToConfigMap) { + private static int applyMapping(int flags, FlagMapping[] flagToConfigMap) { int inputConfig = 0; for (final FlagMapping mapping : flagToConfigMap) { final boolean flagSet = (flags & mapping.mFlag) != 0; @@ -131,7 +131,7 @@ class InputConfigAdapter { } @InputConfigFlags - private static int computeMask(List<FlagMapping> flagToConfigMap) { + private static int computeMask(FlagMapping[] flagToConfigMap) { int mask = 0; for (final FlagMapping mapping : flagToConfigMap) { mask |= mapping.mInputConfig; diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index a84ebd95cf1c..22ca82a29d59 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -290,22 +290,6 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } } - @Override - public void notifyPointerDisplayIdChanged(int displayId, float x, float y) { - synchronized (mService.mGlobalLock) { - mService.setMousePointerDisplayId(displayId); - if (displayId == Display.INVALID_DISPLAY) return; - - final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); - if (dc == null) { - Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId - + " that does not have a valid DisplayContent."); - return; - } - mService.restorePointerIconLocked(dc, x, y); - } - } - /** Waits until the built-in input devices have been configured. */ public boolean waitForInputDevicesReady(long timeoutMillis) { synchronized (mInputDevicesReadyMonitor) { diff --git a/services/core/java/com/android/server/wm/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 ce1a72deb523..b5af8065726d 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import android.annotation.DimenRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -266,10 +267,10 @@ final class LetterboxConfiguration { private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox; // Supplier for the value in pixel to consider when detecting vertical thin letterboxing - private final IntSupplier mThinLetterboxWidthFn; + private final DimenPxIntSupplier mThinLetterboxWidthPxSupplier; // Supplier for the value in pixel to consider when detecting horizontal thin letterboxing - private final IntSupplier mThinLetterboxHeightFn; + private final DimenPxIntSupplier mThinLetterboxHeightPxSupplier; // Allows to enable letterboxing strategy for translucent activities ignoring flags. private boolean mTranslucentLetterboxingOverrideEnabled; @@ -307,6 +308,34 @@ final class LetterboxConfiguration { // Flags dynamically updated with {@link android.provider.DeviceConfig}. @NonNull private final SynchedDeviceConfig mDeviceConfig; + // Cached version of IntSupplier customised to evaluate new dimen in pixels + // when density changes + private static class DimenPxIntSupplier implements IntSupplier { + + @NonNull + private final Context mContext; + + private final int mResourceId; + + private float mLastDensity = Float.MIN_VALUE; + private int mValue = 0; + + private DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) { + mContext = context; + mResourceId = resourceId; + } + + @Override + public int getAsInt() { + final float newDensity = mContext.getResources().getDisplayMetrics().density; + if (newDensity != mLastDensity) { + mLastDensity = newDensity; + mValue = mContext.getResources().getDimensionPixelSize(mResourceId); + } + return mValue; + } + } + LetterboxConfiguration(@NonNull final Context systemUiContext) { this(systemUiContext, new LetterboxConfigurationPersister( () -> readLetterboxHorizontalReachabilityPositionFromConfig( @@ -364,9 +393,10 @@ final class LetterboxConfiguration { R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled); mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled); - mThinLetterboxWidthFn = () -> mContext.getResources().getDimensionPixelSize( + + mThinLetterboxWidthPxSupplier = new DimenPxIntSupplier(mContext, R.dimen.config_letterboxThinLetterboxWidthDp); - mThinLetterboxHeightFn = () -> mContext.getResources().getDimensionPixelSize( + mThinLetterboxHeightPxSupplier = new DimenPxIntSupplier(mContext, R.dimen.config_letterboxThinLetterboxHeightDp); mLetterboxConfigurationPersister = letterboxConfigurationPersister; @@ -1144,7 +1174,7 @@ final class LetterboxConfiguration { * is the maximum value for (W - w) / 2 to be considered for a thin letterboxed app. */ int getThinLetterboxWidthPx() { - return mThinLetterboxWidthFn.getAsInt(); + return mThinLetterboxWidthPxSupplier.getAsInt(); } /** @@ -1153,7 +1183,7 @@ final class LetterboxConfiguration { * value for (H - h) / 2 to be considered for a thin letterboxed app. */ int getThinLetterboxHeightPx() { - return mThinLetterboxHeightFn.getAsInt(); + return mThinLetterboxHeightPxSupplier.getAsInt(); } /** diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 9e16b8abe0de..57827c567b5b 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -989,6 +989,10 @@ final class LetterboxUiController { } } + boolean isLetterboxEducationEnabled() { + return mLetterboxConfiguration.getIsEducationEnabled(); + } + /** * Whether we use split screen aspect ratio for the activity when camera compat treatment * is active because the corresponding config is enabled and activity supports resizing. diff --git a/services/core/java/com/android/server/wm/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 6dec71260fa7..72f592b577da 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -64,7 +64,6 @@ import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 6003c1b897b4..be8c2aea7ad1 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -911,7 +911,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> dc.getInputMonitor().updateInputWindowsLw(true /*force*/); dc.updateSystemGestureExclusion(); dc.updateKeepClearAreas(); - dc.updateTouchExcludeRegion(); }); // Check to see if we are now in a state where the screen should diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index bb86460572af..3b3eeb496ab7 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -740,16 +740,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public void updatePointerIcon(IWindow window) { - final long identity = Binder.clearCallingIdentity(); - try { - mService.updatePointerIcon(window); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override public void updateTapExcludeRegion(IWindow window, Region region) { final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/wm/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..42ca7b44287e 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); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8bd7b5f78cf4..a555388ab233 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -297,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. @@ -3448,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() @@ -4986,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(); @@ -5000,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 diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java index 21e7a8d63773..586f3c35c0c4 100644 --- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java @@ -247,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); @@ -485,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); 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 6a7f60b3447d..a444c96eea97 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -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. */ @@ -515,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 @@ -532,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; } @@ -564,7 +592,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { } void setResumedActivity(ActivityRecord r, String reason) { - warnForNonLeafTaskFragment("setResumedActivity"); if (mResumedActivity == r) { return; } @@ -850,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) { @@ -935,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"); } @@ -965,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", @@ -2895,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/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/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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index dbe3d369db7d..b6035519fdba 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -81,15 +81,11 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; -import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_NONE; @@ -203,7 +199,6 @@ import android.hardware.configstore.V1_0.OptionalBool; import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; -import android.hardware.input.InputManager; import android.hardware.input.InputSettings; import android.net.Uri; import android.os.Binder; @@ -289,8 +284,6 @@ import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.KeyEvent; import android.view.MagnificationSpec; -import android.view.MotionEvent; -import android.view.PointerIcon; import android.view.RemoteAnimationAdapter; import android.view.ScrollCaptureResponse; import android.view.Surface; @@ -335,6 +328,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; +import com.android.internal.os.TransferPipe; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardLockedStateListener; import com.android.internal.policy.IShortcutService; @@ -545,13 +539,16 @@ public class WindowManagerService extends IWindowManager.Stub if (asProto) { return; } + + final long timeoutMs = 1000L; mAtmService.dumpActivity(fd, pw, /* name= */ "all", /* args= */ new String[]{}, /* opti= */ 0, /* dumpAll= */ true, /* dumpVisibleRootTasksOnly= */ true, /* dumpFocusedRootTaskOnly= */ false, INVALID_DISPLAY, UserHandle.USER_ALL, - /* timeout= */ 1000 + timeoutMs ); + dumpVisibleWindowClients(fd, pw, timeoutMs); } @Override @@ -1523,18 +1520,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - static boolean excludeWindowTypeFromTapOutTask(int windowType) { - switch (windowType) { - case TYPE_STATUS_BAR: - case TYPE_NOTIFICATION_SHADE: - case TYPE_NAVIGATION_BAR: - case TYPE_INPUT_METHOD_DIALOG: - case TYPE_VOLUME_OVERLAY: - return true; - } - return false; - } - public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility, int displayId, int requestUserId, @InsetsType int requestedVisibleTypes, InputChannel outInputChannel, InsetsState outInsetsState, @@ -1833,10 +1818,6 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.mWinAddedSinceNullFocus.add(win); } - if (excludeWindowTypeFromTapOutTask(type)) { - displayContent.mTapExcludedWindows.add(win); - } - win.mSession.onWindowAdded(win); mWindowMap.put(client.asBinder(), win); win.initAppOpsState(); @@ -5716,7 +5697,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int UPDATE_ANIMATION_SCALE = 51; public static final int WINDOW_HIDE_TIMEOUT = 52; - public static final int RESTORE_POINTER_ICON = 55; public static final int SET_HAS_OVERLAY_UI = 58; public static final int ANIMATION_FAILSAFE = 60; public static final int RECOMPUTE_FOCUS = 61; @@ -5949,12 +5929,6 @@ public class WindowManagerService extends IWindowManager.Stub } break; } - case RESTORE_POINTER_ICON: { - synchronized (mGlobalLock) { - restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2); - } - break; - } case SET_HAS_OVERLAY_UI: { mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1); break; @@ -7574,144 +7548,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - // The mouse position tracker will be obsolete after the Pointer Icon Refactor. - // TODO(b/293587049): Remove after the refactoring is fully rolled out. - @Nullable - final MousePositionTracker mMousePositionTracker = - com.android.input.flags.Flags.enablePointerChoreographer() ? null - : new MousePositionTracker(); - - private static class MousePositionTracker implements PointerEventListener { - private boolean mLatestEventWasMouse; - private float mLatestMouseX; - private float mLatestMouseY; - - /** - * The display that the pointer (mouse cursor) is currently shown on. This is updated - * directly by InputManagerService when the pointer display changes. - */ - private int mPointerDisplayId = INVALID_DISPLAY; - - /** - * Update the mouse cursor position as a result of a mouse movement. - * @return true if the position was successfully updated, false otherwise. - */ - boolean updatePosition(int displayId, float x, float y) { - synchronized (this) { - mLatestEventWasMouse = true; - - if (displayId != mPointerDisplayId) { - // The display of the position update does not match the display on which the - // mouse pointer is shown, so do not update the position. - return false; - } - mLatestMouseX = x; - mLatestMouseY = y; - return true; - } - } - - void setPointerDisplayId(int displayId) { - synchronized (this) { - mPointerDisplayId = displayId; - } - } - - @Override - public void onPointerEvent(MotionEvent motionEvent) { - if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) { - updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(), - motionEvent.getRawY()); - } else { - synchronized (this) { - mLatestEventWasMouse = false; - } - } - } - }; - - void updatePointerIcon(IWindow client) { - if (mMousePositionTracker == null) { - return; - } - int pointerDisplayId; - float mouseX, mouseY; - - synchronized(mMousePositionTracker) { - if (!mMousePositionTracker.mLatestEventWasMouse) { - return; - } - mouseX = mMousePositionTracker.mLatestMouseX; - mouseY = mMousePositionTracker.mLatestMouseY; - pointerDisplayId = mMousePositionTracker.mPointerDisplayId; - } - - synchronized (mGlobalLock) { - if (mDragDropController.dragDropActiveLocked()) { - // Drag cursor overrides the app cursor. - return; - } - WindowState callingWin = windowForClientLocked(null, client, false); - if (callingWin == null) { - ProtoLog.w(WM_ERROR, "Bad requesting window %s", client); - return; - } - final DisplayContent displayContent = callingWin.getDisplayContent(); - if (displayContent == null) { - return; - } - if (pointerDisplayId != displayContent.getDisplayId()) { - // Do not let the pointer icon be updated by a window on a different display. - return; - } - WindowState windowUnderPointer = - displayContent.getTouchableWinAtPointLocked(mouseX, mouseY); - if (windowUnderPointer != callingWin) { - return; - } - try { - windowUnderPointer.mClient.updatePointerIcon( - windowUnderPointer.translateToWindowX(mouseX), - windowUnderPointer.translateToWindowY(mouseY)); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, "unable to update pointer icon"); - } - } - } - - void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) { - if (mMousePositionTracker == null) { - return; - } - // Mouse position tracker has not been getting updates while dragging, update it now. - if (!mMousePositionTracker.updatePosition( - displayContent.getDisplayId(), latestX, latestY)) { - // The mouse position could not be updated, so ignore this request. - return; - } - - WindowState windowUnderPointer = - displayContent.getTouchableWinAtPointLocked(latestX, latestY); - if (windowUnderPointer != null) { - try { - windowUnderPointer.mClient.updatePointerIcon( - windowUnderPointer.translateToWindowX(latestX), - windowUnderPointer.translateToWindowY(latestY)); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, "unable to restore pointer icon"); - } - } else { - mContext.getSystemService(InputManager.class) - .setPointerIconType(PointerIcon.TYPE_DEFAULT); - } - } - void setMousePointerDisplayId(int displayId) { - if (mMousePositionTracker == null) { - return; - } - mMousePositionTracker.setPointerDisplayId(displayId); - } - /** * Update a tap exclude region in the window identified by the provided id. Touches down on this * region will not: @@ -8466,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 @@ -10350,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 90e7bd7b99e6..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; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e90c845f0d21..8fb83fa0e88c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1447,14 +1447,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame); } + final boolean contentChanged = didFrameInsetsChange || configChanged + || dragResizingChanged || attachedFrameChanged; + // Cancel unchanged non-sync-buffer redraw request to avoid unnecessary reportResized(). + if (!contentChanged && !mRedrawForSyncReported && mPrepareSyncSeqId <= 0 + && mDrawHandlers.isEmpty()) { + mRedrawForSyncReported = true; + } + // Add a window that is using blastSync to the resizing list if it hasn't been reported // already. This because the window is waiting on a finishDrawing from the client. - if (didFrameInsetsChange - || configChanged - || insetsChanged - || dragResizingChanged - || shouldSendRedrawForSync() - || attachedFrameChanged) { + if (contentChanged || insetsChanged || shouldSendRedrawForSync()) { ProtoLog.v(WM_DEBUG_RESIZE, "Resize reasons for w=%s: %s configChanged=%b didFrameInsetsChange=%b", this, mWindowFrames.getInsetsChangedInfo(), @@ -2359,18 +2362,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } final int type = mAttrs.type; - if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) { - dc.mTapExcludedWindows.remove(this); - } if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) { mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(), /*isShown=*/ false); } - // Remove this window from mTapExcludeProvidingWindows. If it was not registered, this will - // not do anything. - dc.mTapExcludeProvidingWindows.remove(this); dc.getDisplayPolicy().removeWindowLw(this); disposeInputChannel(); @@ -5526,18 +5523,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Clear the tap excluded region if the region passed in is null or empty. if (region == null || region.isEmpty()) { mTapExcludeRegion.setEmpty(); - // Remove this window from mTapExcludeProvidingWindows since it won't be providing - // tap exclude regions. - currentDisplay.mTapExcludeProvidingWindows.remove(this); } else { mTapExcludeRegion.set(region); - // Make sure that this window is registered as one that provides a tap exclude region - // for its containing display. - currentDisplay.mTapExcludeProvidingWindows.add(this); } - // Trigger touch exclude region update on current display. - currentDisplay.updateTouchExcludeRegion(); // Trigger touchable region update for this window. currentDisplay.getInputMonitor().updateInputWindowsLw(true /* force */); } @@ -6074,6 +6063,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mPrepareSyncSeqId > 0; } + public boolean isActivityWindow() { + return mActivityRecord != null; + } + void setSecureLocked(boolean isSecure) { ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName()); if (secureWindowState()) { diff --git a/services/core/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..22c0f730ad7d 100644 --- a/services/core/jni/com_android_server_display_DisplayControl.cpp +++ b/services/core/jni/com_android_server_display_DisplayControl.cpp @@ -24,9 +24,11 @@ namespace android { static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure, - jfloat requestedRefreshRate) { - ScopedUtfChars name(env, nameObj); + jstring uniqueIdStr, jfloat requestedRefreshRate) { + const ScopedUtfChars name(env, nameObj); + const ScopedUtfChars uniqueId(env, uniqueIdStr); sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure), + std::string(uniqueId.c_str()), requestedRefreshRate)); return javaObjectForIBinder(env, token); } @@ -178,7 +180,7 @@ static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong ph static const JNINativeMethod sDisplayMethods[] = { // clang-format off - {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;", + {"nativeCreateDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;", (void*)nativeCreateDisplay }, {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V", (void*)nativeDestroyDisplay }, diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 62f5b89e701c..74ca9ad687ea 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -127,7 +127,7 @@ static struct { jmethodID getVirtualKeyQuietTimeMillis; jmethodID getExcludedDeviceNames; jmethodID getInputPortAssociations; - jmethodID getInputUniqueIdAssociations; + jmethodID getInputUniqueIdAssociationsByPort; jmethodID getInputUniqueIdAssociationsByDescriptor; jmethodID getDeviceTypeAssociations; jmethodID getKeyboardLayoutAssociations; @@ -272,22 +272,23 @@ public: void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray); base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name); - base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId, + base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid); status_t removeInputChannel(const sp<IBinder>& connectionToken); status_t pilferPointers(const sp<IBinder>& token); - void displayRemoved(JNIEnv* env, int32_t displayId); - void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj); - void setFocusedDisplay(int32_t displayId); + void displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId); + void setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId, + jobject applicationHandleObj); + void setFocusedDisplay(ui::LogicalDisplayId displayId); void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis); void setInputDispatchMode(bool enabled, bool frozen); void setSystemUiLightsOut(bool lightsOut); - void setPointerDisplayId(int32_t displayId); + void setPointerDisplayId(ui::LogicalDisplayId displayId); int32_t getMousePointerSpeed(); void setPointerSpeed(int32_t speed); - void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled); + void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled); void setTouchpadPointerSpeed(int32_t speed); void setTouchpadNaturalScrollingEnabled(bool enabled); void setTouchpadTapToClickEnabled(bool enabled); @@ -300,13 +301,13 @@ public: void reloadPointerIcons(); void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled); bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, - int32_t displayId, DeviceId deviceId, int32_t pointerId, + ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken); - void setPointerIconVisibility(int32_t displayId, bool visible); + void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible); void setMotionClassifierEnabled(bool enabled); std::optional<std::string> getBluetoothAddress(int32_t deviceId); void setStylusButtonMotionEventsEnabled(bool enabled); - FloatPoint getMouseCursorPosition(int32_t displayId); + FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId); void setStylusPointerIconEnabled(bool enabled); void setInputMethodConnectionIsActive(bool isActive); @@ -325,7 +326,7 @@ public: void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override; bool isInputMethodConnectionActive() override; std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) override; + ui::LogicalDisplayId associatedDisplayId) override; /* --- InputDispatcherPolicyInterface implementation --- */ @@ -348,13 +349,15 @@ public: void notifyVibratorState(int32_t deviceId, bool isOn) override; bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override; void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override; - void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action, - nsecs_t when, uint32_t& policyFlags) override; + void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source, + int32_t action, nsecs_t when, + uint32_t& policyFlags) override; nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent, uint32_t policyFlags) override; std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent, uint32_t policyFlags) override; - void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override; + void pokeUserActivity(nsecs_t eventTime, int32_t eventType, + ui::LogicalDisplayId displayId) override; void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override; void setPointerCapture(const PointerCaptureRequest& request) override; void notifyDropWindow(const sp<IBinder>& token, float x, float y) override; @@ -363,11 +366,13 @@ public: /* --- PointerControllerPolicyInterface implementation --- */ - virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId); - virtual void loadPointerResources(PointerResources* outResources, int32_t displayId); + virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId); + virtual void loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId); virtual void loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, - std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId); + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, + ui::LogicalDisplayId displayId); virtual PointerIconStyle getDefaultPointerIconId(); virtual PointerIconStyle getDefaultStylusIconId(); virtual PointerIconStyle getCustomPointerIconId(); @@ -375,7 +380,8 @@ public: /* --- PointerChoreographerPolicyInterface implementation --- */ std::shared_ptr<PointerControllerInterface> createPointerController( PointerControllerInterface::ControllerType type) override; - void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override; + void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId, + const FloatPoint& position) override; /* --- InputFilterPolicyInterface implementation --- */ void notifyStickyModifierStateChanged(uint32_t modifierState, @@ -399,7 +405,7 @@ private: int32_t pointerSpeed{0}; // Displays on which its associated mice will have pointer acceleration disabled. - std::set<int32_t> displaysWithMousePointerAccelerationDisabled{}; + std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled{}; // True if pointer gestures are enabled. bool pointerGesturesEnabled{true}; @@ -417,7 +423,7 @@ private: std::set<int32_t> disabledInputDevices{}; // Associated Pointer controller display. - int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId pointerDisplayId{ui::LogicalDisplayId::DEFAULT}; // True if stylus button reporting through motion events is enabled. bool stylusButtonMotionEventsEnabled{true}; @@ -450,7 +456,7 @@ private: void updateInactivityTimeoutLocked(); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); void ensureSpriteControllerLocked(); - sp<SurfaceControl> getParentSurfaceForPointers(int displayId); + sp<SurfaceControl> getParentSurfaceForPointers(ui::LogicalDisplayId displayId); static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); template <typename T> std::unordered_map<std::string, T> readMapFromInterleavedJavaArray( @@ -459,7 +465,7 @@ private: void forEachPointerControllerLocked(std::function<void(PointerController&)> apply) REQUIRES(mLock); - PointerIcon loadPointerIcon(JNIEnv* env, int32_t displayId, PointerIconStyle type); + PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type); static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); } }; @@ -490,7 +496,9 @@ void NativeInputManager::dump(std::string& dump) { toString(mLocked.systemUiLightsOut)); dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed); dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n", - dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str()); + dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled, + streamableToString) + .c_str()); dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n", toString(mLocked.pointerGesturesEnabled)); dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n", @@ -552,7 +560,7 @@ base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChann } base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor( - int32_t displayId, const std::string& name, gui::Pid pid) { + ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) { ATRACE_CALL(); return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid); } @@ -616,10 +624,9 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon env->DeleteLocalRef(portAssociations); } - outConfig->uniqueIdAssociationsByPort = - readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo - .getInputUniqueIdAssociations, - "getInputUniqueIdAssociations"); + outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray< + std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort, + "getInputUniqueIdAssociationsByPort"); outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray< std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, @@ -735,7 +742,7 @@ void NativeInputManager::forEachPointerControllerLocked( } } -PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, int32_t displayId, +PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type) { if (type == PointerIconStyle::TYPE_CUSTOM) { LOG(FATAL) << __func__ << ": Cannot load non-system icon type"; @@ -766,7 +773,7 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::createPointerCon return pc; } -void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId, +void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId pointerDisplayId, const FloatPoint& position) { // Notify the Reader so that devices can be reconfigured. { // acquire lock @@ -775,7 +782,7 @@ void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId, return; } mLocked.pointerDisplayId = pointerDisplayId; - ALOGI("%s: pointer displayId set to: %d", __func__, pointerDisplayId); + ALOGI("%s: pointer displayId set to: %s", __func__, pointerDisplayId.toString().c_str()); } // release lock mInputManager->getReader().requestRefreshConfiguration( InputReaderConfiguration::Change::DISPLAY_INFO); @@ -795,7 +802,7 @@ void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged"); } -sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) { +sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) { JNIEnv* env = jniEnv(); jlong nativeSurfaceControlPtr = env->CallLongMethod(mServiceObj, gServiceClassInfo.getParentSurfaceForPointers, @@ -817,9 +824,10 @@ void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) { layer = -1; } mLocked.spriteController = - std::make_shared<SpriteController>(mLooper, layer, [this](int displayId) { - return getParentSurfaceForPointers(displayId); - }); + std::make_shared<SpriteController>(mLooper, layer, + [this](ui::LogicalDisplayId displayId) { + return getParentSurfaceForPointers(displayId); + }); // The SpriteController needs to be shared pointer because the handler callback needs to hold // a weak reference so that we can avoid racy conditions when the controller is being destroyed. mLocked.spriteController->setHandlerController(mLocked.spriteController); @@ -1021,8 +1029,7 @@ void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) { jobject tokenObj = javaObjectForIBinder(env, token); if (tokenObj) { - env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, - tokenObj); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, tokenObj); checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken"); } } @@ -1108,12 +1115,12 @@ void NativeInputManager::notifyVibratorState(int32_t deviceId, bool isOn) { checkAndClearExceptionFromCallback(env, "notifyVibratorState"); } -void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) { +void NativeInputManager::displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId) { mInputManager->getDispatcher().displayRemoved(displayId); } -void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId, - jobject applicationHandleObj) { +void NativeInputManager::setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId, + jobject applicationHandleObj) { if (!applicationHandleObj) { return; } @@ -1123,7 +1130,7 @@ void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId, mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle); } -void NativeInputManager::setFocusedDisplay(int32_t displayId) { +void NativeInputManager::setFocusedDisplay(ui::LogicalDisplayId displayId) { mInputManager->getDispatcher().setFocusedDisplay(displayId); } @@ -1151,7 +1158,7 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { }); } -void NativeInputManager::setPointerDisplayId(int32_t displayId) { +void NativeInputManager::setPointerDisplayId(ui::LogicalDisplayId displayId) { mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId); } @@ -1176,7 +1183,8 @@ void NativeInputManager::setPointerSpeed(int32_t speed) { InputReaderConfiguration::Change::POINTER_SPEED); } -void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) { +void NativeInputManager::setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, + bool enabled) { { // acquire lock std::scoped_lock _l(mLock); @@ -1186,8 +1194,8 @@ void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, b return; } - ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled), - displayId); + ALOGI("Setting mouse pointer acceleration to %s on display %s", toString(enabled), + displayId.toString().c_str()); if (enabled) { mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId); } else { @@ -1326,8 +1334,9 @@ void NativeInputManager::reloadPointerIcons() { } bool NativeInputManager::setPointerIcon( - std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId, - DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken) { + std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, + ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId, + const sp<IBinder>& inputToken) { if (!mInputManager->getDispatcher().isPointerInWindow(inputToken, displayId, deviceId, pointerId)) { LOG(WARNING) << "Attempted to change the pointer icon for deviceId " << deviceId @@ -1339,7 +1348,7 @@ bool NativeInputManager::setPointerIcon( return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId); } -void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) { +void NativeInputManager::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) { mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible); } @@ -1394,7 +1403,7 @@ bool NativeInputManager::isInputMethodConnectionActive() { } std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) { + ui::LogicalDisplayId associatedDisplayId) { return mInputManager->getChoreographer().getViewportForPointerDevice(associatedDisplayId); } @@ -1469,9 +1478,9 @@ void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent& keyEvent, handleInterceptActions(wmActions, when, /*byref*/ policyFlags); } -void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, - int32_t action, nsecs_t when, - uint32_t& policyFlags) { +void NativeInputManager::interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, + uint32_t source, int32_t action, + nsecs_t when, uint32_t& policyFlags) { ATRACE_CALL(); // Policy: // - Ignore untrusted events and pass them along. @@ -1590,7 +1599,8 @@ std::optional<KeyEvent> NativeInputManager::dispatchUnhandledKey(const sp<IBinde return fallbackEvent; } -void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) { +void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, + ui::LogicalDisplayId displayId) { ATRACE_CALL(); android_server_PowerManagerService_userActivity(eventTime, eventType, displayId); } @@ -1621,13 +1631,14 @@ void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) InputReaderConfiguration::Change::POINTER_CAPTURE); } -void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) { +void NativeInputManager::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); *icon = toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_ARROW)); } -void NativeInputManager::loadPointerResources(PointerResources* outResources, int32_t displayId) { +void NativeInputManager::loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1641,7 +1652,8 @@ void NativeInputManager::loadPointerResources(PointerResources* outResources, in void NativeInputManager::loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, - std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId) { + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, + ui::LogicalDisplayId displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1708,7 +1720,7 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) { InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING); } -FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) { +FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) { return mInputManager->getChoreographer().getMouseCursorPosition(displayId); } @@ -1874,7 +1886,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint jstring nameObj, jint pid) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - if (displayId == ADISPLAY_ID_NONE) { + if (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; @@ -1884,7 +1896,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint std::string name = nameChars.c_str(); base::Result<std::unique_ptr<InputChannel>> inputChannel = - im->createInputMonitor(displayId, name, gui::Pid{pid}); + im->createInputMonitor(ui::LogicalDisplayId{displayId}, name, gui::Pid{pid}); if (!inputChannel.ok()) { std::string message = inputChannel.error().message(); @@ -1931,7 +1943,8 @@ static jboolean nativeSetInTouchMode(JNIEnv* env, jobject nativeImplObj, jboolea return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, gui::Pid{pid}, gui::Uid{static_cast<uid_t>(uid)}, - hasPermission, displayId); + hasPermission, + ui::LogicalDisplayId{displayId}); } static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj, @@ -2023,20 +2036,20 @@ static void nativeToggleCapsLock(JNIEnv* env, jobject nativeImplObj, jint device static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->displayRemoved(env, displayId); + im->displayRemoved(env, ui::LogicalDisplayId{displayId}); } static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId, jobject applicationHandleObj) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setFocusedApplication(env, displayId, applicationHandleObj); + im->setFocusedApplication(env, ui::LogicalDisplayId{displayId}, applicationHandleObj); } static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setFocusedDisplay(displayId); + im->setFocusedDisplay(ui::LogicalDisplayId{displayId}); } static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj, @@ -2092,8 +2105,8 @@ static jboolean nativeTransferTouchOnDisplay(JNIEnv* env, jobject nativeImplObj, NativeInputManager* im = getNativeInputManager(env, nativeImplObj); if (im->getInputManager()->getDispatcher().transferTouchOnDisplay(destChannelToken, - static_cast<int32_t>( - displayId))) { + ui::LogicalDisplayId{ + displayId})) { return JNI_TRUE; } else { return JNI_FALSE; @@ -2116,7 +2129,7 @@ static void nativeSetMousePointerAccelerationEnabled(JNIEnv* env, jobject native jint displayId, jboolean enabled) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setMousePointerAccelerationEnabled(displayId, enabled); + im->setMousePointerAccelerationEnabled(ui::LogicalDisplayId{displayId}, enabled); } static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) { @@ -2486,7 +2499,7 @@ static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject ico icon = pointerIcon.style; } - return im->setPointerIcon(std::move(icon), displayId, deviceId, pointerId, + return im->setPointerIcon(std::move(icon), ui::LogicalDisplayId{displayId}, deviceId, pointerId, ibinderForJavaObject(env, inputTokenObj)); } @@ -2494,13 +2507,14 @@ static void nativeSetPointerIconVisibility(JNIEnv* env, jobject nativeImplObj, j jboolean visible) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setPointerIconVisibility(displayId, visible); + im->setPointerIconVisibility(ui::LogicalDisplayId{displayId}, visible); } static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId); + return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, + ui::LogicalDisplayId{displayId}); } static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) { @@ -2512,8 +2526,9 @@ static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplO static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj, jint displayId, jboolean isEligible) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId, - isEligible); + im->getInputManager() + ->getDispatcher() + .setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId{displayId}, isEligible); } static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) { @@ -2649,7 +2664,7 @@ static void nativeCancelCurrentTouch(JNIEnv* env, jobject nativeImplObj) { static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setPointerDisplayId(displayId); + im->setPointerDisplayId(ui::LogicalDisplayId{displayId}); } static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) { @@ -2667,7 +2682,7 @@ static void nativeSetStylusButtonMotionEventsEnabled(JNIEnv* env, jobject native static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - const auto p = im->getMouseCursorPosition(displayId); + const auto p = im->getMouseCursorPosition(ui::LogicalDisplayId{displayId}); const std::array<float, 2> arr = {{p.x, p.y}}; jfloatArray outArr = env->NewFloatArray(2); env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data()); @@ -2712,6 +2727,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[] = { @@ -2820,6 +2840,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"setAccessibilityStickyKeysEnabled", "(Z)V", (void*)nativeSetAccessibilityStickyKeysEnabled}, {"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive}, + {"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId}, }; #define FIND_CLASS(var, className) \ @@ -2930,8 +2951,8 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz, "getInputPortAssociations", "()[Ljava/lang/String;"); - GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz, - "getInputUniqueIdAssociations", "()[Ljava/lang/String;"); + GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByPort, clazz, + "getInputUniqueIdAssociationsByPort", "()[Ljava/lang/String;"); GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, clazz, "getInputUniqueIdAssociationsByDescriptor", "()[Ljava/lang/String;"); diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index d0b290c05ee9..073396848c55 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -99,7 +99,7 @@ static bool setPowerMode(Mode mode, bool enabled) { } void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, - int32_t displayId) { + ui::LogicalDisplayId displayId) { if (gPowerManagerServiceObj) { // Throttle calls into user activity by event type. // We're a little conservative about argument checking here in case the caller @@ -124,8 +124,8 @@ void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t JNIEnv* env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod(gPowerManagerServiceObj, - gPowerManagerServiceClassInfo.userActivityFromNative, - nanoseconds_to_milliseconds(eventTime), eventType, displayId, 0); + gPowerManagerServiceClassInfo.userActivityFromNative, + nanoseconds_to_milliseconds(eventTime), eventType, displayId.val(), 0); checkAndClearExceptionFromCallback(env, "userActivityFromNative"); } } diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h index 36aaceb029c7..ed7fa7c39bd3 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.h +++ b/services/core/jni/com_android_server_power_PowerManagerService.h @@ -19,6 +19,7 @@ #include <nativehelper/JNIHelp.h> #include <powermanager/PowerManager.h> +#include <ui/LogicalDisplayId.h> #include <utils/Timers.h> #include "jni.h" @@ -26,7 +27,7 @@ namespace android { extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, - int32_t displayId); + ui::LogicalDisplayId displayId); } // namespace android diff --git a/services/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 0c83e8e468d9..2e126f1b50ba 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -295,7 +295,7 @@ public final class CredentialManagerService } } - private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) { + static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) { final int resolvedUserId = ActivityManager.handleIncomingUser( Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java index 38ad5b6594b5..b86db065cfd7 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java @@ -16,6 +16,8 @@ package com.android.server.credentials; +import static com.android.server.credentials.CredentialManagerService.getPrimaryProvidersForUserId; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -30,6 +32,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.infra.AbstractPerUserSystemService; import java.util.List; +import java.util.Set; /** @@ -80,9 +83,12 @@ public final class CredentialManagerServiceImpl extends Slog.i(TAG, "newServiceInfoLocked, mInfo null, " + serviceComponent.flattenToString()); } + Set<ComponentName> primaryProviders = + getPrimaryProvidersForUserId(mMaster.getContext(), mUserId); mInfo = CredentialProviderInfoFactory.create( getContext(), serviceComponent, - mUserId, /*isSystemProvider=*/false); + mUserId, /*isSystemProvider=*/false, + primaryProviders.contains(serviceComponent)); return mInfo.getServiceInfo(); } diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java index b1673e2c4c3c..7a026d5bd301 100644 --- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java @@ -67,6 +67,9 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ private final ResultReceiver mAutofillCallback; + @Nullable + private ComponentName mPrimaryProviderComponentName = null; + public GetCandidateRequestSession( Context context, SessionLifetime sessionCallback, Object lock, int userId, int callingUid, @@ -104,8 +107,12 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ if (providerGetCandidateSessions != null) { Slog.d(TAG, "In startProviderSession - provider session created and " + "being added for: " + providerInfo.getComponentName()); - mProviders.put(providerGetCandidateSessions.getComponentName().flattenToString(), - providerGetCandidateSessions); + ComponentName componentName = providerGetCandidateSessions + .getComponentName(); + if (providerInfo.isPrimary()) { + mPrimaryProviderComponentName = componentName; + } + mProviders.put(componentName.flattenToString(), providerGetCandidateSessions); } return providerGetCandidateSessions; } @@ -138,7 +145,7 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ try { invokeClientCallbackSuccess(new GetCandidateCredentialsResponse( - candidateProviderDataList, intent)); + candidateProviderDataList, intent, mPrimaryProviderComponentName)); } catch (RemoteException e) { Slog.e(TAG, "Issue while responding to client with error : " + e); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 375fc5a0280a..2b93d2132a8f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -339,6 +339,7 @@ import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.ManagedSubscriptionsPolicy; import android.app.admin.NetworkEvent; import android.app.admin.PackagePolicy; +import android.app.admin.PackageSetPolicyValue; import android.app.admin.ParcelableGranteeMap; import android.app.admin.ParcelableResource; import android.app.admin.PasswordMetrics; @@ -349,7 +350,6 @@ import android.app.admin.PolicyValue; import android.app.admin.PreferentialNetworkServiceConfig; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; -import android.app.admin.PackageSetPolicyValue; import android.app.admin.StartInstallingUpdateCallback; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; @@ -2718,6 +2718,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL)); setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled); + mInjector.runCryptoSelfTest(); } else { synchronized (getLockObject()) { mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); @@ -15172,10 +15173,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (statusBarService != null) { int flags1 = disabled ? STATUS_BAR_DISABLE_MASK : StatusBarManager.DISABLE_NONE; int flags2 = disabled ? STATUS_BAR_DISABLE2_MASK : StatusBarManager.DISABLE2_NONE; - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags1, - flags2); - statusBarService.disableForUser(info, mToken, mContext.getPackageName(), userId, - "setStatusBarDisabledInternal"); + statusBarService.disableForUser(flags1, mToken, mContext.getPackageName(), userId); + statusBarService.disable2ForUser(flags2, mToken, mContext.getPackageName(), userId); return true; } } catch (RemoteException e) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 4bf3ff4265d4..09eef451c547 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -196,19 +196,27 @@ final class PolicyEnforcerCallbacks { Binder.withCleanCallingIdentity(() -> { PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + pmi.setOwnerProtectedPackages(userId, packages == null ? null : packages.stream().toList()); LocalServices.getService(UsageStatsManagerInternal.class) .setAdminProtectedPackages( packages == null ? null : new ArraySet<>(packages), userId); - if (Flags.disallowUserControlBgUsageFix()) { - if (packages == null) { - return; + if (packages == null || packages.isEmpty()) { + return; + } + + for (int user : resolveUsers(userId)) { + if (Flags.disallowUserControlBgUsageFix()) { + setBgUsageAppOp(packages, pmi, user, appOpsManager); + } + if (Flags.disallowUserControlStoppedStateFix()) { + for (String packageName : packages) { + pmi.setPackageStoppedState(packageName, false, user); + } } - final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - resolveUsers(userId).forEach( - user -> setBgUsageAppOp(packages, pmi, user, appOpsManager)); } }); return true; diff --git a/services/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 9e4f8219ea96..d3072000a56e 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -1276,7 +1276,23 @@ class AppIdPermissionPolicy : SchemePolicy() { packageName, permissionName ) - else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName) + else -> + permissionAllowlist.getProductSignatureAppAllowlistState( + packageName, + permissionName + ) + ?: permissionAllowlist.getVendorSignatureAppAllowlistState( + packageName, + permissionName + ) + ?: permissionAllowlist.getSystemExtSignatureAppAllowlistState( + packageName, + permissionName + ) + ?: permissionAllowlist.getSignatureAppAllowlistState( + packageName, + permissionName + ) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index b155829208ef..649955614851 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -2046,8 +2046,20 @@ class PermissionService(private val service: AccessCheckingService) : writer.println("Unknown app ID $appId.") } } + } else if (args[0] == "--package" && args.size == 2) { + val packageName = args[1] + service.getState { + val packageState = state.externalState.packageStates[packageName] + if (packageState != null) { + writer.dumpAppIdState(packageState.appId, state, indexedSetOf(packageName)) + } else { + writer.println("Unknown package $packageName.") + } + } } else { - writer.println("Usage: dumpsys permission [--app-id APP_ID]") + writer.println( + "Usage: dumpsys permissionmgr [--app-id <APP_ID>] [--package <PACKAGE_NAME>]" + ) } } diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 622e70279700..54d101a3c1cf 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -243,12 +243,12 @@ public final class ProfcollectForwardingService extends SystemService { return; } sSelfService.mIProfcollect.process(); - jobFinished(params, false); } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to process profiles in background: " + e.getMessage()); } }); + jobFinished(params, false); return true; } diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java index 6a82f1656414..3e87c6fe7be7 100644 --- a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -543,6 +544,18 @@ public class TransportConnectionTest { return future.get(); } + @Test + public void onBindingDied_referenceLost_doesNotThrow() { + TransportConnection.TransportConnectionMonitor transportConnectionMonitor = + new TransportConnection.TransportConnectionMonitor( + mContext, /* transportConnection= */ null); + doThrow(new IllegalArgumentException("Service not registered")).when( + mContext).unbindService(any()); + + // Test no exception is thrown + transportConnectionMonitor.onBindingDied(mTransportComponent); + } + private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) { ArgumentCaptor<ServiceConnection> connectionCaptor = ArgumentCaptor.forClass(ServiceConnection.class); diff --git a/services/tests/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/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 cff22654e30c..2bbd3c06556f 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,16 @@ public class InputMethodManagerServiceTestBase { mServiceThread = new ServiceThread( - "TestServiceThread", - Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */ - false); + "immstest1", + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); + mPackageMonitorThread = + new ServiceThread( + "immstest2", + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread, - unusedUserId -> mMockInputMethodBindingController); + mPackageMonitorThread, unusedUserId -> mMockInputMethodBindingController); spyOn(mInputMethodManagerService); // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of @@ -246,6 +256,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 +265,10 @@ public class InputMethodManagerServiceTestBase { mInputMethodManagerService.mInputMethodDeviceConfigs.destroy(); } + if (mPackageMonitorThread != null) { + mPackageMonitorThread.quitSafely(); + } + if (mServiceThread != null) { mServiceThread.quitSafely(); } @@ -295,4 +310,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..5e3bc564f0f6 --- /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 testEqualsSameObject() { + 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.equals(map, map)); + } + + @Test + public void testEqualsEquivalentObject() { + 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.equals(toMap(imi1, imi2), toMap(imi1, imi2))); + + assertTrue("Must return true for the equivalent instances", + InputMethodMap.equals(toMap(imi1, imi2), toMap(imi2, imi1))); + } + + @Test + public void testEqualsDifferentKeys() { + 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.equals(toMap(imi1), toMap(imi1, imi2))); + assertFalse("Must return false if keys are different", + InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1))); + assertFalse("Must return false if keys are different", + InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1, imi3))); + } + + @Test + public void testEqualsDifferentValues() { + 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.equals(toMap(imi1_without_subtypes), toMap(imi1_with_subtypes))); + assertFalse("Must return false if values are different", + InputMethodMap.equals( + 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/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/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..54b2d4dd1429 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -282,7 +282,7 @@ public class DisplayManagerServiceTest { return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener, new VirtualDisplayAdapter.SurfaceControlDisplayFactory() { @Override - public IBinder createDisplay(String name, boolean secure, + public IBinder createDisplay(String name, boolean secure, String uniqueId, float requestedRefreshRate) { return mMockDisplayToken; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 80f38eb52d97..e5685c7f4f43 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1790,6 +1790,7 @@ public final class DisplayPowerControllerTest { verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(), /* ignoreAnimationLimits= */ anyBoolean()); + verify(mHolder.brightnessSetting).setBrightness(brightness); } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index ae6361bdd556..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 @@ -46,6 +46,7 @@ import com.android.server.display.brightness.strategy.AutomaticBrightnessStrateg 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; @@ -90,6 +91,8 @@ public final class DisplayBrightnessStrategySelectorTest { @Mock private AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy; @Mock + private FallbackBrightnessStrategy mFallbackBrightnessStrategy; + @Mock private Resources mResources; @Mock private DisplayManagerFlags mDisplayManagerFlags; @@ -135,7 +138,7 @@ public final class DisplayBrightnessStrategySelectorTest { @Override AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context, - int displayId) { + int displayId, DisplayManagerFlags displayManagerFlags) { return mAutomaticBrightnessStrategy; } @@ -155,6 +158,11 @@ public final class DisplayBrightnessStrategySelectorTest { AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() { return mAutoBrightnessFallbackStrategy; } + + @Override + FallbackBrightnessStrategy getFallbackBrightnessStrategy() { + return mFallbackBrightnessStrategy; + } }; @Rule @@ -355,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, 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 3e78118cd5df..19bff56a4b29 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()); @@ -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); @@ -429,8 +477,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; @@ -461,8 +508,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; 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/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 4a2164582890..42814e7c775e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -239,6 +239,9 @@ public class RescuePartyTest { @Test public void testBootLoopDetectionWithExecutionForAllRescueLevels() { + // this is old test where the flag needs to be disabled + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), any(Executor.class), @@ -449,6 +452,9 @@ public class RescuePartyTest { @Test public void testNonPersistentAppCrashDetectionWithScopedResets() { + // this is old test where the flag needs to be disabled + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), any(Executor.class), @@ -506,6 +512,9 @@ public class RescuePartyTest { @Test public void testNonDeviceConfigSettingsOnlyResetOncePerLevel() { + // this is old test where the flag needs to be disabled + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), any(Executor.class), @@ -879,6 +888,9 @@ public class RescuePartyTest { @Test public void testBootLoopLevels() { + // this is old test where the flag needs to be disabled + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index cc69c1dbf999..28c7fb2396dd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -956,6 +956,7 @@ public class MockingOomAdjusterTests { ConnectionRecord cr = s.getConnections().get(binder).get(0); setFieldValue(ConnectionRecord.class, cr, "activity", mock(ActivityServiceConnectionsHolder.class)); + doReturn(client).when(sService).getTopApp(); doReturn(true).when(cr.activity).isActivityVisible(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java index 599b9cd06ee1..8e1e3392eb1c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java @@ -273,10 +273,8 @@ public class SettingsToPropertiesMapperTest { keyValueMap.put("namespace_1*flag_1", "true"); // case 2: existing prop, stage a different value keyValueMap.put("namespace_1*flag_2", "false"); - // case 3: new prop, stage the non default value + // case 3: new prop keyValueMap.put("namespace_2*flag_1", "true"); - // case 4: new prop, stage the default value - keyValueMap.put("namespace_2*flag_2", "false"); Properties props = new Properties(namespace, keyValueMap); HashMap<String, HashMap<String, String>> toStageProps = @@ -290,11 +288,9 @@ public class SettingsToPropertiesMapperTest { String namespace_1_flag_1 = namespace_1_to_stage.get("flag_1"); String namespace_1_flag_2 = namespace_1_to_stage.get("flag_2"); String namespace_2_flag_1 = namespace_2_to_stage.get("flag_1"); - String namespace_2_flag_2 = namespace_2_to_stage.get("flag_2"); Assert.assertTrue(namespace_1_flag_1 == null); Assert.assertTrue(namespace_1_flag_2 != null); Assert.assertTrue(namespace_2_flag_1 != null); - Assert.assertTrue(namespace_2_flag_2 == null); Assert.assertTrue(namespace_1_flag_2.equals("false")); Assert.assertTrue(namespace_2_flag_1.equals("true")); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/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/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 7d58a2e53693..79f1574105ba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -778,49 +778,6 @@ public final class UserManagerServiceTest { } @Test - public void testGetAliveUsers_shouldExcludeInitialisedEphemeralNonCurrentUsers() { - assertWithMessage("Ephemeral user should not exist at all initially") - .that(mUmi.getUsers(false).stream().anyMatch(u -> u.id == USER_ID)) - .isFalse(); - - // add an ephemeral full user - TestUserData userData = new TestUserData(USER_ID); - userData.info.flags = UserInfo.FLAG_FULL | UserInfo.FLAG_EPHEMERAL; - addUserData(userData); - - assertWithMessage("Ephemeral user should exist as alive after being created") - .that(mUmi.getUsers(true).stream().anyMatch(u -> u.id == USER_ID)) - .isTrue(); - - // mock switch to the user (mark it as initialized & make it the current user) - userData.info.flags |= UserInfo.FLAG_INITIALIZED; - mockCurrentUser(USER_ID); - - assertWithMessage("Ephemeral user should still exist as alive after being switched to") - .that(mUmi.getUsers(true).stream().anyMatch(u -> u.id == USER_ID)) - .isTrue(); - - // switch away from the user - mockCurrentUser(OTHER_USER_ID); - - assertWithMessage("Ephemeral user should not exist as alive after getting switched away") - .that(mUmi.getUsers(true).stream().anyMatch(u -> u.id == USER_ID)) - .isFalse(); - - assertWithMessage("Ephemeral user should still exist as dying after getting switched away") - .that(mUmi.getUsers(false).stream().anyMatch(u -> u.id == USER_ID)) - .isTrue(); - - // finally remove the user - mUms.removeUserInfo(USER_ID); - - assertWithMessage("Ephemeral user should not exist at all after cleanup") - .that(mUmi.getUsers(false).stream().anyMatch(u -> u.id == USER_ID)) - .isFalse(); - } - - - @Test @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() { diff --git a/services/tests/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/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/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java new file mode 100644 index 000000000000..7f2327aa4f24 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA; + +import static com.google.common.truth.Truth.assertThat; + +import android.Manifest; +import android.app.AppOpsManager; +import android.companion.virtual.VirtualDeviceManager; +import android.companion.virtual.VirtualDeviceParams; +import android.content.AttributionSource; +import android.os.Process; +import android.permission.PermissionManager; +import android.permission.flags.Flags; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.testing.TestableContext; +import android.virtualdevice.cts.common.VirtualDeviceRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.Objects; + +@RunWith(AndroidJUnit4.class) +public class AppOpsDeviceAwareServiceTest { + + @Rule + public final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getInstrumentation().getTargetContext()); + + @Rule + public VirtualDeviceRule virtualDeviceRule = + VirtualDeviceRule.withAdditionalPermissions( + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, + Manifest.permission.CREATE_VIRTUAL_DEVICE, + Manifest.permission.GET_APP_OPS_STATS); + + private static final String ATTRIBUTION_TAG_1 = "attributionTag1"; + private static final String ATTRIBUTION_TAG_2 = "attributionTag2"; + private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + private final PermissionManager mPermissionManager = + mContext.getSystemService(PermissionManager.class); + + private VirtualDeviceManager.VirtualDevice mVirtualDevice; + + @Before + public void setUp() { + mVirtualDevice = + virtualDeviceRule.createManagedVirtualDevice( + new VirtualDeviceParams.Builder() + .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM) + .build()); + + mPermissionManager.grantRuntimePermission( + mContext.getOpPackageName(), + Manifest.permission.CAMERA, + mVirtualDevice.getPersistentDeviceId()); + + mPermissionManager.grantRuntimePermission( + mContext.getOpPackageName(), + Manifest.permission.CAMERA, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + } + + @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED) + @Test + public void noteProxyOp_proxyAppOnDefaultDevice() { + AppOpsManager.OpEventProxyInfo proxyInfo = + noteProxyOpWithDeviceId(TestableContext.DEVICE_ID_DEFAULT); + assertThat(proxyInfo.getDeviceId()) + .isEqualTo(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + } + + @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED) + @Test + public void noteProxyOp_proxyAppOnRemoteDevice() { + AppOpsManager.OpEventProxyInfo proxyInfo = + noteProxyOpWithDeviceId(mVirtualDevice.getDeviceId()); + assertThat(proxyInfo.getDeviceId()).isEqualTo(mVirtualDevice.getPersistentDeviceId()); + } + + private AppOpsManager.OpEventProxyInfo noteProxyOpWithDeviceId(int proxyAppDeviceId) { + AttributionSource proxiedAttributionSource = + new AttributionSource.Builder(Process.myUid()) + .setPackageName(mContext.getOpPackageName()) + .setAttributionTag(ATTRIBUTION_TAG_2) + .setDeviceId(mVirtualDevice.getDeviceId()) + .build(); + + AttributionSource proxyAttributionSource = + new AttributionSource.Builder(Process.myUid()) + .setPackageName(mContext.getOpPackageName()) + .setAttributionTag(ATTRIBUTION_TAG_1) + .setDeviceId(proxyAppDeviceId) + .setNextAttributionSource(proxiedAttributionSource) + .build(); + + int mode = mAppOpsManager.noteProxyOp(OP_CAMERA, proxyAttributionSource, null, false); + assertThat(mode).isEqualTo(AppOpsManager.MODE_ALLOWED); + + List<AppOpsManager.PackageOps> packagesOps = + mAppOpsManager.getPackagesForOps( + new String[] {AppOpsManager.OPSTR_CAMERA}, + mVirtualDevice.getPersistentDeviceId()); + + AppOpsManager.PackageOps packageOps = + packagesOps.stream() + .filter( + pkg -> + Objects.equals( + pkg.getPackageName(), mContext.getOpPackageName())) + .findFirst() + .orElseThrow(); + + AppOpsManager.OpEntry opEntry = + packageOps.getOps().stream() + .filter(op -> op.getOp() == OP_CAMERA) + .findFirst() + .orElseThrow(); + + return opEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index 3dc375c5436d..9cd3186f99f3 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -368,24 +368,32 @@ public class AuthServiceTest { } @Test - public void testAuthenticate_throwsWhenUsingTestConfigurations() { + public void testAuthenticate_throwsWhenUsingTestApis() { final PromptInfo promptInfo = mock(PromptInfo.class); - when(promptInfo.containsPrivateApiConfigurations()).thenReturn(false); - when(promptInfo.containsTestConfigurations()).thenReturn(true); + when(promptInfo.requiresInternalPermission()).thenReturn(false); + when(promptInfo.requiresTestOrInternalPermission()).thenReturn(true); - testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo); + testAuthenticate_throwsSecurityException(promptInfo); } @Test public void testAuthenticate_throwsWhenUsingPrivateApis() { final PromptInfo promptInfo = mock(PromptInfo.class); - when(promptInfo.containsPrivateApiConfigurations()).thenReturn(true); - when(promptInfo.containsTestConfigurations()).thenReturn(false); + when(promptInfo.requiresInternalPermission()).thenReturn(true); + when(promptInfo.requiresTestOrInternalPermission()).thenReturn(false); - testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo); + testAuthenticate_throwsSecurityException(promptInfo); } - private void testAuthenticate_throwsWhenUsingTestConfigurations(PromptInfo promptInfo) { + @Test + public void testAuthenticate_throwsWhenUsingAdvancedApis() { + final PromptInfo promptInfo = mock(PromptInfo.class); + when(promptInfo.requiresAdvancedPermission()).thenReturn(true); + + testAuthenticate_throwsSecurityException(promptInfo); + } + + private void testAuthenticate_throwsSecurityException(PromptInfo promptInfo) { mAuthService = new AuthService(mContext, mInjector); mAuthService.onStart(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java index 0716a5c5561d..3698d6fb6b76 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.NotificationManager; +import android.content.Context; import android.content.Intent; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.UserHandle; @@ -75,13 +76,15 @@ public class BiometricDanglingReceiverTest { @Test public void testFingerprintRegisterReceiver() { initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT); - verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any()); + verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(), + eq(Context.RECEIVER_NOT_EXPORTED)); } @Test public void testFaceRegisterReceiver() { initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE); - verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any()); + verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(), + eq(Context.RECEIVER_NOT_EXPORTED)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java index b33a8aa28544..00c8ed188d1e 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java @@ -53,7 +53,7 @@ class InputManagerMockHelper { private IInputDevicesChangedListener mDevicesChangedListener; private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping = new HashMap<>(); - private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation = + private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociationByPort = new HashMap<>(); InputManagerMockHelper(TestableLooper testableLooper, @@ -79,11 +79,11 @@ class InputManagerMockHelper { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]); doAnswer(inv -> mDevices.get(inv.getArgument(0))) .when(mIInputManagerMock).getInputDevice(anyInt()); - doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0), - inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociation( + doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0), + inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort( anyString(), anyString()); - doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when( - mIInputManagerMock).removeUniqueIdAssociation(anyString()); + doAnswer(inv -> mUniqueIdAssociationByPort.remove(inv.getArgument(0))).when( + mIInputManagerMock).removeUniqueIdAssociationByPort(anyString()); // Set a new instance of InputManager for testing that uses the IInputManager mock as the // interface to the server. @@ -113,7 +113,7 @@ class InputManagerMockHelper { .setDescriptor(phys) .setExternal(true) .setAssociatedDisplayId( - mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys), + mDisplayIdMapping.getOrDefault(mUniqueIdAssociationByPort.get(phys), Display.INVALID_DISPLAY)) .build(); diff --git a/services/tests/servicestests/src/com/android/server/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 0cb861532bfc..b50684bb7a25 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -43,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; @@ -186,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 } }; 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 e1b66b53ecbe..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; @@ -121,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(); 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 df27e7828385..4aa074b1a52b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -23,7 +23,9 @@ 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; @@ -143,6 +145,11 @@ public class SystemAudioInitiationActionFromAvrTest { protected boolean isStandbyMessageReceived() { return mStandbyMessageReceived; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); 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/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/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 4af20a97a9aa..a3d57c3df51e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -67,6 +67,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 +94,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; @@ -210,6 +212,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 +251,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 +622,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 +1543,25 @@ 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(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @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(); @@ -1533,6 +1570,29 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @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); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 5e2fe6a080eb..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; @@ -339,6 +341,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -909,7 +912,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } mService.clearNotifications(); - TestableLooper.get(this).processAllMessages(); + if (mTestableLooper != null) { + mTestableLooper.processAllMessages(); + } try { mService.onDestroy(); @@ -920,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); } } @@ -1009,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, @@ -1302,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() { @@ -5969,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(); } @@ -8798,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(); } @@ -14065,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))) @@ -14077,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(); @@ -14086,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))) @@ -14105,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(); @@ -14114,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 @@ -15479,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/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/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 44d1b5421b5b..87395a17698d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -113,15 +113,10 @@ import android.hardware.HardwareBuffer; import android.metrics.LogMaker; import android.os.Binder; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArraySet; -import android.util.DisplayMetrics; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; @@ -131,7 +126,6 @@ import android.view.IDisplayChangeWindowController; import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; import android.view.InsetsState; -import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -151,7 +145,6 @@ import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.WmDisplayCutout; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -178,10 +171,6 @@ import java.util.concurrent.TimeoutException; @RunWith(WindowTestRunner.class) public class DisplayContentTests extends WindowTestsBase { - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @SetupWindows(addAllCommonWindows = true) @Test public void testForAllWindows() { @@ -514,44 +503,6 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); } - /** - * Tests tapping on a root task in different display results in window gaining focus. - */ - @Test - @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_REMOVE_POINTER_EVENT_TRACKING_IN_WM) - public void testInputEventBringsCorrectDisplayInFocus() { - DisplayContent dc0 = mWm.getDefaultDisplayContentLocked(); - // Create a second display - final DisplayContent dc1 = createNewDisplay(); - - // Add root task with activity. - final Task rootTask0 = createTask(dc0); - final Task task0 = createTaskInRootTask(rootTask0, 0 /* userId */); - final ActivityRecord activity = createNonAttachedActivityRecord(dc0); - task0.addChild(activity, 0); - dc0.configureDisplayPolicy(); - assertNotNull(dc0.mTapDetector); - - final Task rootTask1 = createTask(dc1); - final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */); - final ActivityRecord activity1 = createNonAttachedActivityRecord(dc0); - task1.addChild(activity1, 0); - dc1.configureDisplayPolicy(); - assertNotNull(dc1.mTapDetector); - - // tap on primary display. - tapOnDisplay(dc0); - // Check focus is on primary display. - assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus, - dc0.findFocusedWindow()); - - // Tap on secondary display. - tapOnDisplay(dc1); - // Check focus is on secondary. - assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus, - dc1.findFocusedWindow()); - } - @Test public void testFocusedWindowMultipleDisplays() { doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q); @@ -2959,33 +2910,4 @@ public class DisplayContentTests extends WindowTestsBase { throw new RuntimeException(e); } } - - private void tapOnDisplay(final DisplayContent dc) { - final DisplayMetrics dm = dc.getDisplayMetrics(); - final float x = dm.widthPixels / 2; - final float y = dm.heightPixels / 2; - final long downTime = SystemClock.uptimeMillis(); - final long eventTime = SystemClock.uptimeMillis() + 100; - // sending ACTION_DOWN - final MotionEvent downEvent = MotionEvent.obtain( - downTime, - downTime, - MotionEvent.ACTION_DOWN, - x, - y, - 0 /*metaState*/); - downEvent.setDisplayId(dc.getDisplayId()); - dc.mTapDetector.onPointerEvent(downEvent); - - // sending ACTION_UP - final MotionEvent upEvent = MotionEvent.obtain( - downTime, - eventTime, - MotionEvent.ACTION_UP, - x, - y, - 0 /*metaState*/); - upEvent.setDisplayId(dc.getDisplayId()); - dc.mTapDetector.onPointerEvent(upEvent); - } } diff --git a/services/tests/wmtests/src/com/android/server/wm/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/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java index b90fa21cb2b1..79e401c238bd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java @@ -18,15 +18,17 @@ package com.android.server.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; - import static com.android.server.wm.testing.Assert.assertThrows; +import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -35,11 +37,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; import android.platform.test.annotations.Presubmit; +import android.util.DisplayMetrics; import androidx.test.filters.SmallTest; -import com.android.server.wm.testing.Assert; +import com.android.internal.R; import org.junit.Before; import org.junit.Test; @@ -277,7 +281,7 @@ public class LetterboxConfigurationTest { } @Test - public void test_lettterboxPositionWhenReachabilityEnabledIsSet() { + public void test_letterboxPositionWhenReachabilityEnabledIsSet() { // Check that horizontal reachability is set with correct arguments mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability( false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); @@ -344,4 +348,48 @@ public class LetterboxConfigurationTest { mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f); mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1); } + + @Test + public void test_evaluateThinLetterboxWhenDensityChanges() { + final Resources rs = mock(Resources.class); + final DisplayMetrics dm = mock(DisplayMetrics.class); + final LetterboxConfigurationPersister lp = mock(LetterboxConfigurationPersister.class); + spyOn(mContext); + when(rs.getDisplayMetrics()).thenReturn(dm); + when(mContext.getResources()).thenReturn(rs); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp)) + .thenReturn(100); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) + .thenReturn(200); + final LetterboxConfiguration configuration = new LetterboxConfiguration(mContext, lp); + + // Verify the values are the expected ones + dm.density = 100; + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp)) + .thenReturn(100); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) + .thenReturn(200); + final int thinWidthPx = configuration.getThinLetterboxWidthPx(); + final int thinHeightPx = configuration.getThinLetterboxHeightPx(); + assertEquals(100, thinWidthPx); + assertEquals(200, thinHeightPx); + + // We change the values in the resources but not the update condition (density) and the + // result should not change + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp)) + .thenReturn(300); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) + .thenReturn(400); + final int thinWidthPx2 = configuration.getThinLetterboxWidthPx(); + final int thinHeightPx2 = configuration.getThinLetterboxHeightPx(); + assertEquals(100, thinWidthPx2); + assertEquals(200, thinHeightPx2); + + // We update the condition (density) so the new resource values should be read + dm.density = 150; + final int thinWidthPx3 = configuration.getThinLetterboxWidthPx(); + final int thinHeightPx3 = configuration.getThinLetterboxHeightPx(); + assertEquals(300, thinWidthPx3); + assertEquals(400, thinHeightPx3); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index a60d243c9dad..1195c934a6f7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -1623,6 +1623,12 @@ public class LetterboxUiControllerTest extends WindowTestsBase { assertTrue(mController.allowHorizontalReachabilityForThinLetterbox()); } + @Test + public void testIsLetterboxEducationEnabled() { + mController.isLetterboxEducationEnabled(); + verify(mLetterboxConfiguration).getIsEducationEnabled(); + } + private void mockThatProperty(String propertyName, boolean value) throws Exception { Property property = new Property(propertyName, /* value */ value, /* packageName */ "", /* className */ ""); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index afa669807c2e..d9fd312423d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -1894,11 +1894,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testApplyTransaction_createTaskFragmentDecorSurface() { mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG); - // TODO(b/293654166) remove system organizer requirement once security review is cleared. - mController.unregisterOrganizer(mIOrganizer); - registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */); final Task task = createTask(mDisplayContent); - final TaskFragment tf = createTaskFragment(task); final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE).build(); @@ -1913,9 +1909,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testApplyTransaction_removeTaskFragmentDecorSurface() { mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG); - // TODO(b/293654166) remove system organizer requirement once security review is cleared. - mController.unregisterOrganizer(mIOrganizer); - registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */); final Task task = createTask(mDisplayContent); final TaskFragment tf = createTaskFragment(task); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java index 37de51eccff2..4fc222b3e038 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java @@ -95,10 +95,6 @@ public class TestIWindow extends IWindow.Stub { } @Override - public void updatePointerIcon(float x, float y) throws RemoteException { - } - - @Override public void dispatchWindowShown() throws RemoteException { } @@ -128,4 +124,9 @@ public class TestIWindow extends IWindow.Stub { public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) throws RemoteException { } + + @Override + public void dumpWindow(ParcelFileDescriptor pfd) { + + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index fbbb9a2e55a7..b152c3e5355f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -819,17 +819,20 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(win.getOrientationChanging()); } - @SetupWindows(addWindows = W_ABOVE_ACTIVITY) @Test public void testRequestResizeForBlastSync() { - final WindowState win = mChildAppWindowAbove; - makeWindowVisible(win, win.getParentWindow()); + final WindowState win = createWindow(null, TYPE_APPLICATION, "window"); + makeWindowVisible(win); + makeLastConfigReportedToClient(win, true /* visible */); win.mLayoutSeq = win.getDisplayContent().mLayoutSeq; win.reportResized(); win.updateResizingWindowIfNeeded(); assertThat(mWm.mResizingWindows).doesNotContain(win); // Check that the window is in resizing if using blast sync. + final BLASTSyncEngine.SyncGroup syncGroup = mock(BLASTSyncEngine.SyncGroup.class); + syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_BLAST; + win.mSyncGroup = syncGroup; win.reportResized(); win.prepareSync(); assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState); @@ -842,6 +845,20 @@ public class WindowStateTests extends WindowTestsBase { mWm.mResizingWindows.remove(win); win.updateResizingWindowIfNeeded(); assertThat(mWm.mResizingWindows).doesNotContain(win); + + // Non blast sync doesn't require to force resizing, because it won't use syncSeqId. + // And if the window is already drawn, it can report sync finish immediately so that the + // sync group won't be blocked. + win.finishSync(mTransaction, syncGroup, false /* cancel */); + syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_NONE; + win.mSyncGroup = syncGroup; + win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; + win.prepareSync(); + assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState); + win.updateResizingWindowIfNeeded(); + assertThat(mWm.mResizingWindows).doesNotContain(win); + assertTrue(win.isSyncFinished(syncGroup)); + assertEquals(WindowContainer.SYNC_STATE_READY, win.mSyncState); } @Test diff --git a/telephony/java/android/telephony/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 03ba8faa64de..dbe4f2782616 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -13114,39 +13114,41 @@ public class TelephonyManager { }) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) public @Nullable ServiceState getServiceState(@IncludeLocationData int includeLocationData) { - return getServiceStateForSubscriber(getSubId(), + return getServiceStateForSlot(SubscriptionManager.getSlotIndex(getSubId()), includeLocationData != INCLUDE_LOCATION_DATA_FINE, includeLocationData == INCLUDE_LOCATION_DATA_NONE); } /** - * Returns the service state information on specified subscription. Callers require - * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information. + * Returns the service state information on specified SIM slot. * - * May return {@code null} when the subscription is inactive or when there was an error + * May return {@code null} when the {@code slotIndex} is invalid or when there was an error * communicating with the phone process. + * + * @param slotIndex of phone whose service state is returned * @param renounceFineLocationAccess Set this to true if the caller would not like to receive * location related information which will be sent if the caller already possess * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission * @param renounceCoarseLocationAccess Set this to true if the caller would not like to * receive location related information which will be sent if the caller already possess * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions. + * @return Service state on specified SIM slot. */ - private ServiceState getServiceStateForSubscriber(int subId, - boolean renounceFineLocationAccess, + private ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess, boolean renounceCoarseLocationAccess) { try { ITelephony service = getITelephony(); if (service != null) { - return service.getServiceStateForSubscriber(subId, renounceFineLocationAccess, - renounceCoarseLocationAccess, getOpPackageName(), getAttributionTag()); + return service.getServiceStateForSlot(slotIndex, + renounceFineLocationAccess, renounceCoarseLocationAccess, + getOpPackageName(), getAttributionTag()); } } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e); + Log.e(TAG, "Error calling ITelephony#getServiceStateForSlot", e); } catch (NullPointerException e) { AnomalyReporter.reportAnomaly( UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"), - "getServiceStateForSubscriber " + subId + " NPE"); + "getServiceStateForSlot " + slotIndex + " NPE"); } return null; } @@ -13161,7 +13163,35 @@ public class TelephonyManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public ServiceState getServiceStateForSubscriber(int subId) { - return getServiceStateForSubscriber(subId, false, false); + return getServiceStateForSlot( + SubscriptionManager.getSlotIndex(subId), false, false); + } + + /** + * Returns the service state information on specified SIM slot. + * + * If you want continuous updates of service state info, register a {@link TelephonyCallback} + * that implements {@link TelephonyCallback.ServiceStateListener} through + * {@link #registerTelephonyCallback}. + * + * May return {@code null} when the {@code slotIndex} is invalid or when there was an error + * communicating with the phone process + * + * See {@link #getActiveModemCount()} to get the total number of slots + * that are active on the device. + * + * @param slotIndex of phone whose service state is returned + * @return ServiceState on specified SIM slot. + * + * @hide + */ + @RequiresPermission(allOf = { + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_COARSE_LOCATION + }) + @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) + public @Nullable ServiceState getServiceStateForSlot(int slotIndex) { + return getServiceStateForSlot(slotIndex, false, false); } /** diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 0bb5fd513fd1..47f53f372d33 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -1015,13 +1015,27 @@ public final class SatelliteManager { * @hide */ public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3; + /** + * Datagram type indicating that the datagram to be sent or received is of type SOS message and + * is the last message to emergency service provider indicating still needs help. + * @hide + */ + public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4; + /** + * Datagram type indicating that the datagram to be sent or received is of type SOS message and + * is the last message to emergency service provider indicating no more help is needed. + * @hide + */ + public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5; /** @hide */ @IntDef(prefix = "DATAGRAM_TYPE_", value = { DATAGRAM_TYPE_UNKNOWN, DATAGRAM_TYPE_SOS_MESSAGE, DATAGRAM_TYPE_LOCATION_SHARING, - DATAGRAM_TYPE_KEEP_ALIVE + DATAGRAM_TYPE_KEEP_ALIVE, + DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP, + DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED }) @Retention(RetentionPolicy.SOURCE) public @interface DatagramType {} diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index d4da7364b07d..65de7e479890 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1399,19 +1399,18 @@ interface ITelephony { oneway void requestModemActivityInfo(in ResultReceiver result); /** - * Get the service state on specified subscription - * @param subId Subscription id + * Get the service state on specified SIM slot. + * @param slotIndex of phone whose service state is returned * @param renounceFineLocationAccess Set this to true if the caller would not like to * receive fine location related information * @param renounceCoarseLocationAccess Set this to true if the caller would not like to * receive coarse location related information * @param callingPackage The package making the call * @param callingFeatureId The feature in the package - * @return Service state on specified subscription. + * @return Service state on specified SIM slot. */ - ServiceState getServiceStateForSubscriber(int subId, boolean renounceFineLocationAccess, - boolean renounceCoarseLocationAccess, - String callingPackage, String callingFeatureId); + ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess, + boolean renounceCoarseLocationAccess, String callingPackage, String callingFeatureId); /** * Returns the URI for the per-account voicemail ringtone set in Phone settings. diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt index dfbbda6c6f5e..afb3593e3e98 100644 --- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt @@ -9,21 +9,28 @@ import android.security.attestationverification.AttestationVerificationManager.R import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.util.IndentingPrintWriter +import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.security.AttestationVerificationManagerService.DumpLogger import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations import java.io.ByteArrayOutputStream +import java.io.PrintWriter +import java.io.StringWriter import java.security.cert.Certificate import java.security.cert.CertificateFactory import java.security.cert.TrustAnchor import java.security.cert.X509Certificate import java.time.LocalDate +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + /** Test for Peer Device attestation verifier. */ @SmallTest @@ -31,6 +38,7 @@ import java.time.LocalDate class AttestationVerificationPeerDeviceVerifierTest { private val certificateFactory = CertificateFactory.getInstance("X.509") @Mock private lateinit var context: Context + private val dumpLogger = DumpLogger() private lateinit var trustAnchors: HashSet<TrustAnchor> @Before @@ -44,37 +52,50 @@ class AttestationVerificationPeerDeviceVerifierTest { } } + @After + fun dumpAndLog() { + val dump = dumpLogger.getDump() + Log.d(TAG, "$dump") + } + @Test fun verifyAttestation_returnsSuccessTypeChallenge() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 8, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 8, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_SUCCESS) } @Test fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 1, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_SUCCESS) } @Test fun verifyAttestation_returnsSuccessTypePublicKey() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 8, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 8, 1) + ) val leafCert = (TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0] @@ -84,61 +105,75 @@ class AttestationVerificationPeerDeviceVerifierTest { val result = verifier.verifyAttestation( TYPE_PUBLIC_KEY, pkRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_SUCCESS) } @Test fun verifyAttestation_returnsSuccessOwnedBySystem() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 1, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "activeUnlockValid".encodeToByteArray()) challengeRequirements.putBoolean("android.key_owned_by_system", true) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray() + ) + assertThat(result).isEqualTo(RESULT_SUCCESS) } @Test fun verifyAttestation_returnsFailureOwnedBySystem() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 1, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) challengeRequirements.putBoolean("android.key_owned_by_system", true) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } @Test fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2023, 3, 1), - LocalDate.of(2023, 2, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1), + LocalDate.of(2023, 2, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } @Test fun verifyAttestation_returnsFailureTrustedAnchorEmpty() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, HashSet(), false, LocalDate.of(2022, 1, 1), - LocalDate.of(2022, 1, 1)) + context, dumpLogger, HashSet(), false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } @@ -151,32 +186,39 @@ class AttestationVerificationPeerDeviceVerifierTest { } val verifier = AttestationVerificationPeerDeviceVerifier( - context, badTrustAnchors, false, LocalDate.of(2022, 1, 1), - LocalDate.of(2022, 1, 1)) + context, dumpLogger, badTrustAnchors, false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } fun verifyAttestation_returnsFailureChallenge() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 1, 1), - LocalDate.of(2022, 1, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } private fun String.fromPEMFileToCerts(): Collection<Certificate> { return certificateFactory.generateCertificates( InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets() - .open(this)) + .open(this) + ) } private fun String.fromPEMFileToByteArray(): ByteArray { @@ -188,6 +230,12 @@ class AttestationVerificationPeerDeviceVerifierTest { return bos.toByteArray() } + private fun DumpLogger.getDump(): String { + val sw = StringWriter() + this.dumpTo(IndentingPrintWriter(PrintWriter(sw), " ")) + return sw.toString() + } + class TestActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -195,6 +243,7 @@ class AttestationVerificationPeerDeviceVerifierTest { } companion object { + private const val TAG = "AVFTest" private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem" private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME = "test_attestation_with_root_certs.pem" diff --git a/tests/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/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 ae1098d496df..e3a2e674ae7a 100644 --- a/tests/FlickerTests/IME/OWNERS +++ b/tests/FlickerTests/IME/OWNERS @@ -1,3 +1,3 @@ # ime # Bug component: 34867 -include /services/core/java/com/android/server/inputmethod/OWNERS +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 d22bdcf25529..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 @@ -35,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) 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/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/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/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index 3b9ee807077e..3c72498082e4 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -169,12 +169,6 @@ class InputManagerServiceTests { localService.setDisplayViewports(viewports) verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java)) verify(native).setPointerDisplayId(displayId) - - val x = 42f - val y = 314f - service.onPointerDisplayIdChanged(displayId, x, y) - testLooper.dispatchNext() - verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y) } @Test @@ -317,9 +311,8 @@ class InputManagerServiceTests { verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent) } - // TODO(b/324075859): Rename this method to addUniqueIdAssociationByPort_verifyAssociations @Test - fun addUniqueIdAssociation_verifyAssociations() { + fun addUniqueIdAssociationByPort_verifyAssociations() { // Overall goal is to have 2 displays and verify that events from the InputDevice are // sent only to the view that is on the associated display. // So, associate the InputDevice with display 1, then send and verify KeyEvents. @@ -340,7 +333,7 @@ class InputManagerServiceTests { val inputDevice = createInputDevice() // Associate input device with display - service.addUniqueIdAssociation( + service.addUniqueIdAssociationByPort( inputDevice.name, virtualDisplays[0].display.displayId.toString() ) @@ -364,10 +357,10 @@ class InputManagerServiceTests { verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent) // Remove association - service.removeUniqueIdAssociation(inputDevice.name) + service.removeUniqueIdAssociationByPort(inputDevice.name) // Associate with Display 2 - service.addUniqueIdAssociation( + service.addUniqueIdAssociationByPort( inputDevice.name, virtualDisplays[1].display.displayId.toString() ) diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java index 3ab8d370750f..6bcfebc752f1 100644 --- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java +++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java @@ -176,25 +176,19 @@ public class StatusBarTest extends TestActivity }, new Test("Disable Alerts") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNotificationPeekingDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_ALERTS); } }, new Test("Disable Ticker") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNotificationTickerDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_TICKER); } }, new Test("Disable Expand in 3 sec.") { public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setStatusBarExpansionDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND); } }, 3000); } @@ -203,9 +197,7 @@ public class StatusBarTest extends TestActivity public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNotificationIconsDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_ICONS); } }, 3000); } @@ -214,73 +206,56 @@ public class StatusBarTest extends TestActivity public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setStatusBarExpansionDisabled(true); - info.setNotificationIconsDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND + | StatusBarManager.DISABLE_NOTIFICATION_ICONS); } }, 3000); } }, new Test("Disable Home (StatusBarManager)") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNavigationHomeDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_HOME); } }, new Test("Disable Back (StatusBarManager)") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setBackDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_BACK); } }, new Test("Disable Recent (StatusBarManager)") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setRecentsDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_RECENT); } }, new Test("Disable Clock") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setClockDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_CLOCK); } }, new Test("Disable System Info") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setSystemIconsDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_SYSTEM_INFO); } }, new Test("Disable everything in 3 sec") { public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setDisableAll(); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(~StatusBarManager.DISABLE_NONE); } }, 3000); } }, new Test("Enable everything") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_NONE); } }, new Test("Enable everything in 3 sec.") { public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setEnableAll(); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(0); } }, 3000); } diff --git a/tests/graphics/SilkFX/AndroidManifest.xml b/tests/graphics/SilkFX/AndroidManifest.xml index c293589bdbaf..25092b52e2b6 100644 --- a/tests/graphics/SilkFX/AndroidManifest.xml +++ b/tests/graphics/SilkFX/AndroidManifest.xml @@ -23,12 +23,13 @@ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:label="SilkFX" - android:theme="@android:style/Theme.Material"> + android:theme="@style/Theme.UsefulDefault"> <activity android:name=".Main" android:label="SilkFX Demos" android:banner="@drawable/background1" - android:exported="true"> + android:exported="true" + android:theme="@style/Theme.UsefulDefault"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.DEFAULT"/> diff --git a/tests/graphics/SilkFX/res/values/style.xml b/tests/graphics/SilkFX/res/values/style.xml index 66edbb5c9382..4dd626dfb8f5 100644 --- a/tests/graphics/SilkFX/res/values/style.xml +++ b/tests/graphics/SilkFX/res/values/style.xml @@ -23,9 +23,14 @@ <item name="android:windowElevation">0dp</item> <item name="buttonStyle">@style/AppTheme.Button</item> <item name="colorAccent">#bbffffff</item> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> <style name="AppTheme.Button" parent="Widget.AppCompat.Button"> <item name="android:textColor">#ffffffff</item> </style> + <style name="Theme.UsefulDefault" parent="android:Theme.Material"> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + </style> + </resources> diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java index e3f84c19653b..f1260008ca59 100644 --- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java +++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java @@ -24,6 +24,7 @@ import static com.android.server.inputmethod.multisessiontest.TestRequestConstan import android.app.Activity; import android.os.Bundle; +import android.os.Process; import android.util.Log; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; @@ -47,8 +48,9 @@ public final class MainActivity extends ConcurrentUserActivityBase { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Log.v(TAG, "Create MainActivity as user " + getUserId() + " on display " - + getDisplayId()); + Log.v(TAG, "Create MainActivity as user " + + Process.myUserHandle().getIdentifier() + " on display " + + getDisplay().getDisplayId()); setContentView(R.layout.main_activity); mImm = getSystemService(InputMethodManager.class); mEditor = requireViewById(R.id.edit_text); diff --git a/tools/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/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/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/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/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..931f0c5fa793 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 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-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/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; } |