diff options
143 files changed, 3528 insertions, 1149 deletions
diff --git a/Android.bp b/Android.bp index c1fb41f845e3..23234fdc5ae2 100644 --- a/Android.bp +++ b/Android.bp @@ -95,7 +95,7 @@ filegroup { ":platform-compat-native-aidl", // AIDL sources from external directories - ":android.hardware.biometrics.common-V3-java-source", + ":android.hardware.biometrics.common-V4-java-source", ":android.hardware.biometrics.fingerprint-V3-java-source", ":android.hardware.gnss-V2-java-source", ":android.hardware.graphics.common-V3-java-source", diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig index f5e33a80211b..e73b434042af 100644 --- a/apex/jobscheduler/framework/aconfig/job.aconfig +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -1,6 +1,13 @@ package: "android.app.job" flag { + name: "enforce_minimum_time_windows" + namespace: "backstage_power" + description: "Enforce a minimum time window for job latencies & deadlines" + bug: "311402873" +} + +flag { name: "job_debug_info_apis" namespace: "backstage_power" description: "Add APIs to let apps attach debug information to jobs" diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 742ed5f2eeb7..4bc73130db29 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -23,6 +23,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.util.TimeUtils.formatDuration; import android.annotation.BytesLong; @@ -36,6 +37,7 @@ import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.compat.annotation.EnabledSince; +import android.compat.annotation.Overridable; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ComponentName; @@ -48,7 +50,9 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import android.os.Process; import android.os.Trace; +import android.os.UserHandle; import android.util.ArraySet; import android.util.Log; @@ -113,6 +117,16 @@ public class JobInfo implements Parcelable { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long REJECT_NEGATIVE_NETWORK_ESTIMATES = 253665015L; + /** + * Enforce a minimum time window between job latencies and deadlines. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Overridable // Aid in testing + public static final long ENFORCE_MINIMUM_TIME_WINDOWS = 311402873L; + /** @hide */ @IntDef(prefix = { "NETWORK_TYPE_" }, value = { NETWORK_TYPE_NONE, @@ -1866,10 +1880,40 @@ public class JobInfo implements Parcelable { * Set deadline which is the maximum scheduling latency. The job will be run by this * deadline even if other requirements (including a delay set through * {@link #setMinimumLatency(long)}) are not met. + * {@link JobParameters#isOverrideDeadlineExpired()} will return {@code true} if the job's + * deadline has passed. + * * <p> * Because it doesn't make sense setting this property on a periodic job, doing so will * throw an {@link java.lang.IllegalArgumentException} when * {@link android.app.job.JobInfo.Builder#build()} is called. + * + * <p class="note"> + * Since a job will run once the deadline has passed regardless of the status of other + * constraints, setting a deadline of 0 with other constraints makes those constraints + * meaningless when it comes to execution decisions. Avoid doing this. + * </p> + * + * <p> + * Short deadlines hinder the system's ability to optimize scheduling behavior and may + * result in running jobs at inopportune times. Therefore, starting in Android version + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, minimum time windows will be + * enforced to help make it easier to better optimize job execution. Time windows are + * defined as the time between a job's {@link #setMinimumLatency(long) minimum latency} + * and its deadline. If the minimum latency is not set, it is assumed to be 0. + * The following minimums will be enforced: + * <ul> + * <li> + * Jobs with {@link #PRIORITY_DEFAULT} or higher priorities have a minimum time + * window of one hour. + * </li> + * <li>Jobs with {@link #PRIORITY_LOW} have a minimum time window of 6 hours.</li> + * <li>Jobs with {@link #PRIORITY_MIN} have a minimum time window of 12 hours.</li> + * </ul> + * + * Work that must happen immediately should use {@link #setExpedited(boolean)} or + * {@link #setUserInitiated(boolean)} in the appropriate manner. + * * @see JobInfo#getMaxExecutionDelayMillis() */ public Builder setOverrideDeadline(long maxExecutionDelayMillis) { @@ -2143,12 +2187,14 @@ public class JobInfo implements Parcelable { */ public JobInfo build() { return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS), - Compatibility.isChangeEnabled(REJECT_NEGATIVE_NETWORK_ESTIMATES)); + Compatibility.isChangeEnabled(REJECT_NEGATIVE_NETWORK_ESTIMATES), + Compatibility.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS)); } /** @hide */ public JobInfo build(boolean disallowPrefetchDeadlines, - boolean rejectNegativeNetworkEstimates) { + boolean rejectNegativeNetworkEstimates, + boolean enforceMinimumTimeWindows) { // This check doesn't need to be inside enforceValidity. It's an unnecessary legacy // check that would ideally be phased out instead. if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { @@ -2157,7 +2203,8 @@ public class JobInfo implements Parcelable { " setRequiresDeviceIdle is an error."); } JobInfo jobInfo = new JobInfo(this); - jobInfo.enforceValidity(disallowPrefetchDeadlines, rejectNegativeNetworkEstimates); + jobInfo.enforceValidity(disallowPrefetchDeadlines, rejectNegativeNetworkEstimates, + enforceMinimumTimeWindows); return jobInfo; } @@ -2176,7 +2223,8 @@ public class JobInfo implements Parcelable { * @hide */ public final void enforceValidity(boolean disallowPrefetchDeadlines, - boolean rejectNegativeNetworkEstimates) { + boolean rejectNegativeNetworkEstimates, + boolean enforceMinimumTimeWindows) { // Check that network estimates require network type and are reasonable values. if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0) && networkRequest == null) { @@ -2291,6 +2339,39 @@ public class JobInfo implements Parcelable { throw new IllegalArgumentException("Invalid priority level provided: " + mPriority); } + if (enforceMinimumTimeWindows + && Flags.enforceMinimumTimeWindows() + // TODO(312197030): remove exemption for the system + && !UserHandle.isCore(Process.myUid()) + && hasLateConstraint && !isPeriodic) { + final long windowStart = hasEarlyConstraint ? minLatencyMillis : 0; + if (mPriority >= PRIORITY_DEFAULT) { + if (maxExecutionDelayMillis - windowStart < HOUR_IN_MILLIS) { + throw new IllegalArgumentException( + getPriorityString(mPriority) + + " cannot have a time window less than 1 hour." + + " Delay=" + windowStart + + ", deadline=" + maxExecutionDelayMillis); + } + } else if (mPriority >= PRIORITY_LOW) { + if (maxExecutionDelayMillis - windowStart < 6 * HOUR_IN_MILLIS) { + throw new IllegalArgumentException( + getPriorityString(mPriority) + + " cannot have a time window less than 6 hours." + + " Delay=" + windowStart + + ", deadline=" + maxExecutionDelayMillis); + } + } else { + if (maxExecutionDelayMillis - windowStart < 12 * HOUR_IN_MILLIS) { + throw new IllegalArgumentException( + getPriorityString(mPriority) + + " cannot have a time window less than 12 hours." + + " Delay=" + windowStart + + ", deadline=" + maxExecutionDelayMillis); + } + } + } + if (isExpedited) { if (hasEarlyConstraint) { throw new IllegalArgumentException("An expedited job cannot have a time delay"); 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 fecd2fd60b1d..f97100bf2e9b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -4388,7 +4388,7 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.w(TAG, "Uid " + uid + " set bias on its job"); return new JobInfo.Builder(job) .setBias(JobInfo.BIAS_DEFAULT) - .build(false, false); + .build(false, false, false); } } @@ -4410,7 +4410,9 @@ public class JobSchedulerService extends com.android.server.SystemService job.enforceValidity( CompatChanges.isChangeEnabled( JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid), - rejectNegativeNetworkEstimates); + rejectNegativeNetworkEstimates, + CompatChanges.isChangeEnabled( + JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS, callingUid)); if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index afcbddad611e..53b14d616ecc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -1495,7 +1495,7 @@ public final class JobStore { // return value), the deadline is dropped. Periodic jobs require all constraints // to be met, so there's no issue with their deadlines. // The same logic applies for other target SDK-based validation checks. - builtJob = jobBuilder.build(false, false); + builtJob = jobBuilder.build(false, false, false); } catch (Exception e) { Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e); return null; 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 13bea6bd1dd1..b74806494a60 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 @@ -636,7 +636,7 @@ public final class JobStatus { .build()); // Don't perform validation checks at this point since we've already passed the // initial validation check. - job = builder.build(false, false); + job = builder.build(false, false, false); } this.job = job; diff --git a/core/api/current.txt b/core/api/current.txt index bbdb0a19adde..095da8871dc8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -39282,7 +39282,7 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityStart(); method @NonNull public String getKeystoreAlias(); method public int getMaxUsageCount(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); @@ -39290,7 +39290,7 @@ package android.security.keystore { method public boolean isDevicePropertiesAttestationIncluded(); method @NonNull public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified(); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isStrongBoxBacked(); method public boolean isUnlockedDeviceRequired(); @@ -39322,7 +39322,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); @@ -39427,14 +39427,14 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityForOriginationEnd(); method @Nullable public java.util.Date getKeyValidityStart(); method public int getMaxUsageCount(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified(); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isUnlockedDeviceRequired(); method public boolean isUserAuthenticationRequired(); @@ -39456,7 +39456,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 28ef70b14026..0dc04133e664 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -8,13 +8,6 @@ flag { } flag { - name: "mgf1_digest_setter" - namespace: "hardware_backed_security" - description: "Feature flag for mgf1 digest setter in key generation and import parameters." - bug: "308378912" -} - -flag { name: "fix_unlocked_device_required_keys_v2" namespace: "hardware_backed_security" description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys" diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index fd5517d29d74..f28574ecb3b2 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -27,9 +27,6 @@ import android.window.SurfaceSyncGroup; import com.android.window.flags.Flags; -import java.util.concurrent.Executor; -import java.util.function.Consumer; - /** * Provides an interface to the root-Surface of a View Hierarchy or Window. This * is used in combination with the {@link android.view.SurfaceControl} API to enable @@ -197,42 +194,6 @@ public interface AttachedSurfaceControl { } /** - * Add a trusted presentation listener on the SurfaceControl associated with this window. - * - * @param t Transaction that the trusted presentation listener is added on. This should - * be applied by the caller. - * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify - * when the to invoke the callback. - * @param executor The {@link Executor} where the callback will be invoked on. - * @param listener The {@link Consumer} that will receive the callbacks when entered or - * exited the threshold. - * - * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl, - * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer) - * - * @hide b/287076178 un-hide with API bump - */ - default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull SurfaceControl.TrustedPresentationThresholds thresholds, - @NonNull Executor executor, @NonNull Consumer<Boolean> listener) { - } - - /** - * Remove a trusted presentation listener on the SurfaceControl associated with this window. - * - * @param t Transaction that the trusted presentation listener removed on. This should - * be applied by the caller. - * @param listener The {@link Consumer} that was previously registered with - * addTrustedPresentationCallback that should be removed. - * - * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl) - * @hide b/287076178 un-hide with API bump - */ - default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull Consumer<Boolean> listener) { - } - - /** * Transfer the currently in progress touch gesture from the host to the requested * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the * SurfaceControlViewHost was created with the current host's inputToken. diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 17bbee6d020f..36b74e39072a 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -73,6 +73,8 @@ import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; import android.window.ScreenCapture; import android.window.WindowContextInfo; +import android.window.ITrustedPresentationListener; +import android.window.TrustedPresentationThresholds; /** * System private interface to the window manager. @@ -1075,4 +1077,10 @@ interface IWindowManager @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MONITOR_INPUT)") void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId); + + void registerTrustedPresentationListener(in IBinder window, in ITrustedPresentationListener listener, + in TrustedPresentationThresholds thresholds, int id); + + + void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9d2ab1fe2085..2fcd675a8f98 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2182,8 +2182,14 @@ public final class ViewRootImpl implements ViewParent, } } - void notifyInsetsAnimationRunningStateChanged(boolean running) { - mInsetsAnimationRunning = running; + /** + * Notify the when the running state of a insets animation changed. + */ + @VisibleForTesting + public void notifyInsetsAnimationRunningStateChanged(boolean running) { + if (sToolkitSetFrameRateReadOnlyFlagValue) { + mInsetsAnimationRunning = running; + } } @Override @@ -2443,6 +2449,19 @@ public final class ViewRootImpl implements ViewParent, if (updateBoundsLayer(t)) { applyTransactionOnDraw(t); } + + // Set the frame rate selection strategy to FRAME_RATE_SELECTION_STRATEGY_SELF + // This strategy ensures that the frame rate specifications do not cascade down to + // the descendant layers. This is particularly important for applications like Chrome, + // where child surfaces should adhere to default behavior instead of no preference + if (sToolkitSetFrameRateReadOnlyFlagValue) { + try { + mFrameRateTransaction.setFrameRateSelectionStrategy(sc, + sc.FRAME_RATE_SELECTION_STRATEGY_SELF).applyAsyncUnsafe(); + } catch (Exception e) { + Log.e(mTag, "Unable to set frame rate selection strategy ", e); + } + } } private void destroySurface() { @@ -11924,18 +11943,6 @@ public final class ViewRootImpl implements ViewParent, scheduleTraversals(); } - @Override - public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull SurfaceControl.TrustedPresentationThresholds thresholds, - @NonNull Executor executor, @NonNull Consumer<Boolean> listener) { - t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener); - } - - @Override - public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull Consumer<Boolean> listener) { - t.clearTrustedPresentationCallback(getSurfaceControl()); - } private void logAndTrace(String msg) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { @@ -11949,7 +11956,7 @@ public final class ViewRootImpl implements ViewParent, return; } - int frameRateCategory = mIsFrameRateBoosting + int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; try { @@ -12063,6 +12070,14 @@ public final class ViewRootImpl implements ViewParent, } /** + * Get the value of mLastPreferredFrameRateCategory + */ + @VisibleForTesting + public int getLastPreferredFrameRateCategory() { + return mLastPreferredFrameRateCategory; + } + + /** * Get the value of mPreferredFrameRate */ @VisibleForTesting diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 046ea77f196d..f668088e6b44 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -122,7 +122,9 @@ import android.view.WindowInsets.Side.InsetsSide; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.accessibility.AccessibilityNodeInfo; +import android.window.ITrustedPresentationListener; import android.window.TaskFpsCallback; +import android.window.TrustedPresentationThresholds; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -5884,4 +5886,35 @@ public interface WindowManager extends ViewManager { default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) { throw new UnsupportedOperationException(); } + + /** + * Add a trusted presentation listener associated with a window. If the listener has already + * been registered, an AndroidRuntimeException will be thrown. + * + * @param window The Window to add the trusted presentation listener for + * @param thresholds The {@link TrustedPresentationThresholds} that will specify + * when the to invoke the callback. + * @param executor The {@link Executor} where the callback will be invoked on. + * @param listener The {@link ITrustedPresentationListener} that will receive the callbacks + * when entered or exited trusted presentation per the thresholds. + * + * @hide b/287076178 un-hide with API bump + */ + default void registerTrustedPresentationListener(@NonNull IBinder window, + @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor, + @NonNull Consumer<Boolean> listener) { + throw new UnsupportedOperationException(); + } + + /** + * Removes a presentation listener associated with a window. If the listener was not previously + * registered, the call will be a noop. + * + * @hide + * @see #registerTrustedPresentationListener(IBinder, + * TrustedPresentationThresholds, Executor, Consumer) + */ + default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { + throw new UnsupportedOperationException(); + } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 214f1ec3d1ec..a7d814e9ab8c 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -30,9 +30,13 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.util.AndroidRuntimeException; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.view.inputmethod.InputMethodManager; +import android.window.ITrustedPresentationListener; +import android.window.TrustedPresentationThresholds; import com.android.internal.util.FastPrintWriter; @@ -43,6 +47,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.WeakHashMap; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.IntConsumer; /** @@ -143,6 +148,9 @@ public final class WindowManagerGlobal { private Runnable mSystemPropertyUpdater; + private final TrustedPresentationListener mTrustedPresentationListener = + new TrustedPresentationListener(); + private WindowManagerGlobal() { } @@ -324,7 +332,7 @@ public final class WindowManagerGlobal { final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags - & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { + & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } @@ -482,7 +490,7 @@ public final class WindowManagerGlobal { if (who != null) { WindowLeaked leak = new WindowLeaked( what + " " + who + " has leaked window " - + root.getView() + " that was originally added here"); + + root.getView() + " that was originally added here"); leak.setStackTrace(root.getLocation().getStackTrace()); Log.e(TAG, "", leak); } @@ -790,6 +798,86 @@ public final class WindowManagerGlobal { } } + public void registerTrustedPresentationListener(@NonNull IBinder window, + @NonNull TrustedPresentationThresholds thresholds, Executor executor, + @NonNull Consumer<Boolean> listener) { + mTrustedPresentationListener.addListener(window, thresholds, listener, executor); + } + + public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { + mTrustedPresentationListener.removeListener(listener); + } + + private final class TrustedPresentationListener extends + ITrustedPresentationListener.Stub { + private static int sId = 0; + private final ArrayMap<Consumer<Boolean>, Pair<Integer, Executor>> mListeners = + new ArrayMap<>(); + + private final Object mTplLock = new Object(); + + private void addListener(IBinder window, TrustedPresentationThresholds thresholds, + Consumer<Boolean> listener, Executor executor) { + synchronized (mTplLock) { + if (mListeners.containsKey(listener)) { + throw new AndroidRuntimeException("Trying to add duplicate listener"); + } + int id = sId++; + mListeners.put(listener, new Pair<>(id, executor)); + try { + WindowManagerGlobal.getWindowManagerService() + .registerTrustedPresentationListener(window, this, thresholds, id); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + } + + private void removeListener(Consumer<Boolean> listener) { + synchronized (mTplLock) { + var removedListener = mListeners.remove(listener); + if (removedListener == null) { + Log.i(TAG, "listener " + listener + " does not exist."); + return; + } + + try { + WindowManagerGlobal.getWindowManagerService() + .unregisterTrustedPresentationListener(this, removedListener.first); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + } + + @Override + public void onTrustedPresentationChanged(int[] inTrustedStateListenerIds, + int[] outOfTrustedStateListenerIds) { + ArrayList<Runnable> firedListeners = new ArrayList<>(); + synchronized (mTplLock) { + mListeners.forEach((listener, idExecutorPair) -> { + final var listenerId = idExecutorPair.first; + final var executor = idExecutorPair.second; + for (int id : inTrustedStateListenerIds) { + if (listenerId == id) { + firedListeners.add(() -> executor.execute( + () -> listener.accept(/*presentationState*/true))); + } + } + for (int id : outOfTrustedStateListenerIds) { + if (listenerId == id) { + firedListeners.add(() -> executor.execute( + () -> listener.accept(/*presentationState*/false))); + } + } + }); + } + for (int i = 0; i < firedListeners.size(); i++) { + firedListeners.get(i).run(); + } + } + } + /** @hide */ public void addWindowlessRoot(ViewRootImpl impl) { synchronized (mLock) { @@ -801,7 +889,7 @@ public final class WindowManagerGlobal { public void removeWindowlessRoot(ViewRootImpl impl) { synchronized (mLock) { mWindowlessRoots.remove(impl); - } + } } public void setRecentsAppBehindSystemBars(boolean behindSystemBars) { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index d7b74b3bcfe2..b4b1fde89a46 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -37,6 +37,7 @@ import android.os.StrictMode; import android.util.Log; import android.window.ITaskFpsCallback; import android.window.TaskFpsCallback; +import android.window.TrustedPresentationThresholds; import android.window.WindowContext; import android.window.WindowMetricsController; import android.window.WindowProvider; @@ -508,4 +509,17 @@ public final class WindowManagerImpl implements WindowManager { } return false; } + + @Override + public void registerTrustedPresentationListener(@NonNull IBinder window, + @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor, + @NonNull Consumer<Boolean> listener) { + mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener); + } + + @Override + public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { + mGlobal.unregisterTrustedPresentationListener(listener); + + } } diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index aa4275d62046..b29967888312 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -34,7 +34,7 @@ flag { namespace: "accessibility" name: "flash_notification_system_api" description: "Makes flash notification APIs as system APIs for calling from mainline module" - bug: "282821643" + bug: "303131332" } flag { diff --git a/core/java/android/window/ITrustedPresentationListener.aidl b/core/java/android/window/ITrustedPresentationListener.aidl new file mode 100644 index 000000000000..b33128abb7e5 --- /dev/null +++ b/core/java/android/window/ITrustedPresentationListener.aidl @@ -0,0 +1,24 @@ +/* + * 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 android.window; + +/** + * @hide + */ +oneway interface ITrustedPresentationListener { + void onTrustedPresentationChanged(in int[] enteredTrustedStateIds, in int[] exitedTrustedStateIds); +}
\ No newline at end of file diff --git a/core/java/android/window/TrustedPresentationListener.java b/core/java/android/window/TrustedPresentationListener.java new file mode 100644 index 000000000000..02fd6d98fb0d --- /dev/null +++ b/core/java/android/window/TrustedPresentationListener.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * @hide + */ +public interface TrustedPresentationListener { + + void onTrustedPresentationChanged(boolean inTrustedPresentationState); + +} diff --git a/core/java/android/window/TrustedPresentationThresholds.aidl b/core/java/android/window/TrustedPresentationThresholds.aidl new file mode 100644 index 000000000000..d7088bf0fddc --- /dev/null +++ b/core/java/android/window/TrustedPresentationThresholds.aidl @@ -0,0 +1,3 @@ +package android.window; + +parcelable TrustedPresentationThresholds; diff --git a/core/java/android/window/TrustedPresentationThresholds.java b/core/java/android/window/TrustedPresentationThresholds.java new file mode 100644 index 000000000000..801d35c49228 --- /dev/null +++ b/core/java/android/window/TrustedPresentationThresholds.java @@ -0,0 +1,127 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; + +/** + * @hide + */ +public final class TrustedPresentationThresholds implements Parcelable { + /** + * The min alpha the {@link SurfaceControl} is required to have to be considered inside the + * threshold. + */ + @FloatRange(from = 0f, fromInclusive = false, to = 1f) + public final float mMinAlpha; + + /** + * The min fraction of the SurfaceControl that was presented to the user to be considered + * inside the threshold. + */ + @FloatRange(from = 0f, fromInclusive = false, to = 1f) + public final float mMinFractionRendered; + + /** + * The time in milliseconds required for the {@link SurfaceControl} to be in the threshold. + */ + @IntRange(from = 1) + public final int mStabilityRequirementMs; + + private void checkValid() { + if (mMinAlpha <= 0 || mMinFractionRendered <= 0 || mStabilityRequirementMs < 1) { + throw new IllegalArgumentException( + "TrustedPresentationThresholds values are invalid"); + } + } + + /** + * Creates a new TrustedPresentationThresholds. + * + * @param minAlpha The min alpha the {@link SurfaceControl} is required to + * have to be considered inside the + * threshold. + * @param minFractionRendered The min fraction of the SurfaceControl that was presented + * to the user to be considered + * inside the threshold. + * @param stabilityRequirementMs The time in milliseconds required for the + * {@link SurfaceControl} to be in the threshold. + */ + public TrustedPresentationThresholds( + @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha, + @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered, + @IntRange(from = 1) int stabilityRequirementMs) { + this.mMinAlpha = minAlpha; + this.mMinFractionRendered = minFractionRendered; + this.mStabilityRequirementMs = stabilityRequirementMs; + checkValid(); + } + + @Override + public String toString() { + return "TrustedPresentationThresholds { " + + "minAlpha = " + mMinAlpha + ", " + + "minFractionRendered = " + mMinFractionRendered + ", " + + "stabilityRequirementMs = " + mStabilityRequirementMs + + " }"; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeFloat(mMinAlpha); + dest.writeFloat(mMinFractionRendered); + dest.writeInt(mStabilityRequirementMs); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * @hide + */ + TrustedPresentationThresholds(@NonNull Parcel in) { + mMinAlpha = in.readFloat(); + mMinFractionRendered = in.readFloat(); + mStabilityRequirementMs = in.readInt(); + + checkValid(); + } + + /** + * @hide + */ + public static final @NonNull Creator<TrustedPresentationThresholds> CREATOR = + new Creator<TrustedPresentationThresholds>() { + @Override + public TrustedPresentationThresholds[] newArray(int size) { + return new TrustedPresentationThresholds[size]; + } + + @Override + public TrustedPresentationThresholds createFromParcel(@NonNull Parcel in) { + return new TrustedPresentationThresholds(in); + } + }; +} diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 4bb7c33b41e2..8c2a52560050 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -93,6 +93,7 @@ public enum ProtoLogGroup implements IProtoLogGroup { WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), + WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index e0e3a3542cb0..617262203c6b 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -628,6 +628,42 @@ public class ViewRootImplTest { }); } + /** + * We should boost the frame rate if the value of mInsetsAnimationRunning is true. + */ + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public void votePreferredFrameRate_insetsAnimation() { + View view = new View(sContext); + WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + + sInstrumentation.runOnMainSync(() -> { + WindowManager wm = sContext.getSystemService(WindowManager.class); + Display display = wm.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + wmlp.width = (int) (metrics.widthPixels * 0.9); + wmlp.height = (int) (metrics.heightPixels * 0.9); + wm.addView(view, wmlp); + }); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + sInstrumentation.runOnMainSync(() -> { + view.invalidate(); + assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NORMAL); + viewRootImpl.notifyInsetsAnimationRunningStateChanged(true); + view.invalidate(); + }); + sInstrumentation.waitForIdleSync(); + + sInstrumentation.runOnMainSync(() -> { + assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_HIGH); + }); + } @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 2237ba1924db..19128212094d 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -595,6 +595,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1518132958": { + "message": "fractionRendered boundsOverSource=%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "-1517908912": { "message": "requestScrollCapture: caught exception dispatching to window.token=%s", "level": "WARN", @@ -961,6 +967,12 @@ "group": "WM_DEBUG_CONTENT_RECORDING", "at": "com\/android\/server\/wm\/ContentRecorder.java" }, + "-1209762265": { + "message": "Registering listener=%s with id=%d for window=%s with %s", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "-1209252064": { "message": "Clear animatingExit: reason=clearAnimatingFlags win=%s", "level": "DEBUG", @@ -1333,6 +1345,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "-888703350": { + "message": "Skipping %s", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "-883738232": { "message": "Adding more than one toast window for UID at a time.", "level": "WARN", @@ -2803,6 +2821,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "360319850": { + "message": "fractionRendered scale=%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "364992694": { "message": "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s", "level": "VERBOSE", @@ -2983,6 +3007,12 @@ "group": "WM_DEBUG_BACK_PREVIEW", "at": "com\/android\/server\/wm\/BackNavigationController.java" }, + "532771960": { + "message": "Adding untrusted state listener=%s with id=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "535103992": { "message": "Wallpaper may change! Adjusting", "level": "VERBOSE", @@ -3061,6 +3091,12 @@ "group": "WM_DEBUG_DREAM", "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" }, + "605179032": { + "message": "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "608694300": { "message": " NEW SURFACE SESSION %s", "level": "INFO", @@ -3289,6 +3325,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowState.java" }, + "824532141": { + "message": "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f minFractionRendered=%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "829434921": { "message": "Draw state now committed in %s", "level": "VERBOSE", @@ -3583,6 +3625,12 @@ "group": "WM_SHOW_SURFACE_ALLOC", "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java" }, + "1090378847": { + "message": "Checking %d windows", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1100065297": { "message": "Attempted to get IME policy of a display that does not exist: %d", "level": "WARN", @@ -3715,6 +3763,12 @@ "group": "WM_DEBUG_FOCUS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1251721200": { + "message": "unregister failed, couldn't find deathRecipient for %s with id=%d", + "level": "ERROR", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1252594551": { "message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d", "level": "WARN", @@ -3853,6 +3907,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/TaskDisplayArea.java" }, + "1382634842": { + "message": "Unregistering listener=%s with id=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1393721079": { "message": "Starting remote display change: from [rot = %d], to [%dx%d, rot = %d]", "level": "VERBOSE", @@ -3901,6 +3961,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "1445704347": { + "message": "coveredRegionsAbove updated with %s frame:%s region:%s", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1448683958": { "message": "Override pending remote transitionSet=%b adapter=%s", "level": "INFO", @@ -4201,6 +4267,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "1786463281": { + "message": "Adding trusted state listener=%s with id=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1789321832": { "message": "Then token:%s is invalid. It might be removed", "level": "WARN", @@ -4375,6 +4447,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "1955470028": { + "message": "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s scale=%f,%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1964565370": { "message": "Starting remote animation", "level": "INFO", @@ -4659,6 +4737,9 @@ "WM_DEBUG_TASKS": { "tag": "WindowManager" }, + "WM_DEBUG_TPL": { + "tag": "WindowManager" + }, "WM_DEBUG_WALLPAPER": { "tag": "WindowManager" }, diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 4982f3732089..231fa4837441 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -618,7 +618,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see #isMgf1DigestsSpecified() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi("MGF1_DIGEST_SETTER") public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { if (mMgf1Digests.isEmpty()) { throw new IllegalStateException("Mask generation function (MGF) not specified"); @@ -633,7 +633,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see #getMgf1Digests() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi("MGF1_DIGEST_SETTER") public boolean isMgf1DigestsSpecified() { return !mMgf1Digests.isEmpty(); } @@ -1292,7 +1292,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi("MGF1_DIGEST_SETTER") public Builder setMgf1Digests(@NonNull @KeyProperties.DigestEnum String... mgf1Digests) { mMgf1Digests = Set.of(mgf1Digests); return this; diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 7b6b2d142f95..c1e3bab5d37c 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -401,7 +401,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see #isMgf1DigestsSpecified() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi("MGF1_DIGEST_SETTER") public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { if (mMgf1Digests.isEmpty()) { throw new IllegalStateException("Mask generation function (MGF) not specified"); @@ -416,7 +416,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see #getMgf1Digests() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi("MGF1_DIGEST_SETTER") public boolean isMgf1DigestsSpecified() { return !mMgf1Digests.isEmpty(); } @@ -799,7 +799,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi("MGF1_DIGEST_SETTER") public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) { mMgf1Digests = Set.of(mgf1Digests); return this; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 02efc2f3539d..ed4b485f3927 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -28,7 +28,6 @@ import android.hardware.security.keymint.SecurityLevel; import android.hardware.security.keymint.Tag; import android.os.Build; import android.os.StrictMode; -import android.security.Flags; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore2; import android.security.KeyStoreException; @@ -854,22 +853,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mgf1Digest )); }); - - /* If the MGF1 Digest setter is not set, fall back to the previous behaviour: - * Add, as MGF1 Digest function, all the primary digests. - * Avoid adding the default MGF1 digest as it will have been included in the - * mKeymasterMgf1Digests field. - */ - if (!Flags.mgf1DigestSetter()) { - final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster( - DEFAULT_MGF1_DIGEST); - ArrayUtils.forEach(mKeymasterDigests, (digest) -> { - if (digest != defaultMgf1Digest) { - params.add(KeyStore2ParameterUtils.makeEnum( - KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest)); - } - }); - } } }); ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 4f65884138bd..ddbd93e458fd 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -25,7 +25,6 @@ import android.hardware.security.keymint.HardwareAuthenticatorType; import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.SecurityLevel; import android.os.StrictMode; -import android.security.Flags; import android.security.GateKeeper; import android.security.KeyStore2; import android.security.KeyStoreParameter; @@ -538,31 +537,11 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { /* Because of default MGF1 digest is SHA-1. It has to be added in Key * characteristics. Otherwise, crypto operations will fail with Incompatible * MGF1 digest. - * If the MGF1 Digest setter flag isn't set, then the condition in the - * if clause above must be false (cannot have MGF1 digests specified if the - * flag was off). In that case, in addition to adding the default MGF1 - * digest, we have to add all the other digests as MGF1 Digests. - * */ importArgs.add(KeyStore2ParameterUtils.makeEnum( KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST) )); - if (!Flags.mgf1DigestSetter()) { - final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster( - DEFAULT_MGF1_DIGEST); - for (String digest : spec.getDigests()) { - int digestToAddAsMgf1Digest = KeyProperties.Digest.toKeymaster( - digest); - // Do not add the default MGF1 digest as it has been added above. - if (digestToAddAsMgf1Digest != defaultMgf1Digest) { - importArgs.add(KeyStore2ParameterUtils.makeEnum( - KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, - digestToAddAsMgf1Digest - )); - } - } - } } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index baa52a0b5626..5d161962be4a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -397,6 +397,9 @@ public class BubblePositioner { * the screen and the size of the elements around it (e.g. padding, pointer, manage button). */ public int getMaxExpandedViewHeight(boolean isOverflow) { + if (mDeviceConfig.isLargeScreen() && !mDeviceConfig.isSmallTablet() && !isOverflow) { + return getExpandedViewHeightForLargeScreen(); + } // Subtract top insets because availableRect.height would account for that int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top; int paddingTop = showBubblesVertically() @@ -414,6 +417,16 @@ public class BubblePositioner { - bottomPadding; } + private int getExpandedViewHeightForLargeScreen() { + // the expanded view height on large tablets is calculated based on the shortest screen + // size and is the same in both portrait and landscape + int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom); + int shortestScreenSide = Math.min(getScreenRect().height(), getScreenRect().width()); + // Subtract pointer size because it's laid out in LinearLayout with the expanded view. + return shortestScreenSide - maxVerticalInset * 2 + - mManageButtonHeight - mPointerWidth - mExpandedViewPadding * 2; + } + /** * Determines the height for the bubble, ensuring a minimum height. If the height should be as * big as available, returns {@link #MAX_HEIGHT}. @@ -424,15 +437,6 @@ public class BubblePositioner { // overflow in landscape on phone is max return MAX_HEIGHT; } - - if (mDeviceConfig.isLargeScreen() && !mDeviceConfig.isSmallTablet() && !isOverflow) { - // the expanded view height on large tablets is calculated based on the shortest screen - // size and is the same in both portrait and landscape - int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom); - int shortestScreenSide = Math.min(mScreenRect.height(), mScreenRect.width()); - return shortestScreenSide - 2 * maxVerticalInset - mManageButtonHeight; - } - float desiredHeight = isOverflow ? mOverflowHeight : ((Bubble) bubble).getDesiredHeight(mContext); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index fdd30448995f..c1164fca22f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -123,7 +123,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private static final int EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS = SystemProperties.getInt( - "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 450); + "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400); private final Context mContext; private final SyncTransactionQueue mSyncTransactionQueue; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java index 835ebe2206ad..e5ae6e515566 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java @@ -16,10 +16,15 @@ package com.android.wm.shell.bubbles; +import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT; + import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static org.mockito.Mockito.mock; + import android.content.Intent; +import android.content.pm.ShortcutInfo; import android.graphics.Insets; import android.graphics.PointF; import android.graphics.Rect; @@ -226,7 +231,7 @@ public class BubblePositionerTest extends ShellTestCase { } @Test - public void testExpandedViewHeight_onLargeTablet() { + public void testGetExpandedViewHeight_max() { Insets insets = Insets.of(10, 20, 5, 15); Rect screenBounds = new Rect(0, 0, 1800, 2600); @@ -240,10 +245,91 @@ public class BubblePositionerTest extends ShellTestCase { Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName()); Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor()); + assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT); + } + + @Test + public void testGetExpandedViewHeight_customHeight_valid() { + Insets insets = Insets.of(10, 20, 5, 15); + Rect screenBounds = new Rect(0, 0, 1800, 2600); + + DeviceConfig deviceConfig = new ConfigBuilder() + .setLargeScreen() + .setInsets(insets) + .setScreenBounds(screenBounds) + .build(); + mPositioner.update(deviceConfig); + + final int minHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.bubble_expanded_default_height); + Bubble bubble = new Bubble("key", + mock(ShortcutInfo.class), + minHeight + 100 /* desiredHeight */, + 0 /* desiredHeightResId */, + "title", + 0 /* taskId */, + null /* locus */, + true /* isDismissable */, + directExecutor(), + mock(Bubbles.BubbleMetadataFlagListener.class)); + + // Ensure the height is the same as the desired value + assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo( + bubble.getDesiredHeight(mContext)); + } + + + @Test + public void testGetExpandedViewHeight_customHeight_tooSmall() { + Insets insets = Insets.of(10, 20, 5, 15); + Rect screenBounds = new Rect(0, 0, 1800, 2600); + + DeviceConfig deviceConfig = new ConfigBuilder() + .setLargeScreen() + .setInsets(insets) + .setScreenBounds(screenBounds) + .build(); + mPositioner.update(deviceConfig); + + Bubble bubble = new Bubble("key", + mock(ShortcutInfo.class), + 10 /* desiredHeight */, + 0 /* desiredHeightResId */, + "title", + 0 /* taskId */, + null /* locus */, + true /* isDismissable */, + directExecutor(), + mock(Bubbles.BubbleMetadataFlagListener.class)); + + // Ensure the height is the same as the minimum value + final int minHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.bubble_expanded_default_height); + assertThat(mPositioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight); + } + + @Test + public void testGetMaxExpandedViewHeight_onLargeTablet() { + Insets insets = Insets.of(10, 20, 5, 15); + Rect screenBounds = new Rect(0, 0, 1800, 2600); + + DeviceConfig deviceConfig = new ConfigBuilder() + .setLargeScreen() + .setInsets(insets) + .setScreenBounds(screenBounds) + .build(); + mPositioner.update(deviceConfig); + int manageButtonHeight = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height); - float expectedHeight = 1800 - 2 * 20 - manageButtonHeight; - assertThat(mPositioner.getExpandedViewHeight(bubble)).isWithin(0.1f).of(expectedHeight); + int pointerWidth = mContext.getResources().getDimensionPixelSize( + R.dimen.bubble_pointer_width); + int expandedViewPadding = mContext.getResources().getDimensionPixelSize(R + .dimen.bubble_expanded_view_padding); + float expectedHeight = 1800 - 2 * 20 - manageButtonHeight - pointerWidth + - expandedViewPadding * 2; + assertThat(((float) mPositioner.getMaxExpandedViewHeight(false /* isOverflow */))) + .isWithin(0.1f).of(expectedHeight); } /** diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp index c55066af3612..0275e4f13b3b 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.cpp +++ b/libs/hwui/jni/YuvToJpegEncoder.cpp @@ -2,6 +2,7 @@ #include "SkStream.h" #include "YuvToJpegEncoder.h" #include <ui/PixelFormat.h> +#include <utils/Errors.h> #include <hardware/hardware.h> #include "graphics_jni_helpers.h" @@ -295,7 +296,7 @@ void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) { } /////////////////////////////////////////////////////////////////////////////// -using namespace android::ultrahdr; +using namespace ultrahdr; ultrahdr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) { switch (aDataSpace & ADataSpace::STANDARD_MASK) { diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h index a3a322453be7..629f1e64726b 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.h +++ b/libs/hwui/jni/YuvToJpegEncoder.h @@ -108,7 +108,7 @@ public: * @param aDataSpace data space defined in data_space.h. * @return color gamut for JPEG/R. */ - static android::ultrahdr::ultrahdr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace); + static ultrahdr::ultrahdr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace); /** Map data space (defined in DataSpace.java and data_space.h) to the transfer function * used in JPEG/R @@ -117,8 +117,8 @@ public: * @param aDataSpace data space defined in data_space.h. * @return color gamut for JPEG/R. */ - static android::ultrahdr::ultrahdr_transfer_function findHdrTransferFunction( - JNIEnv* env, int aDataSpace); + static ultrahdr::ultrahdr_transfer_function findHdrTransferFunction(JNIEnv* env, + int aDataSpace); }; #endif // _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ diff --git a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm index 90041da529c0..24b4d2fd6487 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm @@ -14,8 +14,8 @@ # # Ukrainian keyboard layout. -# This is a typical Ukrainian PC keyboard layout. -# As an added convenience, English characters are accessible using ralt (Alt Gr). +# Based on PC enhanced Ukrainian layout with added Unicode keys based on +# the Linux one. # type OVERLAY @@ -25,32 +25,34 @@ map key 86 PLUS ### ROW 1 key GRAVE { - label: '\u0401' - base: '\u0451' - shift, capslock: '\u0401' - shift+capslock: '\u0451' - ralt: '`' + label: '\'' + base: '\'' + shift: '\u02bc' + ralt: '\u0301' ralt+shift: '~' } key 1 { label: '1' base: '1' - shift, ralt: '!' + shift: '!' + ralt: '\u00b9' } key 2 { label: '2' base: '2' shift: '"' - ralt: '@' + ralt: '\u00b2' + ralt+shift: '\u2019' } key 3 { label: '3' base: '3' shift: '\u2116' - ralt: '#' + ralt: '\u00a7' + ralt+shift: '\u20b4' } key 4 { @@ -58,60 +60,67 @@ key 4 { base: '4' shift: ';' ralt: '$' + ralt+shift: '\u20ac' } key 5 { label: '5' base: '5' - shift, ralt: '%' + shift: '%' + ralt: '\u00b0' } key 6 { label: '6' base: '6' shift: ':' - ralt: '^' + ralt: '<' } key 7 { label: '7' base: '7' shift: '?' - ralt: '&' + ralt: '>' } key 8 { label: '8' base: '8' - shift, ralt: '*' + shift: '*' + ralt: '\u2022' } key 9 { label: '9' base: '9' - shift, ralt: '(' + shift: '(' + ralt: '[' + ralt+shift: '{' } key 0 { label: '0' base: '0' - shift, ralt: ')' + shift: ')' + ralt: ']' + ralt+shift: '}' } key MINUS { label: '-' base: '-' shift: '_' - ralt: '-' - shift+ralt: '_' + ralt: '\u2014' + shift+ralt: '\u2013' } key EQUALS { label: '=' base: '=' shift: '+' - ralt: '=' - shift+ralt: '+' + ralt: '\u2260' + shift+ralt: '\u00b1' } ### ROW 2 @@ -121,6 +130,9 @@ key Q { base: '\u0439' shift, capslock: '\u0419' shift+capslock: '\u0439' + ralt: '\u0458' + ralt+shift, ralt+capslock: '\u0408' + ralt+shift+capslock: '\u0458' } key W { @@ -128,6 +140,9 @@ key W { base: '\u0446' shift, capslock: '\u0426' shift+capslock: '\u0446' + ralt: '\u045f' + ralt+shift, ralt+capslock: '\u040f' + ralt+shift+capslock: '\u045f' } key E { @@ -135,6 +150,9 @@ key E { base: '\u0443' shift, capslock: '\u0423' shift+capslock: '\u0443' + ralt: '\u045e' + ralt+shift, ralt+capslock: '\u040e' + ralt+shift+capslock: '\u045e' } key R { @@ -142,6 +160,7 @@ key R { base: '\u043a' shift, capslock: '\u041a' shift+capslock: '\u043a' + ralt: '\u00ae' } key T { @@ -149,6 +168,9 @@ key T { base: '\u0435' shift, capslock: '\u0415' shift+capslock: '\u0435' + ralt: '\u0451' + ralt+shift, ralt+capslock: '\u0401' + ralt+shift+capslock: '\u0451' } key Y { @@ -156,6 +178,9 @@ key Y { base: '\u043d' shift, capslock: '\u041d' shift+capslock: '\u043d' + ralt: '\u045a' + ralt+shift, ralt+capslock: '\u040a' + ralt+shift+capslock: '\u045a' } key U { @@ -164,8 +189,8 @@ key U { shift, capslock: '\u0413' shift+capslock: '\u0433' ralt: '\u0491' - shift+ralt, capslock+ralt: '\u0490' - shift+capslock+ralt: '\u0491' + ralt+shift, ralt+capslock: '\u0490' + ralt+shift+capslock: '\u0491' } key I { @@ -201,6 +226,9 @@ key RIGHT_BRACKET { base: '\u0457' shift, capslock: '\u0407' shift+capslock: '\u0457' + ralt: '\u044a' + ralt+shift, ralt+capslock: '\u042a' + ralt+shift+capslock: '\u044a' } ### ROW 3 @@ -217,6 +245,9 @@ key S { base: '\u0456' shift, capslock: '\u0406' shift+capslock: '\u0456' + ralt: '\u044b' + ralt+shift, ralt+capslock: '\u042b' + ralt+shift+capslock: '\u044b' } key D { @@ -259,6 +290,9 @@ key K { base: '\u043b' shift, capslock: '\u041b' shift+capslock: '\u043b' + ralt: '\u0459' + ralt+shift, ralt+capslock: '\u0409' + ralt+shift+capslock: '\u0459' } key L { @@ -266,6 +300,9 @@ key L { base: '\u0434' shift, capslock: '\u0414' shift+capslock: '\u0434' + ralt: '\u0452' + ralt+shift, ralt+capslock: '\u0402' + ralt+shift+capslock: '\u0452' } key SEMICOLON { @@ -282,15 +319,18 @@ key APOSTROPHE { base: '\u0454' shift, capslock: '\u0404' shift+capslock: '\u0454' - ralt: '\'' - ralt+shift: '"' + ralt: '\u044d' + ralt+shift, ralt+capslock: '\u042d' + ralt+shift+capslock: '\u044d' } key BACKSLASH { label: '\\' base: '\\' shift: '/' - ralt: '|' + ralt: '\u0491' + ralt+shift, ralt+capslock: '\u0490' + ralt+shift+capslock: '\u0491' } ### ROW 4 @@ -316,6 +356,9 @@ key X { base: '\u0447' shift, capslock: '\u0427' shift+capslock: '\u0447' + ralt: '\u045b' + ralt+shift, ralt+capslock: '\u040b' + ralt+shift+capslock: '\u045b' } key C { @@ -323,6 +366,7 @@ key C { base: '\u0441' shift, capslock: '\u0421' shift+capslock: '\u0441' + ralt: '\u00a9' } key V { @@ -344,6 +388,7 @@ key N { base: '\u0442' shift, capslock: '\u0422' shift+capslock: '\u0442' + ralt: '\u2122' } key M { @@ -358,8 +403,8 @@ key COMMA { base: '\u0431' shift, capslock: '\u0411' shift+capslock: '\u0431' - ralt: ',' - ralt+shift: '<' + ralt: '\u00ab' + ralt+shift: '\u201e' } key PERIOD { @@ -367,8 +412,8 @@ key PERIOD { base: '\u044e' shift, capslock: '\u042e' shift+capslock: '\u044e' - ralt: '.' - ralt+shift: '>' + ralt: '\u00bb' + ralt+shift: '\u201c' } key SLASH { @@ -376,5 +421,5 @@ key SLASH { base: '.' shift: ',' ralt: '/' - ralt+shift: '?' + ralt+shift: '\u2026' } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index ed5654d4f259..ec50323dd91d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -2432,9 +2432,7 @@ class DatabaseHelper extends SQLiteOpenHelper { R.bool.def_auto_time_zone); // Sync timezone to NITZ loadSetting(stmt, Settings.Global.STAY_ON_WHILE_PLUGGED_IN, - ("1".equals(SystemProperties.get("ro.boot.qemu")) - || res.getBoolean(R.bool.def_stay_on_while_plugged_in)) - ? 1 : 0); + res.getBoolean(R.bool.def_stay_on_while_plugged_in) ? 1 : 0); loadIntegerSetting(stmt, Settings.Global.WIFI_SLEEP_POLICY, R.integer.def_wifi_sleep_policy); diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 42ba6431822e..53755916f83e 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -53,6 +53,7 @@ class DefaultClockController( private val resources: Resources, private val settings: ClockSettings?, private val hasStepClockAnimation: Boolean = false, + private val migratedClocks: Boolean = false, ) : ClockController { override val smallClock: DefaultClockFaceController override val largeClock: LargeClockFaceController @@ -195,6 +196,10 @@ class DefaultClockController( } override fun recomputePadding(targetRegion: Rect?) { + // TODO(b/310989341): remove after changing migrate_clocks_to_blueprint to aconfig + if (migratedClocks) { + return + } // We center the view within the targetRegion instead of within the parent // view by computing the difference and adding that to the padding. val lp = view.getLayoutParams() as FrameLayout.LayoutParams diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index dd52e39488ac..f819da5b53de 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -32,7 +32,8 @@ class DefaultClockProvider( val ctx: Context, val layoutInflater: LayoutInflater, val resources: Resources, - val hasStepClockAnimation: Boolean = false + val hasStepClockAnimation: Boolean = false, + val migratedClocks: Boolean = false ) : ClockProvider { override fun getClocks(): List<ClockMetadata> = listOf(ClockMetadata(DEFAULT_CLOCK_ID)) @@ -47,6 +48,7 @@ class DefaultClockProvider( resources, settings, hasStepClockAnimation, + migratedClocks, ) } diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md index bd0b4abbeb34..4313f44c316b 100644 --- a/packages/SystemUI/docs/qs-tiles.md +++ b/packages/SystemUI/docs/qs-tiles.md @@ -4,25 +4,37 @@ ## About this document -This document is a more or less comprehensive summary of the state and infrastructure used by Quick Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and how SystemUI manages and displays tiles, among other topics. +This document is a more or less comprehensive summary of the state and infrastructure used by Quick +Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and +how SystemUI manages and displays tiles, among other topics. ## What are Quick Settings Tiles? -Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to toggle many settings. This is opened by expanding the notification drawer twice (or once when phone is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications before expanding twice and contains some of the toggles with no secondary line. +Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to +toggle many settings. This is opened by expanding the notification drawer twice (or once when phone +is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications +before expanding twice and contains some of the toggles with no secondary line. -Each of these toggles that appear either in QS or QQS are called Quick Settings Tiles (or tiles for short). They allow the user to enable or disable settings quickly and sometimes provides access to more comprehensive settings pages. +Each of these toggles that appear either in QS or QQS are called Quick Settings Tiles (or tiles for +short). They allow the user to enable or disable settings quickly and sometimes provides access to +more comprehensive settings pages. The following image shows QQS on the left and QS on the right, with the tiles highlighted.  -QS Tiles usually depend on one or more Controllers that bind the tile with the necessary service. Controllers are obtained by the backend and used for communication between the user and the device. +QS Tiles usually depend on one or more Controllers that bind the tile with the necessary service. +Controllers are obtained by the backend and used for communication between the user and the device. ### A note on multi-user support -All the classes described in this document that live inside SystemUI are only instantiated in the process of user 0. The different controllers that back the QS Tiles (also instantiated just in user 0) are user aware and provide an illusion of different instances for different users. +All the classes described in this document that live inside SystemUI are only instantiated in the +process of user 0. The different controllers that back the QS Tiles (also instantiated just in user +0) are user aware and provide an illusion of different instances for different users. -For an example on this, see [`RotationLockController`](/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java). This controller for the `RotationLockTile` listens to changes in all users. +For an example on this, +see [`RotationLockController`](/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java). +This controller for the `RotationLockTile` listens to changes in all users. ## What are tiles made of? @@ -30,104 +42,162 @@ For an example on this, see [`RotationLockController`](/packages/SystemUI/src/co QS Tiles are composed of the following backend classes. -* [`QSTile`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java): Interface providing common behavior for all Tiles. This class also contains some useful utility classes needed for the tiles. - * `Icon`: Defines the basic interface for an icon as used by the tiles. - * `State`: Encapsulates the state of the Tile in order to communicate between the backend and the UI. -* [`QSTileImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java): Abstract implementation of `QSTile`, providing basic common behavior for all tiles. Also implements extensions for different types of `Icon`. All tiles currently defined in SystemUI subclass from this implementation. -* [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles): Each tile from SystemUI is defined here by a class that extends `QSTileImpl`. These implementations connect to corresponding controllers. The controllers serve two purposes: - * track the state of the device and notify the tile when a change has occurred (for example, bluetooth connected to a device) - * accept actions from the tiles to modify the state of the phone (for example, enablind and disabling wifi). -* [`CustomTile`](/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java): Equivalent to the tiles in the previous item, but used for 3rd party tiles. In depth information to be found in [`CustomTile`](#customtile) - -All the elements in SystemUI that work with tiles operate on `QSTile` or the interfaces defined in it. However, all the current implementations of tiles in SystemUI subclass from `QSTileImpl`, as it takes care of many common situations. Throughout this document, we will focus on `QSTileImpl` as examples of tiles. - -The interfaces in `QSTile` as well as other interfaces described in this document can be used to implement plugins to add additional tiles or different behavior. For more information, see [plugins.md](plugins.md) +* [`QSTile`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java): Interface + providing common behavior for all Tiles. This class also contains some useful utility classes + needed for the tiles. + * `Icon`: Defines the basic interface for an icon as used by the tiles. + * `State`: Encapsulates the state of the Tile in order to communicate between the backend and + the UI. +* [`QSTileImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java): Abstract + implementation of `QSTile`, providing basic common behavior for all tiles. Also implements + extensions for different types of `Icon`. All tiles currently defined in SystemUI subclass from + this implementation. +* [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles): + Each tile from SystemUI is defined here by a class that extends `QSTileImpl`. These + implementations connect to corresponding controllers. The controllers serve two purposes: + * track the state of the device and notify the tile when a change has occurred (for example, + bluetooth connected to a device) + * accept actions from the tiles to modify the state of the phone (for example, enablind and + disabling wifi). +* [`CustomTile`](/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java): + Equivalent to the tiles in the previous item, but used for 3rd party tiles. In depth information + to be found in [`CustomTile`](#customtile) + +All the elements in SystemUI that work with tiles operate on `QSTile` or the interfaces defined in +it. However, all the current implementations of tiles in SystemUI subclass from `QSTileImpl`, as it +takes care of many common situations. Throughout this document, we will focus on `QSTileImpl` as +examples of tiles. + +The interfaces in `QSTile` as well as other interfaces described in this document can be used to +implement plugins to add additional tiles or different behavior. For more information, +see [plugins.md](plugins.md) #### Tile State -Each tile has an associated `State` object that is used to communicate information to the corresponding view. The base class `State` has (among others) the following fields: +Each tile has an associated `State` object that is used to communicate information to the +corresponding view. The base class `State` has (among others) the following fields: * **`state`**: one of `Tile#STATE_UNAVAILABLE`, `Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`. * **`icon`**; icon to display. It may depend on the current state. * **`label`**: usually the name of the tile. * **`secondaryLabel`**: text to display in a second line. Usually extra state information. * **`contentDescription`** -* **`expandedAccessibilityClassName`**: usually `Switch.class.getName()` for boolean Tiles. This will make screen readers read the current state of the tile as well as the new state when it's toggled. For this, the Tile has to use `BooleanState`. -* **`handlesLongClick`**: whether the Tile will handle long click. If it won't, it should be set to `false` so it will not be announced for accessibility. +* **`expandedAccessibilityClassName`**: usually `Switch.class.getName()` for boolean Tiles. This + will make screen readers read the current state of the tile as well as the new state when it's + toggled. For this, the Tile has to use `BooleanState`. +* **`handlesLongClick`**: whether the Tile will handle long click. If it won't, it should be set + to `false` so it will not be announced for accessibility. Setting any of these fields during `QSTileImpl#handleUpdateState` will update the UI after it. -Additionally. `BooleanState` has a `value` boolean field that usually would be set to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along with `expandedAccessibilityClassName`. +Additionally. `BooleanState` has a `value` boolean field that usually would be set +to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along +with `expandedAccessibilityClassName`. #### SystemUI tiles -Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common functions and leaves others to be implemented by each tile, in particular those that determine how to handle different events (refresh, click, etc.). +Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common +functions and leaves others to be implemented by each tile, in particular those that determine how +to handle different events (refresh, click, etc.). -For more information on how to implement a tile in SystemUI, see [Implementing a SystemUI tile](#implementing-a-systemui-tile). +For more information on how to implement a tile in SystemUI, +see [Implementing a SystemUI tile](#implementing-a-systemui-tile). ### Tile views -Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated after the backend updates the `State` using `QSTileImpl#handleUpdateState`. - -* **[`QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: Abstract class that provides basic Tile functionality. These allows external [Factories](#qsfactory) to create Tiles. -* **[`QSTileViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.java)**: Implementation of `QSTileView`. It takes care of the following: - * Holding the icon - * Background color and shape - * Ripple - * Click listening - * Labels +Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated +after the backend updates the `State` using `QSTileImpl#handleUpdateState`. + +* **[`QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: + Abstract class that provides basic Tile functionality. These allows + external [Factories](#qsfactory) to create Tiles. +* **[`QSTileViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.java) + **: Implementation of `QSTileView`. It takes care of the following: + * Holding the icon + * Background color and shape + * Ripple + * Click listening + * Labels * **[`QSIconView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java)** -* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)** +* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java) + ** #### QSIconView and QSIconViewImpl -`QSIconView` is an interface that define the basic actions that icons have to respond to. Its base implementation in SystemUI is `QSIconViewImpl` and it and its subclasses are used by all QS tiles. +`QSIconView` is an interface that define the basic actions that icons have to respond to. Its base +implementation in SystemUI is `QSIconViewImpl` and it and its subclasses are used by all QS tiles. -This `ViewGroup` is a container for the icon used in each tile. It has methods to apply the current `State` of the tile, modifying the icon (color and animations). Classes that inherit from this can add other details that are modified when the `State` changes. +This `ViewGroup` is a container for the icon used in each tile. It has methods to apply the +current `State` of the tile, modifying the icon (color and animations). Classes that inherit from +this can add other details that are modified when the `State` changes. -Each `QSTileImpl` can specify that they use a particular implementation of this class when creating an icon. +Each `QSTileImpl` can specify that they use a particular implementation of this class when creating +an icon. ### How are the backend and the views related? -The backend of the tiles (all the implementations of `QSTileImpl`) communicate with the views by using a `State`. The backend populates the state, and then the view maps the state to a visual representation. +The backend of the tiles (all the implementations of `QSTileImpl`) communicate with the views by +using a `State`. The backend populates the state, and then the view maps the state to a visual +representation. -It's important to notice that the state of the tile (internal or visual) is not directly modified by a user action like clicking on the tile. Instead, acting on a tile produces internal state changes on the device, and those trigger the changes on the tile state and UI. +It's important to notice that the state of the tile (internal or visual) is not directly modified by +a user action like clicking on the tile. Instead, acting on a tile produces internal state changes +on the device, and those trigger the changes on the tile state and UI. -When a container for tiles (`QuickQSPanel` or `QSPanel`) has to display tiles, they create a [`TileRecord`](/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java). This associates the corresponding `QSTile` with its `QSTileView`, doing the following: +When a container for tiles (`QuickQSPanel` or `QSPanel`) has to display tiles, they create +a [`TileRecord`](/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java). This associates the +corresponding `QSTile` with its `QSTileView`, doing the following: * Create the corresponding `QSTileView` to display in that container. -* Create a callback for `QSTile` to call when its state changes. Note that a single tile will normally have up to two callbacks: one for QS and one for QQS. +* Create a callback for `QSTile` to call when its state changes. Note that a single tile will + normally have up to two callbacks: one for QS and one for QQS. #### Life of a tile click -This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the device (for example, changes from Settings) will trigger this process starting in step 3. Throughout this section, we assume that we are dealing with a `QSTileImpl`. +This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the +device (for example, changes from Settings) will trigger this process starting in step 3. Throughout +this section, we assume that we are dealing with a `QSTileImpl`. 1. User clicks on tile. The following calls happen in sequence: - 1. `QSTileViewImpl#onClickListener`. - 2. `QSTile#click`. - 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the associated controller. + 1. `QSTileViewImpl#onClickListener`. + 2. `QSTile#click`. + 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the + associated controller. 2. State in the device changes. This is normally outside of SystemUI's control. -3. Controller receives a callback (or `Intent`) indicating the change in the device. The following calls happen: - 1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the new state. - 2. `QSTileImpl#handleRefreshState` -4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller. -5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`. -6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state into the view: - * The tile colors change to match the new state. - * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to the view. - * The tile labels change to match the new state. +3. Controller receives a callback (or `Intent`) indicating the change in the device. The following + calls happen: + 1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the + new state. + 2. `QSTileImpl#handleRefreshState` +4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This + information can be obtained both from the `Object` passed to `refreshState` as well as from the + controller. +5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called. + This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the + new `State`. +6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method + maps the state into the view: + * The tile colors change to match the new state. + * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to + the view. + * The tile labels change to match the new state. ## Third party tiles (TileService) -A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This is implemented by developers subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and interacting with its API. +A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This +is implemented by developers +subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and +interacting with its API. ### API classes -The classes that define the public API are in [core/java/android/service/quicksettings](/core/java/android/service/quicksettings). +The classes that define the public API are +in [core/java/android/service/quicksettings](/core/java/android/service/quicksettings). #### Tile -Parcelable class used to communicate information about the state between the external app and SystemUI. The class supports the following fields: +Parcelable class used to communicate information about the state between the external app and +SystemUI. The class supports the following fields: * Label * Subtitle @@ -135,18 +205,25 @@ Parcelable class used to communicate information about the state between the ext * State (`Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`, `Tile#STATE_UNAVAILABLE`) * Content description -Additionally, it provides a method to notify SystemUI that the information may have changed and the tile should be refreshed. +Additionally, it provides a method to notify SystemUI that the information may have changed and the +tile should be refreshed. #### TileService -This is an abstract Service that needs to be implemented by the developer. The Service manifest must have the permission `android.permission.BIND_QUICK_SETTINGS_TILE` and must respond to the action `android.service.quicksettings.action.QS_TILE`. This will allow SystemUI to find the available tiles and display them to the user. +This is an abstract Service that needs to be implemented by the developer. The Service manifest must +have the permission `android.permission.BIND_QUICK_SETTINGS_TILE` and must respond to the +action `android.service.quicksettings.action.QS_TILE`. This will allow SystemUI to find the +available tiles and display them to the user. -The implementer is responsible for creating the methods that will respond to the following calls from SystemUI: +The implementer is responsible for creating the methods that will respond to the following calls +from SystemUI: * **`onTileAdded`**: called when the tile is added to QS. * **`onTileRemoved`**: called when the tile is removed from QS. -* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of the window when calling `getQSTile` is safe and will provide the correct object. -* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This marks the end of the window described in `onStartListening`. +* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of + the window when calling `getQSTile` is safe and will provide the correct object. +* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This + marks the end of the window described in `onStartListening`. * **`onClick`**: called when the user clicks on the tile. Additionally, the following final methods are provided: @@ -155,7 +232,8 @@ Additionally, the following final methods are provided: public final Tile getQsTile() ``` - Provides the tile object that can be modified. This should only be called in the window between `onStartListening` and `onStopListening`. + Provides the tile object that can be modified. This should only be called in the window + between `onStartListening` and `onStopListening`. * ```java public final boolean isLocked() @@ -163,13 +241,15 @@ Additionally, the following final methods are provided: public final boolean isSecure() ``` - Provide information about the secure state of the device. This can be used by the tile to accept or reject actions on the tile. + Provide information about the secure state of the device. This can be used by the tile to accept + or reject actions on the tile. * ```java public final void unlockAndRun(Runnable) ``` - May prompt the user to unlock the device if locked. Once the device is unlocked, it runs the given `Runnable`. + May prompt the user to unlock the device if locked. Once the device is unlocked, it runs the + given `Runnable`. * ```java public final void showDialog(Dialog) @@ -179,72 +259,110 @@ Additionally, the following final methods are provided: ##### Binding -When the Service is bound, a callback Binder is provided by SystemUI for all the callbacks, as well as an identifier token (`Binder`). This token is used in the callbacks to identify this `TileService` and match it to the corresponding tile. +When the Service is bound, a callback Binder is provided by SystemUI for all the callbacks, as well +as an identifier token (`Binder`). This token is used in the callbacks to identify +this `TileService` and match it to the corresponding tile. -The tiles are bound once immediately on creation. After that, the tile is bound whenever it should start listening. When the panels are closed, and the tile is set to stop listening, it will be unbound after a delay of `TileServiceManager#UNBIND_DELAY` (30s), if it's not set to listening again. +The tiles are bound once immediately on creation. After that, the tile is bound whenever it should +start listening. When the panels are closed, and the tile is set to stop listening, it will be +unbound after a delay of `TileServiceManager#UNBIND_DELAY` (30s), if it's not set to listening +again. ##### Active tile -A `TileService` can be declared as an active tile by adding specific meta-data to its manifest (see [TileService#META_DATA_ACTIVE_TILE](https://developer.android.com/reference/android/service/quicksettings/TileService#META_DATA_ACTIVE_TILE)). In this case, it won't receive a call of `onStartListening` when QS is opened. Instead, the tile must request listening status by making a call to `TileService#requestListeningState` with its component name. This will initiate a window that will last until the tile is updated. +A `TileService` can be declared as an active tile by adding specific meta-data to its manifest ( +see [TileService#META_DATA_ACTIVE_TILE](https://developer.android.com/reference/android/service/quicksettings/TileService#META_DATA_ACTIVE_TILE)). +In this case, it won't receive a call of `onStartListening` when QS is opened. Instead, the tile +must request listening status by making a call to `TileService#requestListeningState` with its +component name. This will initiate a window that will last until the tile is updated. The tile will also be granted listening status if it's clicked by the user. ### SystemUI classes -The following sections describe the classes that live in SystemUI to support third party tiles. These classes live in [SystemUI/src/com/android/systemui/qs/external](/packages/SystemUI/src/com/android/systemui/qs/external/) +The following sections describe the classes that live in SystemUI to support third party tiles. +These classes live +in [SystemUI/src/com/android/systemui/qs/external](/packages/SystemUI/src/com/android/systemui/qs/external/) #### CustomTile -This class is an subclass of `QSTileImpl` to be used with third party tiles. It provides similar behavior to SystemUI tiles as well as handling exclusive behavior like lifting default icons and labels from the application manifest. +This class is an subclass of `QSTileImpl` to be used with third party tiles. It provides similar +behavior to SystemUI tiles as well as handling exclusive behavior like lifting default icons and +labels from the application manifest. #### TileServices -This class is the central controller for all tile services that are currently in Quick Settings as well as provides the support for starting new ones. It is also an implementation of the `Binder` that receives all calls from current `TileService` components and dispatches them to SystemUI or the corresponding `CustomTile`. +This class is the central controller for all tile services that are currently in Quick Settings as +well as provides the support for starting new ones. It is also an implementation of the `Binder` +that receives all calls from current `TileService` components and dispatches them to SystemUI or the +corresponding `CustomTile`. -Whenever a binder call is made to this class, it matches the corresponding token assigned to the `TileService` with the `ComponentName` and verifies that the call comes from the right UID to prevent spoofing. +Whenever a binder call is made to this class, it matches the corresponding token assigned to +the `TileService` with the `ComponentName` and verifies that the call comes from the right UID to +prevent spoofing. -As this class is the only one that's aware of every `TileService` that's currently bound, it is also in charge of requesting some to be unbound whenever there is a low memory situation. +As this class is the only one that's aware of every `TileService` that's currently bound, it is also +in charge of requesting some to be unbound whenever there is a low memory situation. #### TileLifecycleManager -This class is in charge of binding and unbinding to a particular `TileService` when necessary, as well as sending the corresponding binder calls. It does not decide whether the tile should be bound or unbound, unless it's requested to process a message. It additionally handles errors in the `Binder` as well as changes in the corresponding component (like updates and enable/disable). +This class is in charge of binding and unbinding to a particular `TileService` when necessary, as +well as sending the corresponding binder calls. It does not decide whether the tile should be bound +or unbound, unless it's requested to process a message. It additionally handles errors in +the `Binder` as well as changes in the corresponding component (like updates and enable/disable). -The class has a queue that stores requests while the service is not bound, to be processed as soon as the service is bound. +The class has a queue that stores requests while the service is not bound, to be processed as soon +as the service is bound. -Each `TileService` gets assigned an exclusive `TileLifecycleManager` when its corresponding tile is added to the set of current ones and kept as long as the tile is available to the user. +Each `TileService` gets assigned an exclusive `TileLifecycleManager` when its corresponding tile is +added to the set of current ones and kept as long as the tile is available to the user. #### TileServiceManager -Each instance of this class is an intermediary between the `TileServices` controller and a `TileLifecycleManager` corresponding to a particular `TileService`. +Each instance of this class is an intermediary between the `TileServices` controller and +a `TileLifecycleManager` corresponding to a particular `TileService`. This class handles management of the service, including: * Deciding when to bind and unbind, requesting it to the `TileLifecycleManager`. * Relaying messages to the `TileService` through the `TileLifecycleManager`. * Determining the service's bind priority (to deal with OOM situations). -* Detecting when the package/component has been removed in order to remove the tile and references to it. +* Detecting when the package/component has been removed in order to remove the tile and references + to it. ## How are tiles created/instantiated? -This section describes the classes that aid in the creation of each tile as well as the complete lifecycle of a tile. First we describe two important interfaces/classes. +This section describes the classes that aid in the creation of each tile as well as the complete +lifecycle of a tile. First we describe two important interfaces/classes. ### QSTileHost -This class keeps track of the tiles selected by the current user (backed in the Secure Setting `sysui_qs_tiles`) to be displayed in Quick Settings. Whenever the value of this setting changes (or on device start), the whole list of tiles is read. This is compared with the current tiles, destroying unnecessary ones and creating needed ones. +This class keeps track of the tiles selected by the current user (backed in the Secure +Setting `sysui_qs_tiles`) to be displayed in Quick Settings. Whenever the value of this setting +changes (or on device start), the whole list of tiles is read. This is compared with the current +tiles, destroying unnecessary ones and creating needed ones. -It additionally provides a point of communication between the tiles and the StatusBar, for example to open it and collapse it. And a way for the StatusBar service to add tiles (only works for `CustomTile`). +It additionally provides a point of communication between the tiles and the StatusBar, for example +to open it and collapse it. And a way for the StatusBar service to add tiles (only works +for `CustomTile`). #### Tile specs -Each single tile is identified by a spec, which is a unique String for that type of tile. The current tiles are stored as a Setting string of comma separated values of these specs. Additionally, the default tiles (that appear on a fresh system) configuration value is stored likewise. +Each single tile is identified by a spec, which is a unique String for that type of tile. The +current tiles are stored as a Setting string of comma separated values of these specs. Additionally, +the default tiles (that appear on a fresh system) configuration value is stored likewise. -SystemUI tile specs are usually a single simple word identifying the tile (like `wifi` or `battery`). Custom tile specs are always a string of the form `custom(...)` where the ellipsis is a flattened String representing the `ComponentName` for the corresponding `TileService`. +SystemUI tile specs are usually a single simple word identifying the tile (like `wifi` +or `battery`). Custom tile specs are always a string of the form `custom(...)` where the ellipsis is +a flattened String representing the `ComponentName` for the corresponding `TileService`. ### QSFactory -This interface provides a way of creating tiles and views from a spec. It can be used in plugins to provide different definitions for tiles. +This interface provides a way of creating tiles and views from a spec. It can be used in plugins to +provide different definitions for tiles. -In SystemUI there is only one implementation of this factory and that is the default factory (`QSFactoryImpl`) in `QSTileHost`. +In SystemUI there is only one implementation of this factory and that is the default +factory (`QSFactoryImpl`) in `QSTileHost`. #### QSFactoryImpl @@ -254,87 +372,145 @@ This class implements two methods as specified in the `QSFactory` interface: public QSTile createTile(String) ``` - Creates a tile (backend) from a given spec. The factory has providers for all of the SystemUI tiles, returning one when the correct spec is used. + Creates a tile (backend) from a given spec. The factory has providers for all of the SystemUI + tiles, returning one when the correct spec is used. - If the spec is not recognized but it has the `custom(` prefix, the factory tries to create a `CustomTile` for the component in the spec. This could fail (the component is not a valid `TileService` or is not enabled) and will be detected later when the tile is polled to determine if it's available. + If the spec is not recognized but it has the `custom(` prefix, the factory tries to create + a `CustomTile` for the component in the spec. This could fail (the component is not a + valid `TileService` or is not enabled) and will be detected later when the tile is polled to + determine if it's available. * ```java public QSTileView createTileView(QSTile, boolean) ``` - Creates a view for the corresponding `QSTile`. The second parameter determines if the view that is created should be a collapsed one (for using in QQS) or not (for using in QS). + Creates a view for the corresponding `QSTile`. The second parameter determines if the view that is + created should be a collapsed one (for using in QQS) or not (for using in QS). ### Lifecycle of a Tile -We describe first the parts of the lifecycle that are common to SystemUI tiles and third party tiles. Following that, there will be a section with the steps that are exclusive to third party tiles. - -1. The tile is added through the QS customizer by the user. This will immediately save the new list of tile specs to the Secure Setting `sysui_qs_tiles`. This step could also happend if `StatusBar` adds tiles (either through adb, or through its service interface as with the `DevelopmentTiles`). -2. This triggers a "setting changed" that is caught by `QSTileHost`. This class processes the new value of the setting and finds out that there is a new spec in the list. Alternatively, when the device is booted, all tiles in the setting are considered as "new". -3. `QSTileHost` calls all the available `QSFactory` classes that it has registered in order to find the first one that will be able to create a tile with that spec. Assume that `QSFactoryImpl` managed to create the tile, which is some implementation of `QSTile` (either a SystemUI subclass of `QSTileImpl` or a `CustomTile`). If the tile is available, it's stored in a map and things proceed forward. -4. `QSTileHost` calls its callbacks indicating that the tiles have changed. In particular, `QSPanel` and `QuickQSPanel` receive this call with the full list of tiles. We will focus on these two classes. -5. For each tile in this list, a `QSTileView` is created (collapsed or expanded) and attached to a `TileRecord` containing the tile backend and the view. Additionally: - * a callback is attached to the tile to communicate between the backend and the view or the panel. +We describe first the parts of the lifecycle that are common to SystemUI tiles and third party +tiles. Following that, there will be a section with the steps that are exclusive to third party +tiles. + +1. The tile is added through the QS customizer by the user. This will immediately save the new list + of tile specs to the Secure Setting `sysui_qs_tiles`. This step could also happend if `StatusBar` + adds tiles (either through adb, or through its service interface as with the `DevelopmentTiles`). +2. This triggers a "setting changed" that is caught by `QSTileHost`. This class processes the new + value of the setting and finds out that there is a new spec in the list. Alternatively, when the + device is booted, all tiles in the setting are considered as "new". +3. `QSTileHost` calls all the available `QSFactory` classes that it has registered in order to find + the first one that will be able to create a tile with that spec. Assume that `QSFactoryImpl` + managed to create the tile, which is some implementation of `QSTile` (either a SystemUI subclass + of `QSTileImpl` or a `CustomTile`). If the tile is available, it's stored in a map and things + proceed forward. +4. `QSTileHost` calls its callbacks indicating that the tiles have changed. In particular, `QSPanel` + and `QuickQSPanel` receive this call with the full list of tiles. We will focus on these two + classes. +5. For each tile in this list, a `QSTileView` is created (collapsed or expanded) and attached to + a `TileRecord` containing the tile backend and the view. Additionally: + * a callback is attached to the tile to communicate between the backend and the view or the + panel. * the click listeners in the tile are attached to those of the view. 6. The tile view is added to the corresponding layout. -When the tile is removed from the list of current tiles, all these classes are properly disposed including removing the callbacks and making sure that the backends remove themselves from the controllers they were listening to. +When the tile is removed from the list of current tiles, all these classes are properly disposed +including removing the callbacks and making sure that the backends remove themselves from the +controllers they were listening to. #### Lifecycle of a CustomTile -In step 3 of the previous process, when a `CustomTile` is created, additional steps are taken to ensure the proper binding to the service as described in [Third party tiles (TileService)](#third-party-tiles-tileservice). +In step 3 of the previous process, when a `CustomTile` is created, additional steps are taken to +ensure the proper binding to the service as described +in [Third party tiles (TileService)](#third-party-tiles-tileservice). -1. The `CustomTile` obtains the `TileServices` class from the `QSTileHost` and request the creation of a `TileServiceManager` with its token. As the spec for the `CustomTile` contains the `ComponentName` of the associated service, this can be used to bind to it. -2. The `TileServiceManager` creates its own `TileLifecycleManager` to take care of binding to the service. -3. `TileServices` creates maps between the token, the `CustomTile`, the `TileServiceManager`, the token and the `ComponentName`. +1. The `CustomTile` obtains the `TileServices` class from the `QSTileHost` and request the creation + of a `TileServiceManager` with its token. As the spec for the `CustomTile` contains + the `ComponentName` of the associated service, this can be used to bind to it. +2. The `TileServiceManager` creates its own `TileLifecycleManager` to take care of binding to the + service. +3. `TileServices` creates maps between the token, the `CustomTile`, the `TileServiceManager`, the + token and the `ComponentName`. ## Implementing a tile -This section describes necessary and recommended steps when implementing a Quick Settings tile. Some of them are optional and depend on the requirements of the tile. +This section describes necessary and recommended steps when implementing a Quick Settings tile. Some +of them are optional and depend on the requirements of the tile. ### Implementing a SystemUI tile -1. Create a class (preferably in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles)) implementing `QSTileImpl` with a particular type of `State` as a parameter. -2. Create an injectable constructor taking a `QSHost` and whichever classes are needed for the tile's operation. Normally this would be other SystemUI controllers. -3. Implement the methods described in [Abstract methods in QSTileImpl](#abstract-methods-in-qstileimpl). Look at other tiles for help. Some considerations to have in mind: - * If the tile will not support long click (like the `FlashlightTile`), set `state.handlesLongClick` to `false` (maybe in `newTileState`). +1. Create a class (preferably + in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles)) + implementing `QSTileImpl` with a particular type of `State` as a parameter. +2. Create an injectable constructor taking a `QSHost` and whichever classes are needed for the + tile's operation. Normally this would be other SystemUI controllers. +3. Implement the methods described + in [Abstract methods in QSTileImpl](#abstract-methods-in-qstileimpl). Look at other tiles for + help. Some considerations to have in mind: + * If the tile will not support long click (like the `FlashlightTile`), + set `state.handlesLongClick` to `false` (maybe in `newTileState`). * Changes to the tile state (either from controllers or from clicks) should call `refreshState`. - * Use only `handleUpdateState` to modify the values of the state to the new ones. This can be done by polling controllers or through the `arg` parameter. - * If the controller is not a `CallbackController`, respond to `handleSetListening` by attaching/dettaching from controllers. + * Use only `handleUpdateState` to modify the values of the state to the new ones. This can be + done by polling controllers or through the `arg` parameter. + * If the controller is not a `CallbackController`, respond to `handleSetListening` by + attaching/dettaching from controllers. * Implement `isAvailable` so the tile will not be created when it's not necessary. -4. Either create a new feature module or find an existing related feature module and add the following binding method: +4. Either create a new feature module or find an existing related feature module and add the + following binding method: * ```kotlin @Binds @IntoMap @StringKey(YourNewTile.TILE_SPEC) // A unique word that will map to YourNewTile fun bindYourNewTile(yourNewTile: YourNewTile): QSTileImpl<*> ``` -5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles. -6. In [SystemUI/res/values/tiles_states_strings.xml](/packages/SystemUI/res/values/tiles_states_strings.xml), add a new array for your tile. The name has to be `tile_states_<spec>`. Use a good description to help the translators. -7. In [`SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt), add a new element to the map in `SubtitleArrayMapping` corresponding to the resource created in the previous step. +5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), + modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, + add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles + that SystemUI knows how to create (to show to the user in the customization screen). The second + one contains only the default tiles that the user will experience on a fresh boot or after they + reset their tiles. +6. +In [SystemUI/res/values/tiles_states_strings.xml](/packages/SystemUI/res/values/tiles_states_strings.xml), +add a new array for your tile. The name has to be `tile_states_<spec>`. Use a good description to +help the translators. +7. +In [`SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt), +add a new element to the map in `SubtitleArrayMapping` corresponding to the resource created in the +previous step. #### Abstract methods in QSTileImpl -Following are methods that need to be implemented when creating a new SystemUI tile. `TState` is a type variable of type `State`. +Following are methods that need to be implemented when creating a new SystemUI tile. `TState` is a +type variable of type `State`. * ```java public TState newTileState() ``` - Creates a new `State` for this tile to use. Each time the state changes, it is copied into a new one and the corresponding fields are modified. The framework provides `State`, `BooleanState` (has an on and off state and provides this as a content description), `SignalState` (`BooleanState` with `activityIn` and `activityOut`), and `SlashState` (can be rotated or slashed through). + Creates a new `State` for this tile to use. Each time the state changes, it is copied into a new + one and the corresponding fields are modified. The framework provides `State`, `BooleanState` (has + an on and off state and provides this as a content description), `SignalState` (`BooleanState` + with `activityIn` and `activityOut`), and `SlashState` (can be rotated or slashed through). - If a tile has special behavior (no long click, no ripple), it can be set in its state here. + If a tile has special behavior (no long click, no ripple), it can be set in its state here. * ```java public void handleSetListening(boolean) ``` - Initiates or terminates listening behavior, like listening to Callbacks from controllers. This gets triggered when QS is expanded or collapsed (i.e., when the tile is visible and actionable). Most tiles (like `WifiTile`) do not implement this. Instead, Tiles are LifecycleOwner and are marked as `RESUMED` or `DESTROYED` in `QSTileImpl#handleListening` and handled as part of the lifecycle of [CallbackController](/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java) + Initiates or terminates listening behavior, like listening to Callbacks from controllers. This + gets triggered when QS is expanded or collapsed (i.e., when the tile is visible and actionable). + Most tiles (like `WifiTile`) do not implement this. Instead, Tiles are LifecycleOwner and are + marked as `RESUMED` or `DESTROYED` in `QSTileImpl#handleListening` and handled as part of the + lifecycle + of [CallbackController](/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java) * ```java public QSIconView createTileView(Context) ``` - Allows a Tile to use a `QSIconView` different from `QSIconViewImpl` (see [Tile views](#tile-views)), which is the default defined in `QSTileImpl` + Allows a Tile to use a `QSIconView` different from `QSIconViewImpl` ( + see [Tile views](#tile-views)), which is the default defined in `QSTileImpl` * ```java public Intent getLongClickIntent() @@ -350,36 +526,47 @@ Following are methods that need to be implemented when creating a new SystemUI t protected void handleLongClick() ``` - Handles what to do when the Tile is clicked. In general, a Tile will make calls to its controller here and maybe update its state immediately (by calling `QSTileImpl#refreshState`). A Tile can also decide to ignore the click here, if it's `Tile#STATE_UNAVAILABLE`. + Handles what to do when the Tile is clicked. In general, a Tile will make calls to its controller + here and maybe update its state immediately (by calling `QSTileImpl#refreshState`). A Tile can + also decide to ignore the click here, if it's `Tile#STATE_UNAVAILABLE`. - By default long click redirects to click and long click launches the intent defined in `getLongClickIntent`. + By default long click redirects to click and long click launches the intent defined + in `getLongClickIntent`. * ```java protected void handleUpdateState(TState, Object) ``` - Updates the `State` of the Tile based on the state of the device as provided by the respective controller. It will be called every time the Tile becomes visible, is interacted with or `QSTileImpl#refreshState` is called. After this is done, the updated state will be reflected in the UI. + Updates the `State` of the Tile based on the state of the device as provided by the respective + controller. It will be called every time the Tile becomes visible, is interacted with + or `QSTileImpl#refreshState` is called. After this is done, the updated state will be reflected in + the UI. * ```java @Deprecated public int getMetricsCategory() ``` - ~~Identifier for this Tile, as defined in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). This is used to log events related to this Tile.~~ + ~~Identifier for this Tile, as defined + in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). + This is used to log events related to this Tile.~~ This is now deprecated in favor of `UiEvent` that use the tile spec. * ```java public boolean isAvailable() ``` - Determines if a Tile is available to be used (for example, disable `WifiTile` in devices with no Wifi support). If this is false, the Tile will be destroyed upon creation. + Determines if a Tile is available to be used (for example, disable `WifiTile` in devices with no + Wifi support). If this is false, the Tile will be destroyed upon creation. * ```java public CharSequence getTileLabel() ``` - Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to each available tile. + Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to + each available tile. ### Implementing a third party tile -For information about this, use the Android Developer documentation for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
\ No newline at end of file +For information about this, use the Android Developer documentation +for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
\ No newline at end of file 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 602f3dc29491..da97a1283261 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -1041,8 +1041,9 @@ public class AuthControllerTest extends SysuiTestCase { mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, () -> mUdfpsController, () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle, - mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger, - mLogContextInteractor, () -> mBiometricPromptCredentialInteractor, + mPanelInteractionDetector, mUserManager, mLockPatternUtils, () -> mUdfpsLogger, + () -> mLogContextInteractor, + () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor, () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor, mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java index 0b922a844690..de767e39499a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java @@ -65,7 +65,7 @@ public class SystemUIDialogTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock - private DialogDelegate<SystemUIDialog> mDelegate; + private SystemUIDialog.Delegate mDelegate; @Before public void setup() { diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 03960d522653..7db21b2be04d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -37,6 +37,7 @@ <!-- Used while animating the navbar during a long press. --> <dimen name="navigation_home_handle_additional_width_for_animation">20dp</dimen> <dimen name="navigation_home_handle_additional_height_for_animation">4dp</dimen> + <dimen name="navigation_home_handle_shrink_width_for_animation">16dp</dimen> <!-- Size of the nav bar edge panels, should be greater to the edge sensitivity + the drag threshold --> 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 26e785d9a704..46329148a659 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 @@ -149,10 +149,11 @@ interface ISystemUiProxy { * * @param isTouchDown {@code true} if the button is starting to be pressed ({@code false} if * released or canceled) + * @param shrink {@code true} if the handle should shrink, {@code false} if it should grow * @param durationMs how long the animation should take (for the {@code isTouchDown} case, this * should be the same as the amount of time to trigger a long-press) */ - oneway void animateNavBarLongPress(boolean isTouchDown, long durationMs) = 54; + oneway void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) = 54; // Next id = 55 } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 9716d98cf5e0..ee35bb9dff84 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.res.Resources; import android.view.LayoutInflater; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Background; @@ -30,6 +29,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.dagger.KeyguardClockLog; import com.android.systemui.plugins.PluginManager; +import com.android.systemui.res.R; import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.shared.clocks.DefaultClockProvider; @@ -67,7 +67,8 @@ public abstract class ClockRegistryModule { context, layoutInflater, resources, - featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION)), + featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION), + featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)), context.getString(R.string.lockscreen_clock_id_fallback), logBuffer, /* keepAllLoaded = */ false, diff --git a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java index 494efb7d3f87..45cc71ecebb9 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java @@ -36,7 +36,6 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.UserSwitcherController; -import dagger.Lazy; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; @@ -141,23 +140,23 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver { * reset and restart of guest user. */ public static final class ResetSessionDialogFactory { - private final Lazy<SystemUIDialog> mDialogLazy; + private final SystemUIDialog.Factory mDialogFactory; private final Resources mResources; private final ResetSessionDialogClickListener.Factory mClickListenerFactory; @Inject public ResetSessionDialogFactory( - Lazy<SystemUIDialog> dialogLazy, + SystemUIDialog.Factory dialogFactory, @Main Resources resources, ResetSessionDialogClickListener.Factory clickListenerFactory) { - mDialogLazy = dialogLazy; + mDialogFactory = dialogFactory; mResources = resources; mClickListenerFactory = clickListenerFactory; } /** Create a guest reset dialog instance */ public AlertDialog create(int userId) { - SystemUIDialog dialog = mDialogLazy.get(); + SystemUIDialog dialog = mDialogFactory.create(); ResetSessionDialogClickListener listener = mClickListenerFactory.create( userId, dialog); dialog.setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title); @@ -216,22 +215,22 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver { * exit of guest user. */ public static final class ExitSessionDialogFactory { - private final Lazy<SystemUIDialog> mDialogLazy; + private final SystemUIDialog.Factory mDialogFactory; private final ExitSessionDialogClickListener.Factory mClickListenerFactory; private final Resources mResources; @Inject public ExitSessionDialogFactory( - Lazy<SystemUIDialog> dialogLazy, + SystemUIDialog.Factory dialogFactory, ExitSessionDialogClickListener.Factory clickListenerFactory, @Main Resources resources) { - mDialogLazy = dialogLazy; + mDialogFactory = dialogFactory; mClickListenerFactory = clickListenerFactory; mResources = resources; } public AlertDialog create(boolean isEphemeral, int userId) { - SystemUIDialog dialog = mDialogLazy.get(); + SystemUIDialog dialog = mDialogFactory.create(); ExitSessionDialogClickListener clickListener = mClickListenerFactory.create( isEphemeral, userId, dialog); if (isEphemeral) { diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 7ccf70427327..685ea81fe40d 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -5,6 +5,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.SearchManager; import android.app.StatusBarManager; @@ -146,6 +147,7 @@ public class AssistManager { private final DisplayTracker mDisplayTracker; private final SecureSettings mSecureSettings; private final SelectedUserInteractor mSelectedUserInteractor; + private final ActivityManager mActivityManager; private final DeviceProvisionedController mDeviceProvisionedController; @@ -186,7 +188,8 @@ public class AssistManager { UserTracker userTracker, DisplayTracker displayTracker, SecureSettings secureSettings, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, + ActivityManager activityManager) { mContext = context; mDeviceProvisionedController = controller; mCommandQueue = commandQueue; @@ -199,6 +202,7 @@ public class AssistManager { mDisplayTracker = displayTracker; mSecureSettings = secureSettings; mSelectedUserInteractor = selectedUserInteractor; + mActivityManager = activityManager; registerVoiceInteractionSessionListener(); registerVisualQueryRecognitionStatusListener(); @@ -270,6 +274,9 @@ public class AssistManager { } public void startAssist(Bundle args) { + if (mActivityManager.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED) { + return; + } if (shouldOverrideAssist(args)) { try { if (mOverviewProxyService.getProxy() == null) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 8fe42b536b1e..877afce7fe65 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -86,6 +86,8 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -133,7 +135,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @NonNull private final Provider<PromptSelectorInteractor> mPromptSelectorInteractor; @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider; @NonNull private final Provider<PromptViewModel> mPromptViewModelProvider; - @NonNull private final LogContextInteractor mLogContextInteractor; + @NonNull private final Lazy<LogContextInteractor> mLogContextInteractor; private final Display mDisplay; private float mScaleFactor = 1f; @@ -156,7 +158,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @Nullable private UdfpsOverlayParams mUdfpsOverlayParams; @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback; @Nullable private SideFpsController mSideFpsController; - @NonNull private UdfpsLogger mUdfpsLogger; + @NonNull private Lazy<UdfpsLogger> mUdfpsLogger; @VisibleForTesting IBiometricSysuiReceiver mReceiver; @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps; @@ -309,7 +311,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, }); mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation); mUdfpsController.setUdfpsDisplayMode(new UdfpsDisplayMode(mContext, mExecution, - this, mUdfpsLogger)); + this, mUdfpsLogger.get())); mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect(); } @@ -755,8 +757,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, - @NonNull UdfpsLogger udfpsLogger, - @NonNull LogContextInteractor logContextInteractor, + @NonNull Lazy<UdfpsLogger> udfpsLogger, + @NonNull Lazy<LogContextInteractor> logContextInteractor, @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractorProvider, @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @@ -903,7 +905,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @Override public void setBiometricContextListener(IBiometricContextListener listener) { - mLogContextInteractor.addBiometricContextListener(listener); + mLogContextInteractor.get().addBiometricContextListener(listener); } /** @@ -932,14 +934,14 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, */ public void requestMaxRefreshRate(boolean request) throws RemoteException { if (mUdfpsRefreshRateRequestCallback == null) { - mUdfpsLogger.log( + mUdfpsLogger.get().log( "PreAuthRefreshRate", "skip request - refreshRateCallback is null", LogLevel.DEBUG ); return; } - mUdfpsLogger.requestMaxRefreshRate(request); + mUdfpsLogger.get().requestMaxRefreshRate(request); mUdfpsRefreshRateRequestCallback.onAuthenticationPossible(mContext.getDisplayId(), request); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java index c22a66b210cb..df27cbb070b6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.hardware.biometrics.BiometricSourceType; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.statusbar.phone.SystemUIDialog; import javax.inject.Inject; @@ -41,7 +40,8 @@ public class BiometricNotificationBroadcastReceiver extends BroadcastReceiver { private final Context mContext; private final BiometricNotificationDialogFactory mNotificationDialogFactory; @Inject - BiometricNotificationBroadcastReceiver(Context context, + BiometricNotificationBroadcastReceiver( + Context context, BiometricNotificationDialogFactory notificationDialogFactory) { mContext = context; mNotificationDialogFactory = notificationDialogFactory; @@ -53,15 +53,16 @@ public class BiometricNotificationBroadcastReceiver extends BroadcastReceiver { switch (action) { case ACTION_SHOW_FACE_REENROLL_DIALOG: - mNotificationDialogFactory.createReenrollDialog(mContext, - new SystemUIDialog(mContext), + mNotificationDialogFactory.createReenrollDialog( + mContext.getUserId(), + mContext::startActivity, BiometricSourceType.FACE) .show(); break; case ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG: mNotificationDialogFactory.createReenrollDialog( - mContext, - new SystemUIDialog(mContext), + mContext.getUserId(), + mContext::startActivity, BiometricSourceType.FINGERPRINT) .show(); break; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java index 2962be8e55a2..fd0feef7689b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java @@ -16,9 +16,11 @@ package com.android.systemui.biometrics; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.Dialog; -import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.Face; import android.hardware.face.FaceManager; @@ -29,9 +31,11 @@ import android.util.Log; import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.phone.SystemUIDialog; import javax.inject.Inject; +import javax.inject.Provider; /** * Manages the creation of dialogs to be shown for biometric re enroll notifications. @@ -39,44 +43,56 @@ import javax.inject.Inject; @SysUISingleton public class BiometricNotificationDialogFactory { private static final String TAG = "BiometricNotificationDialogFactory"; + private final Resources mResources; + private final SystemUIDialog.Factory mSystemUIDialogFactory; + @Nullable private final FingerprintManager mFingerprintManager; + @Nullable private final FaceManager mFaceManager; @Inject - BiometricNotificationDialogFactory() {} + BiometricNotificationDialogFactory( + @Main Resources resources, + SystemUIDialog.Factory systemUIDialogFactory, + @Nullable FingerprintManager fingerprintManager, + @Nullable FaceManager faceManager) { + mResources = resources; + mSystemUIDialogFactory = systemUIDialogFactory; + mFingerprintManager = fingerprintManager; + mFaceManager = faceManager; + } - Dialog createReenrollDialog(final Context context, final SystemUIDialog sysuiDialog, - BiometricSourceType biometricSourceType) { + Dialog createReenrollDialog( + int userId, ActivityStarter activityStarter, BiometricSourceType biometricSourceType) { + SystemUIDialog sysuiDialog = mSystemUIDialogFactory.create(); if (biometricSourceType == BiometricSourceType.FACE) { - sysuiDialog.setTitle(context.getString(R.string.face_re_enroll_dialog_title)); - sysuiDialog.setMessage(context.getString(R.string.face_re_enroll_dialog_content)); + sysuiDialog.setTitle(mResources.getString(R.string.face_re_enroll_dialog_title)); + sysuiDialog.setMessage(mResources.getString(R.string.face_re_enroll_dialog_content)); } else if (biometricSourceType == BiometricSourceType.FINGERPRINT) { - FingerprintManager fingerprintManager = context.getSystemService( - FingerprintManager.class); - sysuiDialog.setTitle(context.getString(R.string.fingerprint_re_enroll_dialog_title)); - if (fingerprintManager.getEnrolledFingerprints().size() == 1) { - sysuiDialog.setMessage(context.getString( + sysuiDialog.setTitle(mResources.getString(R.string.fingerprint_re_enroll_dialog_title)); + if (mFingerprintManager.getEnrolledFingerprints().size() == 1) { + sysuiDialog.setMessage(mResources.getString( R.string.fingerprint_re_enroll_dialog_content_singular)); } else { - sysuiDialog.setMessage(context.getString( + sysuiDialog.setMessage(mResources.getString( R.string.fingerprint_re_enroll_dialog_content)); } } sysuiDialog.setPositiveButton(R.string.biometric_re_enroll_dialog_confirm, - (dialog, which) -> onReenrollDialogConfirm(context, biometricSourceType)); + (dialog, which) -> onReenrollDialogConfirm( + userId, biometricSourceType, activityStarter)); sysuiDialog.setNegativeButton(R.string.biometric_re_enroll_dialog_cancel, (dialog, which) -> {}); return sysuiDialog; } - private static Dialog createReenrollFailureDialog(Context context, - BiometricSourceType biometricType) { - final SystemUIDialog sysuiDialog = new SystemUIDialog(context); + private Dialog createReenrollFailureDialog(BiometricSourceType biometricType) { + final SystemUIDialog sysuiDialog = mSystemUIDialogFactory.create(); if (biometricType == BiometricSourceType.FACE) { - sysuiDialog.setMessage(context.getString( + sysuiDialog.setMessage(mResources.getString( R.string.face_reenroll_failure_dialog_content)); } else if (biometricType == BiometricSourceType.FINGERPRINT) { - sysuiDialog.setMessage(context.getString( + sysuiDialog.setMessage(mResources.getString( R.string.fingerprint_reenroll_failure_dialog_content)); } @@ -84,41 +100,41 @@ public class BiometricNotificationDialogFactory { return sysuiDialog; } - private static void onReenrollDialogConfirm(final Context context, - BiometricSourceType biometricType) { + private void onReenrollDialogConfirm( + int userId, BiometricSourceType biometricType, ActivityStarter activityStarter) { if (biometricType == BiometricSourceType.FACE) { - reenrollFace(context); + reenrollFace(userId, activityStarter); } else if (biometricType == BiometricSourceType.FINGERPRINT) { - reenrollFingerprint(context); + reenrollFingerprint(userId, activityStarter); } } - private static void reenrollFingerprint(Context context) { - FingerprintManager fingerprintManager = context.getSystemService(FingerprintManager.class); - if (fingerprintManager == null) { + @SuppressLint("MissingPermission") + private void reenrollFingerprint(int userId, ActivityStarter activityStarter) { + if (mFingerprintManager == null) { Log.e(TAG, "Not launching enrollment. Fingerprint manager was null!"); - createReenrollFailureDialog(context, BiometricSourceType.FINGERPRINT).show(); + createReenrollFailureDialog(BiometricSourceType.FINGERPRINT).show(); return; } - if (!fingerprintManager.hasEnrolledTemplates(context.getUserId())) { - createReenrollFailureDialog(context, BiometricSourceType.FINGERPRINT).show(); + if (!mFingerprintManager.hasEnrolledTemplates(userId)) { + createReenrollFailureDialog(BiometricSourceType.FINGERPRINT).show(); return; } // Remove all enrolled fingerprint. Launch enrollment if successful. - fingerprintManager.removeAll(context.getUserId(), + mFingerprintManager.removeAll(userId, new FingerprintManager.RemovalCallback() { boolean mDidShowFailureDialog; @Override - public void onRemovalError(Fingerprint fingerprint, int errMsgId, - CharSequence errString) { + public void onRemovalError( + Fingerprint fingerprint, int errMsgId, CharSequence errString) { Log.e(TAG, "Not launching enrollment." + "Failed to remove existing face(s)."); if (!mDidShowFailureDialog) { mDidShowFailureDialog = true; - createReenrollFailureDialog(context, BiometricSourceType.FINGERPRINT) + createReenrollFailureDialog(BiometricSourceType.FINGERPRINT) .show(); } } @@ -129,27 +145,27 @@ public class BiometricNotificationDialogFactory { Intent intent = new Intent(Settings.ACTION_FINGERPRINT_ENROLL); intent.setPackage("com.android.settings"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); + activityStarter.startActivity(intent); } } }); } - private static void reenrollFace(Context context) { - FaceManager faceManager = context.getSystemService(FaceManager.class); - if (faceManager == null) { + @SuppressLint("MissingPermission") + private void reenrollFace(int userId, ActivityStarter activityStarter) { + if (mFaceManager == null) { Log.e(TAG, "Not launching enrollment. Face manager was null!"); - createReenrollFailureDialog(context, BiometricSourceType.FACE).show(); + createReenrollFailureDialog(BiometricSourceType.FACE).show(); return; } - if (!faceManager.hasEnrolledTemplates(context.getUserId())) { - createReenrollFailureDialog(context, BiometricSourceType.FACE).show(); + if (!mFaceManager.hasEnrolledTemplates(userId)) { + createReenrollFailureDialog(BiometricSourceType.FACE).show(); return; } // Remove all enrolled faces. Launch enrollment if successful. - faceManager.removeAll(context.getUserId(), + mFaceManager.removeAll(userId, new FaceManager.RemovalCallback() { boolean mDidShowFailureDialog; @@ -159,7 +175,7 @@ public class BiometricNotificationDialogFactory { + "Failed to remove existing face(s)."); if (!mDidShowFailureDialog) { mDidShowFailureDialog = true; - createReenrollFailureDialog(context, BiometricSourceType.FACE).show(); + createReenrollFailureDialog(BiometricSourceType.FACE).show(); } } @@ -169,9 +185,13 @@ public class BiometricNotificationDialogFactory { Intent intent = new Intent("android.settings.FACE_ENROLL"); intent.setPackage("com.android.settings"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); + activityStarter.startActivity(intent); } } }); } + + interface ActivityStarter { + void startActivity(Intent intent); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java index 17bf1a716944..b78b1f128376 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java @@ -16,16 +16,11 @@ package com.android.systemui.bluetooth; -import android.annotation.Nullable; -import android.content.Context; import android.view.View; -import com.android.internal.logging.UiEventLogger; -import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.animation.DialogLaunchAnimator; -import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.media.dialog.MediaOutputDialogFactory; +import com.android.systemui.statusbar.phone.SystemUIDialog; import javax.inject.Inject; @@ -35,25 +30,15 @@ import javax.inject.Inject; @SysUISingleton public class BroadcastDialogController { - private Context mContext; - private UiEventLogger mUiEventLogger; - private DialogLaunchAnimator mDialogLaunchAnimator; - private MediaOutputDialogFactory mMediaOutputDialogFactory; - private final LocalBluetoothManager mLocalBluetoothManager; - private BroadcastSender mBroadcastSender; + private final DialogLaunchAnimator mDialogLaunchAnimator; + private final BroadcastDialogDelegate.Factory mBroadcastDialogFactory; @Inject - public BroadcastDialogController(Context context, UiEventLogger uiEventLogger, + public BroadcastDialogController( DialogLaunchAnimator dialogLaunchAnimator, - MediaOutputDialogFactory mediaOutputDialogFactory, - @Nullable LocalBluetoothManager localBluetoothManager, - BroadcastSender broadcastSender) { - mContext = context; - mUiEventLogger = uiEventLogger; + BroadcastDialogDelegate.Factory broadcastDialogFactory) { mDialogLaunchAnimator = dialogLaunchAnimator; - mMediaOutputDialogFactory = mediaOutputDialogFactory; - mLocalBluetoothManager = localBluetoothManager; - mBroadcastSender = broadcastSender; + mBroadcastDialogFactory = broadcastDialogFactory; } /** Creates a [BroadcastDialog] for the user to switch broadcast or change the output device @@ -61,11 +46,10 @@ public class BroadcastDialogController { * @param currentBroadcastAppName Indicates the APP name currently broadcasting * @param outputPkgName Indicates the output media package name to be switched */ - public void createBroadcastDialog(String currentBroadcastAppName, String outputPkgName, - boolean aboveStatusBar, View view) { - BroadcastDialog broadcastDialog = new BroadcastDialog(mContext, mMediaOutputDialogFactory, - mLocalBluetoothManager, currentBroadcastAppName, outputPkgName, mUiEventLogger, - mBroadcastSender); + public void createBroadcastDialog( + String currentBroadcastAppName, String outputPkgName, View view) { + SystemUIDialog broadcastDialog = mBroadcastDialogFactory.create( + currentBroadcastAppName, outputPkgName).createDialog(); if (view != null) { mDialogLaunchAnimator.showFromView(broadcastDialog, view); } else { diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java index 00e952718436..00bbb20ed4f9 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java @@ -5,7 +5,7 @@ * you may not use this 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, @@ -18,6 +18,8 @@ package com.android.systemui.bluetooth; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Dialog; import android.bluetooth.BluetoothLeBroadcast; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.content.Context; @@ -26,7 +28,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.widget.Button; @@ -38,39 +39,47 @@ import com.android.internal.logging.UiEventLogger; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.MediaOutputConstants; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.media.controls.util.MediaDataUtils; import com.android.systemui.media.dialog.MediaOutputDialogFactory; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.Executor; -import java.util.concurrent.Executors; /** * Dialog for showing le audio broadcasting dialog. */ -public class BroadcastDialog extends SystemUIDialog { +public class BroadcastDialogDelegate implements SystemUIDialog.Delegate { private static final String TAG = "BroadcastDialog"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int HANDLE_BROADCAST_FAILED_DELAY = 3000; + private static final String CURRENT_BROADCAST_APP = "current_broadcast_app"; + private static final String OUTPUT_PKG_NAME = "output_pkg_name"; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); - private Context mContext; - private UiEventLogger mUiEventLogger; - @VisibleForTesting - protected View mDialogView; - private MediaOutputDialogFactory mMediaOutputDialogFactory; - private LocalBluetoothManager mLocalBluetoothManager; - private BroadcastSender mBroadcastSender; - private String mCurrentBroadcastApp; - private String mOutputPackageName; - private Executor mExecutor; + private final Context mContext; + private final UiEventLogger mUiEventLogger; + private final MediaOutputDialogFactory mMediaOutputDialogFactory; + private final LocalBluetoothManager mLocalBluetoothManager; + private final BroadcastSender mBroadcastSender; + private final SystemUIDialog.Factory mSystemUIDialogFactory; + private final String mCurrentBroadcastApp; + private final String mOutputPackageName; + private final Executor mExecutor; private boolean mShouldLaunchLeBroadcastDialog; private Button mSwitchBroadcast; + private final Set<SystemUIDialog> mDialogs = new HashSet<>(); + private final BluetoothLeBroadcast.Callback mBroadcastCallback = new BluetoothLeBroadcast.Callback() { @Override @@ -136,43 +145,64 @@ public class BroadcastDialog extends SystemUIDialog { } }; - public BroadcastDialog(Context context, MediaOutputDialogFactory mediaOutputDialogFactory, - LocalBluetoothManager localBluetoothManager, String currentBroadcastApp, - String outputPkgName, UiEventLogger uiEventLogger, BroadcastSender broadcastSender) { - super(context); - if (DEBUG) { - Log.d(TAG, "Init BroadcastDialog"); - } + @AssistedFactory + public interface Factory { + BroadcastDialogDelegate create( + @Assisted(CURRENT_BROADCAST_APP) String currentBroadcastApp, + @Assisted(OUTPUT_PKG_NAME) String outputPkgName + ); + } - mContext = getContext(); + @AssistedInject + BroadcastDialogDelegate( + Context context, + MediaOutputDialogFactory mediaOutputDialogFactory, + @Nullable LocalBluetoothManager localBluetoothManager, + UiEventLogger uiEventLogger, + Executor executor, + BroadcastSender broadcastSender, + SystemUIDialog.Factory systemUIDialogFactory, + @Assisted(CURRENT_BROADCAST_APP) String currentBroadcastApp, + @Assisted(OUTPUT_PKG_NAME) String outputPkgName) { + mContext = context; mMediaOutputDialogFactory = mediaOutputDialogFactory; mLocalBluetoothManager = localBluetoothManager; + mSystemUIDialogFactory = systemUIDialogFactory; mCurrentBroadcastApp = currentBroadcastApp; mOutputPackageName = outputPkgName; mUiEventLogger = uiEventLogger; - mExecutor = Executors.newSingleThreadExecutor(); + mExecutor = executor; mBroadcastSender = broadcastSender; + + if (DEBUG) { + Log.d(TAG, "Init BroadcastDialog"); + } + } + + @Override + public SystemUIDialog createDialog() { + return mSystemUIDialogFactory.create(this); } @Override - public void start() { + public void onStart(SystemUIDialog dialog) { + mDialogs.add(dialog); registerBroadcastCallBack(mExecutor, mBroadcastCallback); } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) { if (DEBUG) { Log.d(TAG, "onCreate"); } mUiEventLogger.log(BroadcastDialogEvent.BROADCAST_DIALOG_SHOW); - mDialogView = LayoutInflater.from(mContext).inflate(R.layout.broadcast_dialog, null); - final Window window = getWindow(); - window.setContentView(mDialogView); + View dialogView = dialog.getLayoutInflater().inflate(R.layout.broadcast_dialog, null); + final Window window = dialog.getWindow(); + window.setContentView(dialogView); - TextView title = mDialogView.requireViewById(R.id.dialog_title); - TextView subTitle = mDialogView.requireViewById(R.id.dialog_subtitle); + TextView title = dialogView.requireViewById(R.id.dialog_title); + TextView subTitle = dialogView.requireViewById(R.id.dialog_subtitle); title.setText(mContext.getString( R.string.bt_le_audio_broadcast_dialog_title, mCurrentBroadcastApp)); String switchBroadcastApp = MediaDataUtils.getAppLabel(mContext, mOutputPackageName, @@ -180,27 +210,28 @@ public class BroadcastDialog extends SystemUIDialog { subTitle.setText(mContext.getString( R.string.bt_le_audio_broadcast_dialog_sub_title, switchBroadcastApp)); - mSwitchBroadcast = mDialogView.requireViewById(R.id.switch_broadcast); - Button changeOutput = mDialogView.requireViewById(R.id.change_output); - Button cancelBtn = mDialogView.requireViewById(R.id.cancel); + mSwitchBroadcast = dialogView.requireViewById(R.id.switch_broadcast); + Button changeOutput = dialogView.requireViewById(R.id.change_output); + Button cancelBtn = dialogView.requireViewById(R.id.cancel); mSwitchBroadcast.setText(mContext.getString( R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null); mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast()); changeOutput.setOnClickListener((view) -> { mMediaOutputDialogFactory.create(mOutputPackageName, true, null); - dismiss(); + dialog.dismiss(); }); cancelBtn.setOnClickListener((view) -> { if (DEBUG) { Log.d(TAG, "BroadcastDialog dismiss."); } - dismiss(); + dialog.dismiss(); }); } @Override - public void stop() { + public void onStop(SystemUIDialog dialog) { unregisterBroadcastCallBack(mBroadcastCallback); + mDialogs.remove(dialog); } void refreshSwitchBroadcastButton() { @@ -271,10 +302,9 @@ public class BroadcastDialog extends SystemUIDialog { } @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (!hasFocus && isShowing()) { - dismiss(); + public void onWindowFocusChanged(SystemUIDialog dialog, boolean hasFocus) { + if (!hasFocus && dialog.isShowing()) { + dialog.dismiss(); } } @@ -333,6 +363,6 @@ public class BroadcastDialog extends SystemUIDialog { .setPackage(mContext.getPackageName()) .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG) .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, mOutputPackageName)); - dismiss(); + mDialogs.forEach(Dialog::dismiss); } } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 63b4288ce055..e0ce3db39403 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -27,7 +27,7 @@ import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_S import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; -import android.os.SystemProperties; +import android.os.Build; import android.provider.Settings; import android.util.Log; @@ -87,7 +87,7 @@ public class ClipboardListener implements String clipSource = mClipboardManager.getPrimaryClipSource(); ClipData clipData = mClipboardManager.getPrimaryClip(); - if (shouldSuppressOverlay(clipData, clipSource, isEmulator())) { + if (shouldSuppressOverlay(clipData, clipSource, Build.IS_EMULATOR)) { Log.i(TAG, "Clipboard overlay suppressed."); return; } @@ -141,10 +141,6 @@ public class ClipboardListener implements return true; } - private static boolean isEmulator() { - return SystemProperties.getBoolean("ro.boot.qemu", false); - } - private boolean isUserSetupComplete() { return Settings.Secure.getInt(mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt index 70d713845f00..4e40042a49b0 100644 --- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt @@ -16,31 +16,19 @@ package com.android.systemui.contrast import android.app.Activity -import android.app.UiModeManager -import android.content.Context import android.os.Bundle -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.settings.UserTracker -import com.android.systemui.util.settings.SecureSettings -import java.util.concurrent.Executor import javax.inject.Inject -/** Trampoline activity responsible for creating a [ContrastDialog] */ +/** Trampoline activity responsible for creating a [ContrastDialogDelegate] */ class ContrastDialogActivity @Inject constructor( - private val context: Context, - @Main private val mainExecutor: Executor, - private val uiModeManager: UiModeManager, - private val userTracker: UserTracker, - private val secureSettings: SecureSettings + private val contrastDialogDelegate : ContrastDialogDelegate ) : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val contrastDialog = - ContrastDialog(context, mainExecutor, uiModeManager, userTracker, secureSettings) - contrastDialog.show() + contrastDialogDelegate.createDialog().show() finish() } } diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt index e9b59306c552..63b01edb01fa 100644 --- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt @@ -21,54 +21,58 @@ import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD import android.app.UiModeManager.ContrastUtils.fromContrastLevel import android.app.UiModeManager.ContrastUtils.toContrastLevel -import android.content.Context import android.os.Bundle import android.provider.Settings -import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout import com.android.internal.annotations.VisibleForTesting -import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.settings.SecureSettings import java.util.concurrent.Executor +import javax.inject.Inject /** Dialog to select contrast options */ -class ContrastDialog( - context: Context?, +class ContrastDialogDelegate @Inject constructor( + private val sysuiDialogFactory : SystemUIDialog.Factory, @Main private val mainExecutor: Executor, private val uiModeManager: UiModeManager, private val userTracker: UserTracker, private val secureSettings: SecureSettings, -) : SystemUIDialog(context), UiModeManager.ContrastChangeListener { +) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener { + + override fun createDialog(): SystemUIDialog { + return sysuiDialogFactory.create(this) + } @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout> lateinit var dialogView: View @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD) - public override fun onCreate(savedInstanceState: Bundle?) { - dialogView = LayoutInflater.from(context).inflate(R.layout.contrast_dialog, null) - setView(dialogView) + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null) + with(dialog) { + setView(dialogView) - setTitle(R.string.quick_settings_contrast_label) - setNeutralButton(R.string.cancel) { _, _ -> - secureSettings.putFloatForUser( - Settings.Secure.CONTRAST_LEVEL, - initialContrast, - userTracker.userId - ) - dismiss() + setTitle(R.string.quick_settings_contrast_label) + setNeutralButton(R.string.cancel) { _, _ -> + secureSettings.putFloatForUser( + Settings.Secure.CONTRAST_LEVEL, + initialContrast, + userTracker.userId + ) + dialog.dismiss() + } + setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() } } - setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dismiss() } - super.onCreate(savedInstanceState) - contrastButtons = mapOf( - CONTRAST_LEVEL_STANDARD to requireViewById(R.id.contrast_button_standard), - CONTRAST_LEVEL_MEDIUM to requireViewById(R.id.contrast_button_medium), - CONTRAST_LEVEL_HIGH to requireViewById(R.id.contrast_button_high) + CONTRAST_LEVEL_STANDARD to dialogView.requireViewById( + R.id.contrast_button_standard), + CONTRAST_LEVEL_MEDIUM to dialogView.requireViewById(R.id.contrast_button_medium), + CONTRAST_LEVEL_HIGH to dialogView.requireViewById(R.id.contrast_button_high) ) contrastButtons.forEach { (contrastLevel, contrastButton) -> @@ -86,11 +90,11 @@ class ContrastDialog( highlightContrast(toContrastLevel(initialContrast)) } - override fun start() { + override fun onStart(dialog: SystemUIDialog) { uiModeManager.addContrastChangeListener(mainExecutor, this) } - override fun stop() { + override fun onStop(dialog: SystemUIDialog) { uiModeManager.removeContrastChangeListener(this) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt index d1c62188d3fc..8d5e6c391e6e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt @@ -19,34 +19,71 @@ package com.android.systemui.keyguard.data.repository import android.os.UserHandle import android.provider.Settings import androidx.annotation.VisibleForTesting -import com.android.keyguard.KeyguardClockSwitch.SMALL +import com.android.keyguard.ClockEventController +import com.android.keyguard.KeyguardClockSwitch.ClockSize +import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockId import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext +interface KeyguardClockRepository { + /** clock size determined by notificationPanelViewController, LARGE or SMALL */ + val clockSize: StateFlow<Int> + + /** clock size selected in picker, DYNAMIC or SMALL */ + val selectedClockSize: Flow<SettingsClockSize> + + /** clock id, selected from clock carousel in wallpaper picker */ + val currentClockId: Flow<ClockId> + + val currentClock: StateFlow<ClockController?> + + val clockEventController: ClockEventController + fun setClockSize(@ClockSize size: Int) +} + @SysUISingleton -class KeyguardClockRepository +class KeyguardClockRepositoryImpl @Inject constructor( private val secureSettings: SecureSettings, private val clockRegistry: ClockRegistry, + override val clockEventController: ClockEventController, @Background private val backgroundDispatcher: CoroutineDispatcher, -) { + @Application private val applicationScope: CoroutineScope, +) : KeyguardClockRepository { + + /** Receive SMALL or LARGE clock should be displayed on keyguard. */ + private val _clockSize: MutableStateFlow<Int> = MutableStateFlow(LARGE) + override val clockSize: StateFlow<Int> = _clockSize.asStateFlow() - val selectedClockSize: Flow<SettingsClockSize> = + override fun setClockSize(size: Int) { + _clockSize.value = size + } + + override val selectedClockSize: Flow<SettingsClockSize> = secureSettings .observerFlow( names = arrayOf(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK), @@ -55,7 +92,7 @@ constructor( .onStart { emit(Unit) } // Forces an initial update. .map { getClockSize() } - val currentClockId: Flow<ClockId> = + override val currentClockId: Flow<ClockId> = callbackFlow { fun send() { trySend(clockRegistry.currentClockId) @@ -72,8 +109,16 @@ constructor( awaitClose { clockRegistry.unregisterClockChangeListener(listener) } } .mapNotNull { it } + .distinctUntilChanged() - val currentClock = currentClockId.map { clockRegistry.createCurrentClock() } + override val currentClock: StateFlow<ClockController?> = + currentClockId + .map { clockRegistry.createCurrentClock() } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = clockRegistry.createCurrentClock() + ) @VisibleForTesting suspend fun getClockSize(): SettingsClockSize { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 6ff446edca38..791ac07db5cf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -18,8 +18,6 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point import android.hardware.biometrics.BiometricSourceType -import com.android.keyguard.KeyguardClockSwitch.ClockSize -import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.biometrics.AuthController @@ -192,9 +190,6 @@ interface KeyguardRepository { /** Observable updated when keyguardDone should be called either now or soon. */ val keyguardDone: Flow<KeyguardDone> - /** Receive SMALL or LARGE clock should be displayed on keyguard. */ - val clockSize: Flow<Int> - /** Receive whether clock should be centered on lockscreen. */ val clockShouldBeCentered: Flow<Boolean> @@ -247,8 +242,6 @@ interface KeyguardRepository { suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone) - fun setClockSize(@ClockSize size: Int) - fun setClockShouldBeCentered(shouldBeCentered: Boolean) } @@ -293,9 +286,6 @@ constructor( private val _clockPosition = MutableStateFlow(Position(0, 0)) override val clockPosition = _clockPosition.asStateFlow() - private val _clockSize = MutableStateFlow(LARGE) - override val clockSize: Flow<Int> = _clockSize.asStateFlow() - private val _clockShouldBeCentered = MutableStateFlow(true) override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow() @@ -681,10 +671,6 @@ constructor( _isActiveDreamLockscreenHosted.value = isLockscreenHosted } - override fun setClockSize(@ClockSize size: Int) { - _clockSize.value = size - } - override fun setClockShouldBeCentered(shouldBeCentered: Boolean) { _clockShouldBeCentered.value = shouldBeCentered } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 565962394db1..6138330c2e76 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -74,4 +74,6 @@ interface KeyguardRepositoryModule { fun bind(impl: BouncerMessageAuditLogger): CoreStartable @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository + + @Binds fun keyguardClockRepository(impl: KeyguardClockRepositoryImpl): KeyguardClockRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt index 2f103f612563..3887e69a47a2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.keyguard.ClockEventController +import com.android.keyguard.KeyguardClockSwitch.ClockSize import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardClockRepository import com.android.systemui.keyguard.shared.model.SettingsClockSize @@ -25,6 +26,7 @@ import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockId import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow private val TAG = KeyguardClockInteractor::class.simpleName /** Manages keyguard clock for the lockscreen root view. */ @@ -33,7 +35,6 @@ private val TAG = KeyguardClockInteractor::class.simpleName class KeyguardClockInteractor @Inject constructor( - val eventController: ClockEventController, private val keyguardClockRepository: KeyguardClockRepository, ) { @@ -41,11 +42,17 @@ constructor( val currentClockId: Flow<ClockId> = keyguardClockRepository.currentClockId - val currentClock: Flow<ClockController> = keyguardClockRepository.currentClock + val currentClock: StateFlow<ClockController?> = keyguardClockRepository.currentClock - var clock: ClockController? - get() = eventController.clock - set(value) { - eventController.clock = value + var clock: ClockController? by keyguardClockRepository.clockEventController::clock + + val clockSize: StateFlow<Int> = keyguardClockRepository.clockSize + fun setClockSize(@ClockSize size: Int) { + keyguardClockRepository.setClockSize(size) + } + + val clockEventController: ClockEventController + get() { + return keyguardClockRepository.clockEventController } } 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 e58d7710877b..c0e8e2b60f33 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 @@ -23,7 +23,6 @@ import android.app.StatusBarManager import android.graphics.Point import android.util.MathUtils import com.android.app.animation.Interpolators -import com.android.keyguard.KeyguardClockSwitch.ClockSize import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -243,8 +242,6 @@ constructor( } } - val clockSize: Flow<Int> = repository.clockSize.distinctUntilChanged() - val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered /** Whether to animate the next doze mode transition. */ @@ -321,10 +318,6 @@ constructor( repository.setAnimateDozingTransitions(animate) } - fun setClockSize(@ClockSize size: Int) { - repository.setClockSize(size) - } - fun setClockShouldBeCentered(shouldBeCentered: Boolean) { repository.setClockShouldBeCentered(shouldBeCentered) } 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 c688cfff2bf9..48f6092fd570 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 @@ -17,15 +17,16 @@ package com.android.systemui.keyguard.ui.binder import android.transition.TransitionManager +import androidx.annotation.VisibleForTesting +import androidx.constraintlayout.helper.widget.Layer import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.ui.view.layout.items.ClockSection +import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ClockController @@ -40,13 +41,12 @@ object KeyguardClockViewBinder { clockSection: ClockSection, keyguardRootView: ConstraintLayout, viewModel: KeyguardClockViewModel, - keyguardBlueprintInteractor: KeyguardBlueprintInteractor, keyguardClockInteractor: KeyguardClockInteractor, featureFlags: FeatureFlagsClassic, ) { keyguardRootView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { - keyguardClockInteractor.eventController.registerListeners(keyguardRootView) + keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView) } } keyguardRootView.repeatWhenAttached { @@ -54,10 +54,11 @@ object KeyguardClockViewBinder { launch { if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) return@launch viewModel.currentClock.collect { currentClock -> - viewModel.clock?.let { clock -> cleanupClockViews(clock, keyguardRootView) } + cleanupClockViews(viewModel.clock, keyguardRootView, viewModel.burnInLayer) viewModel.clock = currentClock - addClockViews(currentClock, keyguardRootView) - keyguardBlueprintInteractor.refreshBlueprint() + addClockViews(currentClock, keyguardRootView, viewModel.burnInLayer) + viewModel.burnInLayer?.updatePostLayout(keyguardRootView) + applyConstraints(clockSection, keyguardRootView, true) } } // TODO: Weather clock dozing animation @@ -71,13 +72,61 @@ object KeyguardClockViewBinder { } launch { if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) return@launch - viewModel.clockShouldBeCentered.collect { shouldBeCentered -> - clockSection.setClockShouldBeCentered( - viewModel.useLargeClock && shouldBeCentered - ) + viewModel.clockShouldBeCentered.collect { applyConstraints(clockSection, keyguardRootView, true) } } + launch { + if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) return@launch + viewModel.hasCustomWeatherDataDisplay.collect { + applyConstraints(clockSection, keyguardRootView, true) + } + } + } + } + } + + private fun cleanupClockViews( + clockController: ClockController?, + rootView: ConstraintLayout, + burnInLayer: Layer? + ) { + clockController?.let { clock -> + clock.smallClock.layout.views.forEach { + 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) } + } + } + + @VisibleForTesting + fun addClockViews( + clockController: ClockController?, + rootView: ConstraintLayout, + burnInLayer: Layer? + ) { + clockController?.let { clock -> + clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view + if (clock.largeClock.layout.views.size == 1) { + clock.largeClock.layout.views[0].id = R.id.lockscreen_clock_view_large + } + // small clock should either be a single view or container with id + // `lockscreen_clock_view` + clock.smallClock.layout.views.forEach { + rootView.addView(it) + burnInLayer?.addView(it) + } + clock.largeClock.layout.views.forEach { rootView.addView(it) } + if (clock.config.useAlternateSmartspaceAODTransition) { + clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) } } } } @@ -92,22 +141,6 @@ object KeyguardClockViewBinder { if (animated) { TransitionManager.beginDelayedTransition(rootView) } - constraintSet.applyTo(rootView) } - - private fun cleanupClockViews(clock: ClockController, rootView: ConstraintLayout) { - clock.smallClock.layout.views.forEach { rootView.removeView(it) } - clock.largeClock.layout.views.forEach { rootView.removeView(it) } - } - - private fun addClockViews(clock: ClockController, rootView: ConstraintLayout) { - clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view - if (clock.largeClock.layout.views.size == 1) { - clock.largeClock.layout.views[0].id = R.id.lockscreen_clock_view_large - } - // small clock should either be a single view or container with id `lockscreen_clock_view` - clock.smallClock.layout.views.forEach { rootView.addView(it) } - clock.largeClock.layout.views.forEach { rootView.addView(it) } - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index 1a8f62597037..4efd9ef5f21c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -23,10 +23,10 @@ import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.Flags.keyguardBottomAreaRefactor -import com.android.systemui.res.R import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi 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 c0d3d336719e..51e1f60acd64 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 @@ -41,6 +41,7 @@ import com.android.systemui.common.shared.model.TintedIcon import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel @@ -140,25 +141,35 @@ object KeyguardRootViewBinder { } launch { + // When translation happens in burnInLayer, it won't be weather clock + // large clock isn't added to burnInLayer due to its scale transition + // so we also need to add translation to it here + // same as translationX viewModel.translationY.collect { y -> childViews[burnInLayerId]?.translationY = y + childViews[largeClockId]?.translationY = y } } launch { viewModel.translationX.collect { x -> childViews[burnInLayerId]?.translationX = x + childViews[largeClockId]?.translationX = x } } launch { viewModel.scale.collect { (scale, scaleClockOnly) -> if (scaleClockOnly) { + // For clocks except weather clock, we have scale transition + // besides translate childViews[largeClockId]?.let { it.scaleX = scale it.scaleY = scale } } else { + // For weather clock, large clock should have only scale + // transition with other parts in burnInLayer childViews[burnInLayerId]?.scaleX = scale childViews[burnInLayerId]?.scaleY = scale } @@ -247,7 +258,10 @@ object KeyguardRootViewBinder { } } } - viewModel.clockControllerProvider = clockControllerProvider + + if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { + viewModel.clockControllerProvider = clockControllerProvider + } onLayoutChangeListener = OnLayoutChange(viewModel) view.addOnLayoutChangeListener(onLayoutChangeListener) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index 41a2e509b5d0..954d2cf6ed8a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -1,3 +1,19 @@ +/* + * 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.keyguard.ui.binder import androidx.constraintlayout.widget.ConstraintLayout diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 59c798bfca1e..11872d90cc34 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -119,7 +119,6 @@ constructor( private val screenOffAnimationController: ScreenOffAnimationController, private val shadeInteractor: ShadeInteractor, ) { - val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) private val width: Int = bundle.getInt(KEY_VIEW_WIDTH) private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index fa27442707a3..1c6a2abdcbe7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -21,9 +21,9 @@ import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialInd import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection -import com.android.systemui.keyguard.ui.view.layout.items.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection +import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection @@ -75,10 +75,10 @@ constructor( defaultStatusBarSection, defaultNotificationStackScrollLayoutSection, aodNotificationIconsSection, + smartspaceSection, aodBurnInSection, communalTutorialIndicatorSection, clockSection, - smartspaceSection, defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views. ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index 484d351a362e..df9ae41ed970 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -22,8 +22,12 @@ import android.view.View import androidx.constraintlayout.helper.widget.Layer import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import javax.inject.Inject @@ -32,21 +36,31 @@ class AodBurnInSection @Inject constructor( private val context: Context, + private val clockViewModel: KeyguardClockViewModel, + private val smartspaceViewModel: KeyguardSmartspaceViewModel, + private val featureFlags: FeatureFlagsClassic, ) : KeyguardSection() { + lateinit var burnInLayer: Layer override fun addViews(constraintLayout: ConstraintLayout) { if (!KeyguardShadeMigrationNssl.isEnabled) { return } - val statusView = constraintLayout.requireViewById<View>(R.id.keyguard_status_view) val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container) - val burnInLayer = + burnInLayer = Layer(context).apply { id = R.id.burn_in_layer addView(nic) - addView(statusView) + if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { + val statusView = + constraintLayout.requireViewById<View>(R.id.keyguard_status_view) + addView(statusView) + } } + if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { + addSmartspaceViews(constraintLayout) + } constraintLayout.addView(burnInLayer) } @@ -54,6 +68,9 @@ constructor( if (!KeyguardShadeMigrationNssl.isEnabled) { return } + if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { + clockViewModel.burnInLayer = burnInLayer + } } override fun applyConstraints(constraintSet: ConstraintSet) { @@ -65,4 +82,22 @@ constructor( override fun removeViews(constraintLayout: ConstraintLayout) { constraintLayout.removeView(R.id.burn_in_layer) } + + private fun addSmartspaceViews(constraintLayout: ConstraintLayout) { + burnInLayer.apply { + if (smartspaceViewModel.isSmartspaceEnabled) { + val smartspaceView = + constraintLayout.requireViewById<View>(smartspaceViewModel.smartspaceViewId) + addView(smartspaceView) + if (smartspaceViewModel.isDateWeatherDecoupled) { + val dateView = + constraintLayout.requireViewById<View>(smartspaceViewModel.dateId) + val weatherView = + constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId) + addView(weatherView) + addView(dateView) + } + } + } + } } 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 941c295ab86a..021f06434e80 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 @@ -15,7 +15,7 @@ * */ -package com.android.systemui.keyguard.ui.view.layout.items +package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context import android.view.View @@ -28,7 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder @@ -39,7 +38,6 @@ import com.android.systemui.plugins.ClockFaceLayout import com.android.systemui.res.R import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.Utils -import dagger.Lazy import javax.inject.Inject internal fun ConstraintSet.setVisibility( @@ -60,7 +58,6 @@ constructor( val smartspaceViewModel: KeyguardSmartspaceViewModel, private val context: Context, private val splitShadeStateController: SplitShadeStateController, - private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>, private val featureFlags: FeatureFlagsClassic, ) : KeyguardSection() { override fun addViews(constraintLayout: ConstraintLayout) {} @@ -70,7 +67,6 @@ constructor( this, constraintLayout, keyguardClockViewModel, - keyguardBlueprintInteractor.get(), clockInteractor, featureFlags ) @@ -109,15 +105,15 @@ constructor( return previousValue != largeClockEndGuideline } - fun getTargetClockFace(clock: ClockController): ClockFaceLayout = + private fun getTargetClockFace(clock: ClockController): ClockFaceLayout = if (keyguardClockViewModel.useLargeClock) getLargeClockFace(clock) else getSmallClockFace(clock) - fun getNonTargetClockFace(clock: ClockController): ClockFaceLayout = + private fun getNonTargetClockFace(clock: ClockController): ClockFaceLayout = if (keyguardClockViewModel.useLargeClock) getSmallClockFace(clock) else getLargeClockFace(clock) - fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout - fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout + private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout + private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout fun applyDefaultConstraints(constraints: ConstraintSet) { constraints.apply { connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START) @@ -138,6 +134,7 @@ constructor( ) } connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin) + constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT) constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT) constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT) constrainHeight( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt index 8aef7c23b45d..56f717d7e4ef 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt @@ -22,12 +22,12 @@ import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.Flags.keyguardBottomAreaRefactor -import com.android.systemui.res.R import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel +import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController import javax.inject.Inject import kotlinx.coroutines.DisposableHandle diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt index 4abcca9d1151..851a45f31705 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt @@ -53,7 +53,7 @@ constructor( private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>, private val notificationPanelViewController: Lazy<NotificationPanelViewController>, private val keyguardMediaController: KeyguardMediaController, - private val splitShadeStateController: SplitShadeStateController + private val splitShadeStateController: SplitShadeStateController, ) : KeyguardSection() { private val statusViewId = R.id.keyguard_status_view @@ -76,6 +76,9 @@ constructor( keyguardStatusView.findViewById<View>(R.id.left_aligned_notification_icon_container)?.let { it.setVisibility(View.GONE) } + // Should keep this even if flag, migrating clocks to blueprint, is on + // cause some events in clockEventController rely on keyguardStatusViewController + // TODO(b/313499340): clean up constraintLayout.addView(keyguardStatusView) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 25931a654cd2..a005692c6dbf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -47,9 +47,9 @@ constructor( val keyguardUnlockAnimationController: KeyguardUnlockAnimationController, val featureFlags: FeatureFlagsClassic, ) : KeyguardSection() { - var smartspaceView: View? = null - var weatherView: View? = null - var dateView: View? = null + private var smartspaceView: View? = null + private var weatherView: View? = null + private var dateView: View? = null override fun addViews(constraintLayout: ConstraintLayout) { if (!featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { @@ -65,16 +65,11 @@ constructor( constraintLayout.addView(dateView) } } - keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView } override fun bindData(constraintLayout: ConstraintLayout) { - KeyguardSmartspaceViewBinder.bind( - this, - constraintLayout, - keyguardClockViewModel, - ) + KeyguardSmartspaceViewBinder.bind(this, constraintLayout, keyguardClockViewModel) } override fun applyConstraints(constraintSet: ConstraintSet) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index c54f47b48745..7ffa149d7dd9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import androidx.constraintlayout.helper.widget.Layer import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.keyguard.KeyguardClockSwitch.SMALL import com.android.systemui.dagger.SysUISingleton @@ -29,7 +30,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.stateIn @SysUISingleton @@ -40,19 +40,14 @@ constructor( val keyguardClockInteractor: KeyguardClockInteractor, @Application private val applicationScope: CoroutineScope, ) { + var burnInLayer: Layer? = null val useLargeClock: Boolean get() = clockSize.value == LARGE - var clock: ClockController? - set(value) { - keyguardClockInteractor.clock = value - } - get() { - return keyguardClockInteractor.clock - } + var clock: ClockController? by keyguardClockInteractor::clock val clockSize = - combine(keyguardClockInteractor.selectedClockSize, keyguardInteractor.clockSize) { + combine(keyguardClockInteractor.selectedClockSize, keyguardClockInteractor.clockSize) { selectedSize, clockSize -> if (selectedSize == SettingsClockSize.SMALL) { @@ -61,7 +56,6 @@ constructor( clockSize } } - .distinctUntilChanged() .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), @@ -72,16 +66,23 @@ constructor( val hasCustomWeatherDataDisplay = combine(clockSize, currentClock) { size, clock -> - (if (size == LARGE) clock.largeClock.config.hasCustomWeatherDataDisplay - else clock.smallClock.config.hasCustomWeatherDataDisplay) + clock?.let { + (if (size == LARGE) clock.largeClock.config.hasCustomWeatherDataDisplay + else clock.smallClock.config.hasCustomWeatherDataDisplay) + } + ?: false } - .distinctUntilChanged() .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = false + initialValue = currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay + ?: false ) val clockShouldBeCentered: Flow<Boolean> = - keyguardInteractor.clockShouldBeCentered.distinctUntilChanged() + keyguardInteractor.clockShouldBeCentered.stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = true + ) } 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 f63afebb60ab..af1705369dbb 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 @@ -21,10 +21,13 @@ import android.content.Context import android.util.MathUtils import android.view.View.VISIBLE import com.android.app.animation.Interpolators +import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.systemui.Flags.newAodTransition import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -68,11 +71,21 @@ constructor( private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, private val burnInInteractor: BurnInInteractor, + private val keyguardClockViewModel: KeyguardClockViewModel, private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, screenOffAnimationController: ScreenOffAnimationController, + // TODO(b/310989341): remove after changing migrate_clocks_to_blueprint to aconfig + private val featureFlags: FeatureFlagsClassic, ) { var clockControllerProvider: Provider<ClockController>? = null + get() { + if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { + return Provider { keyguardClockViewModel.clock } + } else { + return field + } + } /** System insets that keyguard needs to stay out of */ var topInset: Int = 0 @@ -103,7 +116,8 @@ constructor( return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn -> val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) val useScaleOnly = - clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false + (clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition + ?: false) && keyguardClockViewModel.clockSize.value == LARGE if (useScaleOnly) { BurnInModel( translationX = 0, @@ -113,7 +127,12 @@ constructor( } else { // Ensure the desired translation doesn't encroach on the top inset val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt() - val translationY = -(statusViewTop - Math.max(topInset, statusViewTop + burnInY)) + val translationY = + if (featureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { + burnInY + } else { + -(statusViewTop - Math.max(topInset, statusViewTop + burnInY)) + } BurnInModel( translationX = MathUtils.lerp(0, burnIn.translationX, interpolation).toInt(), translationY = translationY, @@ -194,7 +213,6 @@ constructor( .distinctUntilChanged() fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) { - keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom)) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt index 8e33651ced6a..4541458892bb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt @@ -30,16 +30,21 @@ constructor(val context: Context, smartspaceController: LockscreenSmartspaceCont val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled() val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled() val smartspaceViewId: Int - get() { - return context.resources - .getIdentifier("bc_smartspace_view", "id", context.packageName) - .also { - if (it == 0) { - Log.d(TAG, "Cannot resolve id bc_smartspace_view") - } - } - } + get() = getId("bc_smartspace_view") + + val dateId: Int + get() = getId("date_smartspace_view") + + val weatherId: Int + get() = getId("weather_smartspace_view") + private fun getId(name: String): Int { + return context.resources.getIdentifier(name, "id", context.packageName).also { + if (it == 0) { + Log.d(TAG, "Cannot resolve id $name") + } + } + } fun getDimen(name: String): Int { val res = context.packageManager.getResourcesForApplication(context.packageName) val id = res.getIdentifier(name, "dimen", context.packageName) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index 04883c368b78..2551da8e7795 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -676,7 +676,7 @@ public class MediaControlPanel { mLogger.logOpenBroadcastDialog(mUid, mPackageName, mInstanceId); mCurrentBroadcastApp = device.getName().toString(); mBroadcastDialogController.createBroadcastDialog(mCurrentBroadcastApp, - mPackageName, true, mMediaViewHolder.getSeamlessButton()); + mPackageName, mMediaViewHolder.getSeamlessButton()); } else { mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId); mMediaOutputDialogFactory.create(mPackageName, true, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index e2e94dae900a..0a72a2f15e95 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -400,8 +400,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } @Override - public void animateNavBarLongPress(boolean isTouchDown, long durationMs) { - mView.getHomeHandle().animateLongPress(isTouchDown, durationMs); + public void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) { + mView.getHomeHandle().animateLongPress(isTouchDown, shrink, durationMs); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java index 5fe830e6e442..5739abcae7eb 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java @@ -247,10 +247,10 @@ public class ButtonDispatcher { } } - public void animateLongPress(boolean isTouchDown, long durationMs) { + public void animateLongPress(boolean isTouchDown, boolean shrink, long durationMs) { for (int i = 0; i < mViews.size(); i++) { if (mViews.get(i) instanceof ButtonInterface) { - ((ButtonInterface) mViews.get(i)).animateLongPress(isTouchDown, durationMs); + ((ButtonInterface) mViews.get(i)).animateLongPress(isTouchDown, shrink, durationMs); } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java index 356b2f7c7cb8..5f8fafd4ae4c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonInterface.java @@ -35,9 +35,10 @@ public interface ButtonInterface { * Animate the button being long-pressed. * * @param isTouchDown {@code true} if the button is starting to be pressed ({@code false} if - * released or canceled) - * @param durationMs how long the animation should take (for the {@code isTouchDown} case, this - * should be the same as the amount of time to trigger a long-press) + * released or canceled) + * @param shrink {@code true} if the handle should shrink, {@code false} if it should grow + * @param durationMs how long the animation should take (for the {@code isTouchDown} case, this + * should be the same as the amount of time to trigger a long-press) */ - default void animateLongPress(boolean isTouchDown, long durationMs) {} + default void animateLongPress(boolean isTouchDown, boolean shrink, long durationMs) {} } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java index 039d0e00070e..d1ce1f613044 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java @@ -44,7 +44,9 @@ public class NavigationHandle extends View implements ButtonInterface { protected final float mBottom; private final float mAdditionalWidthForAnimation; private final float mAdditionalHeightForAnimation; + private final float mShrinkWidthForAnimation; private boolean mRequiresInvalidate; + private boolean mShrink; private ObjectAnimator mPulseAnimator = null; private float mPulseAnimationProgress; @@ -75,6 +77,8 @@ public class NavigationHandle extends View implements ButtonInterface { res.getDimension(R.dimen.navigation_home_handle_additional_width_for_animation); mAdditionalHeightForAnimation = res.getDimension(R.dimen.navigation_home_handle_additional_height_for_animation); + mShrinkWidthForAnimation = + res.getDimension(R.dimen.navigation_home_handle_shrink_width_for_animation); final int dualToneDarkTheme = Utils.getThemeAttr(context, R.attr.darkIconTheme); final int dualToneLightTheme = Utils.getThemeAttr(context, R.attr.lightIconTheme); @@ -101,9 +105,17 @@ public class NavigationHandle extends View implements ButtonInterface { // Draw that bar int navHeight = getHeight(); - float additionalHeight = mAdditionalHeightForAnimation * mPulseAnimationProgress; + float additionalHeight; + float additionalWidth; + if (mShrink) { + additionalHeight = 0; + additionalWidth = -mShrinkWidthForAnimation * mPulseAnimationProgress; + } else { + additionalHeight = mAdditionalHeightForAnimation * mPulseAnimationProgress; + additionalWidth = mAdditionalWidthForAnimation * mPulseAnimationProgress; + } + float height = mRadius * 2 + additionalHeight; - float additionalWidth = mAdditionalWidthForAnimation * mPulseAnimationProgress; float width = getWidth() + additionalWidth; float x = -additionalWidth; float y = navHeight - mBottom - height + (additionalHeight / 2); @@ -138,26 +150,32 @@ public class NavigationHandle extends View implements ButtonInterface { public void setDelayTouchFeedback(boolean shouldDelay) {} @Override - public void animateLongPress(boolean isTouchDown, long durationMs) { + public void animateLongPress(boolean isTouchDown, boolean shrink, long durationMs) { if (mPulseAnimator != null) { mPulseAnimator.cancel(); } + mShrink = shrink; Interpolator interpolator; - if (isTouchDown) { - // For now we animate the navbar expanding and contracting so that the navbar is the - // original size by the end of {@code duration}. This is because a screenshot is taken - // at that point and we don't want to capture the larger navbar. - // TODO(b/306400785): Determine a way to exclude navbar from the screenshot. - - // Fraction of the touch down animation to expand; remaining is used to contract again. - float expandFraction = 0.9f; - interpolator = t -> t <= expandFraction + if (shrink) { + interpolator = Interpolators.LEGACY_DECELERATE; + } else { + if (isTouchDown) { + // For now we animate the navbar expanding and contracting so that the navbar is + // the original size by the end of {@code duration}. This is because a screenshot + // is taken at that point and we don't want to capture the larger navbar. + // TODO(b/306400785): Determine a way to exclude navbar from the screenshot. + + // Fraction of the touch down animation to expand; remaining is used to contract + // again. + float expandFraction = 0.9f; + interpolator = t -> t <= expandFraction ? Interpolators.clampToProgress(Interpolators.LEGACY, t, 0, expandFraction) : 1 - Interpolators.clampToProgress( Interpolators.LINEAR, t, expandFraction, 1); - } else { - interpolator = Interpolators.LEGACY_DECELERATE; + } else { + interpolator = Interpolators.LEGACY_DECELERATE; + } } mPulseAnimator = diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 377803fa6639..45917e85d80e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -248,9 +248,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } @Override - public void animateNavBarLongPress(boolean isTouchDown, long durationMs) { + public void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) { verifyCallerAndClearCallingIdentityPostMain("animateNavBarLongPress", () -> - notifyAnimateNavBarLongPress(isTouchDown, durationMs)); + notifyAnimateNavBarLongPress(isTouchDown, shrink, durationMs)); } @Override @@ -929,9 +929,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } - private void notifyAnimateNavBarLongPress(boolean isTouchDown, long durationMs) { + private void notifyAnimateNavBarLongPress(boolean isTouchDown, boolean shrink, + long durationMs) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { - mConnectionCallbacks.get(i).animateNavBarLongPress(isTouchDown, durationMs); + mConnectionCallbacks.get(i).animateNavBarLongPress(isTouchDown, shrink, durationMs); } } @@ -1079,7 +1080,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis default void onAssistantGestureCompletion(float velocity) {} default void startAssistant(Bundle bundle) {} default void setAssistantOverridesRequested(int[] invocationTypes) {} - default void animateNavBarLongPress(boolean isTouchDown, long durationMs) {} + default void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) {} } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index bff0b93dccb0..62d8fb99603e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -177,7 +177,8 @@ public class RecordingController activityStarter, mUserContextProvider, onStartRecordingClicked, - mMediaProjectionMetricsLogger)) + mMediaProjectionMetricsLogger, + mDialogFactory)) : new ScreenRecordDialog( context, /* controller= */ this, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt index 3f6c58d73346..56ada3a9f6e8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt @@ -54,6 +54,7 @@ class ScreenRecordPermissionDialogDelegate( private val userContextProvider: UserContextProvider, private val onStartRecordingClicked: Runnable?, mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, + private val systemUIDialogFactory: SystemUIDialog.Factory ) : BaseMediaProjectionPermissionDialogDelegate<SystemUIDialog>( createOptionList(), @@ -62,14 +63,18 @@ class ScreenRecordPermissionDialogDelegate( mediaProjectionMetricsLogger, R.drawable.ic_screenrecord, R.color.screenrecord_icon_color - ) { + ), SystemUIDialog.Delegate { private lateinit var tapsSwitch: Switch private lateinit var tapsView: View private lateinit var audioSwitch: Switch private lateinit var options: Spinner + override fun createDialog(): SystemUIDialog { + return systemUIDialogFactory.create(this) + } + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { - super.onCreate(dialog, savedInstanceState) + super<BaseMediaProjectionPermissionDialogDelegate>.onCreate(dialog, savedInstanceState) setDialogTitle(R.string.screenrecord_permission_dialog_title) dialog.setTitle(R.string.screenrecord_title) setStartButtonText(R.string.screenrecord_permission_dialog_continue) diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index d382b7ae2bf0..d13edf01cc4a 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -21,8 +21,6 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY; -import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; - import android.app.Activity; import android.content.res.Configuration; import android.graphics.Rect; @@ -89,17 +87,6 @@ public class BrightnessDialog extends Activity { if (mShadeInteractor.isQsExpanded().getValue()) { finish(); } - - View view = findViewById(R.id.brightness_mirror_container); - if (view != null) { - collectFlow(view, mShadeInteractor.isQsExpanded(), this::onShadeStateChange); - } - } - - private void onShadeStateChange(boolean isQsExpanded) { - if (isQsExpanded) { - requestFinish(); - } } private void setWindowAttributes() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7e84eeb24d73..66d70e6ee2a2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -130,6 +130,7 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewConfigurator; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -542,6 +543,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final NPVCDownEventState.Buffer mLastDownEvents; private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; + private final KeyguardClockInteractor mKeyguardClockInteractor; private float mMinExpandHeight; private boolean mPanelUpdateWhenAnimatorEnds; private boolean mHasVibratedOnOpen = false; @@ -760,6 +762,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump SystemClock systemClock, KeyguardBottomAreaViewModel keyguardBottomAreaViewModel, KeyguardBottomAreaInteractor keyguardBottomAreaInteractor, + KeyguardClockInteractor keyguardClockInteractor, AlternateBouncerInteractor alternateBouncerInteractor, DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel, OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel, @@ -964,6 +967,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump updateUserSwitcherFlags(); mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel; mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; + mKeyguardClockInteractor = keyguardClockInteractor; KeyguardLongPressViewBinder.bind( mView.requireViewById(R.id.keyguard_long_press), keyguardLongPressViewModel, @@ -1610,7 +1614,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled(); boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange(); if (mFeatureFlags.isEnabled(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT)) { - mKeyguardInteractor.setClockSize(computeDesiredClockSize()); + mKeyguardClockInteractor.setClockSize(computeDesiredClockSize()); } else { mKeyguardStatusViewController.displayClock(computeDesiredClockSize(), shouldAnimateClockChange); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 824de01ca59f..2df30dccb13d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -90,8 +90,13 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh private int mLastConfigurationWidthDp = -1; private int mLastConfigurationHeightDp = -1; - private List<Runnable> mOnCreateRunnables = new ArrayList<>(); + private final List<Runnable> mOnCreateRunnables = new ArrayList<>(); + /** + * @deprecated Don't subclass SystemUIDialog. Please subclass {@link Delegate} and pass it to + * {@link Factory#create(DialogDelegate)} to create a custom dialog. + */ + @Deprecated public SystemUIDialog(Context context) { this(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK); } @@ -109,27 +114,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh Dependency.get(SystemUIDialogManager.class), Dependency.get(SysUiState.class), Dependency.get(BroadcastDispatcher.class), - Dependency.get(DialogLaunchAnimator.class), - new DialogDelegate<>() {}); - } - - @Inject - public SystemUIDialog( - @Application Context context, - FeatureFlags featureFlags, - SystemUIDialogManager systemUIDialogManager, - SysUiState sysUiState, - BroadcastDispatcher broadcastDispatcher, - DialogLaunchAnimator dialogLaunchAnimator) { - this(context, - DEFAULT_THEME, - DEFAULT_DISMISS_ON_DEVICE_LOCK, - featureFlags, - systemUIDialogManager, - sysUiState, - broadcastDispatcher, - dialogLaunchAnimator, - new DialogDelegate<>(){}); + Dependency.get(DialogLaunchAnimator.class)); } public static class Factory { @@ -156,11 +141,25 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mDialogLaunchAnimator = dialogLaunchAnimator; } + /** Creates a new instance of {@link SystemUIDialog} with no customized behavior. + * + * When you just need a dialog, call this. + */ + public SystemUIDialog create() { + return create(new DialogDelegate<>(){}); + } + /** * Creates a new instance of {@link SystemUIDialog} with {@code delegate} as the {@link - * DialogDelegate}. + * Delegate}. + * + * When you need to customize the dialog, pass it a delegate. */ - public SystemUIDialog create(DialogDelegate<SystemUIDialog> delegate) { + public SystemUIDialog create(Delegate delegate) { + return create((DialogDelegate<SystemUIDialog>) delegate); + } + + private SystemUIDialog create(DialogDelegate<SystemUIDialog> dialogDelegate) { return new SystemUIDialog( mContext, DEFAULT_THEME, @@ -170,7 +169,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mSysUiState, mBroadcastDispatcher, mDialogLaunchAnimator, - delegate); + dialogDelegate); } } @@ -588,4 +587,18 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mDialog.dismiss(); } } + + /** + * A delegate class that should be implemented in place of sublcassing {@link SystemUIDialog}. + * + * Implement this interface and then pass an instance of your implementation to + * {@link SystemUIDialog.Factory#create(Delegate)}. + */ + public interface Delegate extends DialogDelegate<SystemUIDialog> { + /** + * Returns a new {@link SystemUIDialog} which has been passed this Delegate in its + * construction. + */ + SystemUIDialog createDialog(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java index e7e907a90d34..e4f1c87fc305 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.debug.IAdbManager; import android.hardware.usb.UsbManager; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.ServiceManager; @@ -69,8 +70,7 @@ public class UsbDebuggingActivity extends AlertActivity super.onCreate(icicle); // Emulator does not support reseating the usb cable to reshow the dialog. - boolean isEmulator = SystemProperties.get("ro.boot.qemu").equals("1"); - if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0 && !isEmulator) { + if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0 && !Build.IS_EMULATOR) { mDisconnectedReceiver = new UsbDisconnectedReceiver(this); IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE); mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java index 0ff8da5dfba7..8c8544cd6e5b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java @@ -20,16 +20,14 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; @@ -60,14 +58,13 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { @Rule public MockitoRule rule = MockitoJUnit.rule(); - @Mock - FingerprintManager mFingerprintManager; - @Mock - FaceManager mFaceManager; - @Mock - SystemUIDialog mDialog; + @Mock Resources mResources; + @Mock FingerprintManager mFingerprintManager; + @Mock FaceManager mFaceManager; + @Mock SystemUIDialog.Factory mSystemUIDialogFactory; + @Mock SystemUIDialog mDialog; + @Mock BiometricNotificationDialogFactory.ActivityStarter mActivityStarter; - private Context mContextSpy; private final ArgumentCaptor<DialogInterface.OnClickListener> mOnClickListenerArgumentCaptor = ArgumentCaptor.forClass(DialogInterface.OnClickListener.class); private final ArgumentCaptor<Intent> mIntentArgumentCaptor = @@ -76,15 +73,16 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { @Before public void setUp() throws ExecutionException, InterruptedException { - mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager); - mContext.addMockSystemService(FaceManager.class, mFaceManager); - mContextSpy = spy(mContext); - when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); - doNothing().when(mContextSpy).startActivity(any()); - - mDialogFactory = new BiometricNotificationDialogFactory(); + when(mSystemUIDialogFactory.create()).thenReturn(mDialog); + + mDialogFactory = new BiometricNotificationDialogFactory( + mResources, + mSystemUIDialogFactory, + mFingerprintManager, + mFaceManager + ); } @Test @@ -92,8 +90,7 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { assumeTrue(getContext().getPackageManager() .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)); - mDialogFactory.createReenrollDialog(mContextSpy, mDialog, - BiometricSourceType.FINGERPRINT); + mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT); verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture()); @@ -108,7 +105,7 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { removalCallbackArgumentCaptor.getValue().onRemovalSucceeded(null /* fp */, 0 /* remaining */); - verify(mContextSpy).startActivity(mIntentArgumentCaptor.capture()); + verify(mActivityStarter).startActivity(mIntentArgumentCaptor.capture()); assertThat(mIntentArgumentCaptor.getValue().getAction()).isEqualTo( Settings.ACTION_FINGERPRINT_ENROLL); } @@ -118,8 +115,7 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { assumeTrue(getContext().getPackageManager() .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)); - mDialogFactory.createReenrollDialog(mContextSpy, mDialog, - BiometricSourceType.FINGERPRINT); + mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FINGERPRINT); verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture()); @@ -134,7 +130,7 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { removalCallbackArgumentCaptor.getValue().onRemovalError(null /* fp */, 0 /* errmsgId */, "Error" /* errString */); - verify(mContextSpy, never()).startActivity(any()); + verify(mActivityStarter, never()).startActivity(any()); } @Test @@ -142,8 +138,7 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { assumeTrue(getContext().getPackageManager() .hasSystemFeature(PackageManager.FEATURE_FACE)); - mDialogFactory.createReenrollDialog(mContextSpy, mDialog, - BiometricSourceType.FACE); + mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE); verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture()); @@ -158,7 +153,7 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { removalCallbackArgumentCaptor.getValue().onRemovalSucceeded(null /* fp */, 0 /* remaining */); - verify(mContextSpy).startActivity(mIntentArgumentCaptor.capture()); + verify(mActivityStarter).startActivity(mIntentArgumentCaptor.capture()); assertThat(mIntentArgumentCaptor.getValue().getAction()).isEqualTo( "android.settings.FACE_ENROLL"); } @@ -168,8 +163,7 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { assumeTrue(getContext().getPackageManager() .hasSystemFeature(PackageManager.FEATURE_FACE)); - mDialogFactory.createReenrollDialog(mContextSpy, mDialog, - BiometricSourceType.FACE); + mDialogFactory.createReenrollDialog(0, mActivityStarter, BiometricSourceType.FACE); verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture()); @@ -184,6 +178,6 @@ public class BiometricNotificationDialogFactoryTest extends SysuiTestCase { removalCallbackArgumentCaptor.getValue().onRemovalError(null /* face */, 0 /* errmsgId */, "Error" /* errString */); - verify(mContextSpy, never()).startActivity(any()); + verify(mActivityStarter, never()).startActivity(any()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java index 3c106789290d..c6771b262114 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java @@ -18,9 +18,7 @@ package com.android.systemui.biometrics; import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG; import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -71,6 +69,8 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { @Mock NotificationManager mNotificationManager; @Mock + BiometricNotificationDialogFactory mNotificationDialogFactory; + @Mock Optional<FingerprintReEnrollNotification> mFingerprintReEnrollNotificationOptional; @Mock FingerprintReEnrollNotification mFingerprintReEnrollNotification; @@ -103,9 +103,8 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { mLooper = TestableLooper.get(this); Handler handler = new Handler(mLooper.getLooper()); - BiometricNotificationDialogFactory dialogFactory = new BiometricNotificationDialogFactory(); BiometricNotificationBroadcastReceiver broadcastReceiver = - new BiometricNotificationBroadcastReceiver(mContext, dialogFactory); + new BiometricNotificationBroadcastReceiver(mContext, mNotificationDialogFactory); mBiometricNotificationService = new BiometricNotificationService(mContext, mKeyguardUpdateMonitor, mKeyguardStateController, handler, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java index eee0ed45345d..4022d4388ab1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java @@ -16,8 +16,10 @@ package com.android.systemui.bluetooth; +import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK; import static com.google.common.truth.Truth.assertThat; - +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -26,31 +28,41 @@ import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.test.filters.SmallTest; -import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; -import com.android.systemui.res.R; -import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.DialogLaunchAnimator; +import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; +import com.android.systemui.model.SysUiState; +import com.android.systemui.res.R; +import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; 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; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class BroadcastDialogTest extends SysuiTestCase { +public class BroadcastDialogDelegateTest extends SysuiTestCase { private static final String CURRENT_BROADCAST_APP = "Music"; private static final String SWITCH_APP = "System UI"; @@ -61,33 +73,64 @@ public class BroadcastDialogTest extends SysuiTestCase { private final LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast = mock( LocalBluetoothLeBroadcast.class); private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class); - private BroadcastDialog mBroadcastDialog; - private View mDialogView; + private BroadcastDialogDelegate mBroadcastDialogDelegate; + private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); + @Mock SystemUIDialog.Factory mSystemUIDialogFactory; + @Mock SystemUIDialogManager mDialogManager; + @Mock SysUiState mSysUiState; + @Mock DialogLaunchAnimator mDialogLaunchAnimator; + @Mock MediaOutputDialogFactory mMediaOutputDialogFactory; + private SystemUIDialog mDialog; private TextView mTitle; private TextView mSubTitle; private Button mSwitchBroadcastAppButton; private Button mChangeOutputButton; + private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null); - mBroadcastDialog = new BroadcastDialog(mContext, mock(MediaOutputDialogFactory.class), - mLocalBluetoothManager, CURRENT_BROADCAST_APP, TEST_PACKAGE, - mock(UiEventLogger.class), mBroadcastSender); - mBroadcastDialog.show(); - mDialogView = mBroadcastDialog.mDialogView; + + mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true); + when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSystemUIDialogFactory.create(any())).thenReturn(mDialog); + + mBroadcastDialogDelegate = new BroadcastDialogDelegate( + mContext, + mMediaOutputDialogFactory, + mLocalBluetoothManager, + new UiEventLoggerFake(), + mFakeExecutor, + mBroadcastSender, + mSystemUIDialogFactory, + CURRENT_BROADCAST_APP, + TEST_PACKAGE); + + mDialog = new SystemUIDialog( + mContext, + 0, + DEFAULT_DISMISS_ON_DEVICE_LOCK, + mFeatureFlags, + mDialogManager, + mSysUiState, + getFakeBroadcastDispatcher(), + mDialogLaunchAnimator, + mBroadcastDialogDelegate + ); + + mDialog.show(); } @After public void tearDown() { - mBroadcastDialog.dismiss(); + mDialog.dismiss(); } @Test public void onCreate_withCurrentApp_titleIsCurrentAppName() { - mTitle = mDialogView.requireViewById(R.id.dialog_title); + mTitle = mDialog.requireViewById(R.id.dialog_title); assertThat(mTitle.getText().toString()).isEqualTo(mContext.getString( R.string.bt_le_audio_broadcast_dialog_title, CURRENT_BROADCAST_APP)); @@ -95,7 +138,7 @@ public class BroadcastDialogTest extends SysuiTestCase { @Test public void onCreate_withCurrentApp_subTitleIsSwitchAppName() { - mSubTitle = mDialogView.requireViewById(R.id.dialog_subtitle); + mSubTitle = mDialog.requireViewById(R.id.dialog_subtitle); assertThat(mSubTitle.getText()).isEqualTo( mContext.getString(R.string.bt_le_audio_broadcast_dialog_sub_title, SWITCH_APP)); @@ -103,7 +146,7 @@ public class BroadcastDialogTest extends SysuiTestCase { @Test public void onCreate_withCurrentApp_switchBtnIsSwitchAppName() { - mSwitchBroadcastAppButton = mDialogView.requireViewById(R.id.switch_broadcast); + mSwitchBroadcastAppButton = mDialog.requireViewById(R.id.switch_broadcast); assertThat(mSwitchBroadcastAppButton.getText().toString()).isEqualTo( mContext.getString(R.string.bt_le_audio_broadcast_dialog_switch_app, SWITCH_APP)); @@ -111,17 +154,17 @@ public class BroadcastDialogTest extends SysuiTestCase { @Test public void onClick_withChangeOutput_dismissBroadcastDialog() { - mChangeOutputButton = mDialogView.requireViewById(R.id.change_output); + mChangeOutputButton = mDialog.requireViewById(R.id.change_output); mChangeOutputButton.performClick(); - assertThat(mBroadcastDialog.isShowing()).isFalse(); + assertThat(mDialog.isShowing()).isFalse(); } @Test public void onClick_withSwitchBroadcast_stopCurrentBroadcast() { when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( mLocalBluetoothLeBroadcast); - mSwitchBroadcastAppButton = mDialogView.requireViewById(R.id.switch_broadcast); + mSwitchBroadcastAppButton = mDialog.requireViewById(R.id.switch_broadcast); mSwitchBroadcastAppButton.performClick(); verify(mLocalBluetoothLeBroadcast).stopLatestBroadcast(); @@ -129,7 +172,7 @@ public class BroadcastDialogTest extends SysuiTestCase { @Test public void onClick_withSwitchBroadcast_stopCurrentBroadcastFailed() { - mSwitchBroadcastAppButton = mDialogView.requireViewById(R.id.switch_broadcast); + mSwitchBroadcastAppButton = mDialog.requireViewById(R.id.switch_broadcast); mSwitchBroadcastAppButton.performClick(); assertThat(mSwitchBroadcastAppButton.getText().toString()).isEqualTo( @@ -139,9 +182,9 @@ public class BroadcastDialogTest extends SysuiTestCase { @Test public void handleLeBroadcastStopped_withBroadcastProfileNull_doRefreshButton() { when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null); - mSwitchBroadcastAppButton = mDialogView.requireViewById(R.id.switch_broadcast); + mSwitchBroadcastAppButton = mDialog.requireViewById(R.id.switch_broadcast); - mBroadcastDialog.handleLeBroadcastStopped(); + mBroadcastDialogDelegate.handleLeBroadcastStopped(); assertThat(mSwitchBroadcastAppButton.getText().toString()).isEqualTo( mContext.getString(R.string.bt_le_audio_broadcast_dialog_switch_app, SWITCH_APP)); @@ -152,7 +195,7 @@ public class BroadcastDialogTest extends SysuiTestCase { when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn( mLocalBluetoothLeBroadcast); - mBroadcastDialog.handleLeBroadcastStopped(); + mBroadcastDialogDelegate.handleLeBroadcastStopped(); verify(mLocalBluetoothLeBroadcast).startBroadcast(eq(SWITCH_APP), any()); } @@ -160,17 +203,17 @@ public class BroadcastDialogTest extends SysuiTestCase { @Test public void handleLeBroadcastMetadataChanged_withNotLaunchFlag_doNotDismissDialog() { - mBroadcastDialog.handleLeBroadcastMetadataChanged(); + mBroadcastDialogDelegate.handleLeBroadcastMetadataChanged(); - assertThat(mBroadcastDialog.isShowing()).isTrue(); + assertThat(mDialog.isShowing()).isTrue(); } @Test public void handleLeBroadcastMetadataChanged_withLaunchFlag_dismissDialog() { - mBroadcastDialog.handleLeBroadcastStarted(); - mBroadcastDialog.handleLeBroadcastMetadataChanged(); + mBroadcastDialogDelegate.handleLeBroadcastStarted(); + mBroadcastDialogDelegate.handleLeBroadcastMetadataChanged(); - assertThat(mBroadcastDialog.isShowing()).isFalse(); + assertThat(mDialog.isShowing()).isFalse(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt new file mode 100644 index 000000000000..e931384fd61e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.contrast + +import android.app.UiModeManager +import android.app.UiModeManager.ContrastUtils.fromContrastLevel +import android.os.Looper +import android.provider.Settings +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.LayoutInflater +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.model.SysUiState +import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.time.FakeSystemClock +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.eq +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** Test the behaviour of buttons of the [ContrastDialogDelegate]. */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class ContrastDialogDelegateTest : SysuiTestCase() { + + private val mainExecutor = FakeExecutor(FakeSystemClock()) + private lateinit var mContrastDialogDelegate: ContrastDialogDelegate + @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory + @Mock private lateinit var sysuiDialog: SystemUIDialog + @Mock private lateinit var mockUiModeManager: UiModeManager + @Mock private lateinit var mockUserTracker: UserTracker + @Mock private lateinit var mockSecureSettings: SecureSettings + @Mock private lateinit var sysuiState: SysUiState + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mDependency.injectTestDependency(FeatureFlags::class.java, FakeFeatureFlags()) + mDependency.injectTestDependency(SysUiState::class.java, sysuiState) + mDependency.injectMockDependency(DialogLaunchAnimator::class.java) + whenever(sysuiState.setFlag(any(), any())).thenReturn(sysuiState) + whenever(sysuiDialogFactory.create(any())).thenReturn(sysuiDialog) + whenever(sysuiDialog.layoutInflater).thenReturn(LayoutInflater.from(mContext)) + + whenever(mockUserTracker.userId).thenReturn(context.userId) + if (Looper.myLooper() == null) Looper.prepare() + + mContrastDialogDelegate = + ContrastDialogDelegate( + sysuiDialogFactory, + mainExecutor, + mockUiModeManager, + mockUserTracker, + mockSecureSettings + ) + } + + @Test + fun testClickButtons_putsContrastInSettings() { + mContrastDialogDelegate.onCreate(sysuiDialog, null) + + mContrastDialogDelegate.contrastButtons.forEach { + (contrastLevel: Int, clickedButton: FrameLayout) -> + clickedButton.performClick() + mainExecutor.runAllReady() + verify(mockSecureSettings) + .putFloatForUser( + eq(Settings.Secure.CONTRAST_LEVEL), + eq(fromContrastLevel(contrastLevel)), + eq(context.userId) + ) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogTest.kt deleted file mode 100644 index 7753197afc9b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.contrast - -import android.app.UiModeManager -import android.app.UiModeManager.ContrastUtils.fromContrastLevel -import android.os.Looper -import android.provider.Settings -import android.testing.AndroidTestingRunner -import android.widget.FrameLayout -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.settings.UserTracker -import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.settings.SecureSettings -import com.google.common.util.concurrent.MoreExecutors -import java.util.concurrent.Executor -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.eq -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -/** Test the behaviour of buttons of the [ContrastDialog]. */ -@SmallTest -@RunWith(AndroidTestingRunner::class) -class ContrastDialogTest : SysuiTestCase() { - - private lateinit var mainExecutor: Executor - private lateinit var contrastDialog: ContrastDialog - @Mock private lateinit var mockUiModeManager: UiModeManager - @Mock private lateinit var mockUserTracker: UserTracker - @Mock private lateinit var mockSecureSettings: SecureSettings - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - mainExecutor = MoreExecutors.directExecutor() - whenever(mockUserTracker.userId).thenReturn(context.userId) - } - - @Test - fun testClickButtons_putsContrastInSettings() { - if (Looper.myLooper() == null) Looper.prepare() - contrastDialog = - ContrastDialog( - context, - mainExecutor, - mockUiModeManager, - mockUserTracker, - mockSecureSettings - ) - contrastDialog.show() - try { - contrastDialog.contrastButtons.forEach { - (contrastLevel: Int, clickedButton: FrameLayout) -> - clickedButton.performClick() - verify(mockSecureSettings) - .putFloatForUser( - eq(Settings.Secure.CONTRAST_LEVEL), - eq(fromContrastLevel(contrastLevel)), - eq(context.userId) - ) - } - } finally { - contrastDialog.dismiss() - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt index bc40c2ddb407..d75cbec8c542 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import android.provider.Settings import androidx.test.filters.SmallTest +import com.android.keyguard.ClockEventController import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.shared.model.SettingsClockSize @@ -47,6 +48,7 @@ class KeyguardClockRepositoryTest : SysuiTestCase() { private lateinit var underTest: KeyguardClockRepository private lateinit var fakeSettings: FakeSettings @Mock private lateinit var clockRegistry: ClockRegistry + @Mock private lateinit var clockEventController: ClockEventController @Before fun setup() { @@ -55,7 +57,14 @@ class KeyguardClockRepositoryTest : SysuiTestCase() { scheduler = TestCoroutineScheduler() dispatcher = StandardTestDispatcher(scheduler) scope = TestScope(dispatcher) - underTest = KeyguardClockRepository(fakeSettings, clockRegistry, dispatcher) + underTest = + KeyguardClockRepositoryImpl( + fakeSettings, + clockRegistry, + clockEventController, + dispatcher, + scope.backgroundScope + ) } @Test 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 new file mode 100644 index 000000000000..0981c6239cb9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt @@ -0,0 +1,103 @@ +/* + * 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.keyguard.ui.binder + +import android.view.View +import androidx.constraintlayout.helper.widget.Layer +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.ClockConfig +import com.android.systemui.plugins.ClockController +import com.android.systemui.plugins.ClockFaceController +import com.android.systemui.plugins.ClockFaceLayout +import com.android.systemui.util.mockito.whenever +import kotlin.test.Test +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +@kotlinx.coroutines.ExperimentalCoroutinesApi +class KeyguardClockViewBinderTest : SysuiTestCase() { + @Mock private lateinit var rootView: ConstraintLayout + @Mock private lateinit var burnInLayer: Layer + @Mock private lateinit var clock: ClockController + @Mock private lateinit var largeClock: ClockFaceController + @Mock private lateinit var smallClock: ClockFaceController + @Mock private lateinit var largeClockView: View + @Mock private lateinit var smallClockView: View + @Mock private lateinit var smallClockFaceLayout: ClockFaceLayout + @Mock private lateinit var largeClockFaceLayout: ClockFaceLayout + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun addClockViews_nonWeatherClock() { + setupNonWeatherClock() + KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer) + verify(rootView).addView(smallClockView) + verify(rootView).addView(largeClockView) + verify(burnInLayer).addView(smallClockView) + verify(burnInLayer, never()).addView(largeClockView) + } + + @Test + fun addClockViews_WeatherClock() { + setupWeatherClock() + KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer) + verify(rootView).addView(smallClockView) + verify(rootView).addView(largeClockView) + verify(burnInLayer).addView(smallClockView) + verify(burnInLayer).addView(largeClockView) + } + + private fun setupWeatherClock() { + setupClock() + val clockConfig = + ClockConfig( + id = "WEATHER_CLOCK", + name = "", + description = "", + useAlternateSmartspaceAODTransition = true + ) + whenever(clock.config).thenReturn(clockConfig) + } + + private fun setupNonWeatherClock() { + setupClock() + val clockConfig = ClockConfig(id = "NON_WEATHER_CLOCK", name = "", description = "") + whenever(clock.config).thenReturn(clockConfig) + } + + private fun setupClock() { + whenever(largeClockFaceLayout.views).thenReturn(listOf(largeClockView)) + whenever(smallClockFaceLayout.views).thenReturn(listOf(smallClockView)) + whenever(clock.largeClock).thenReturn(largeClock) + whenever(clock.smallClock).thenReturn(smallClock) + whenever(largeClock.layout).thenReturn(largeClockFaceLayout) + whenever(smallClock.layout).thenReturn(smallClockFaceLayout) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 740fce988a68..3109e761e423 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -27,9 +27,9 @@ import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialInd import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.KeyguardRootView -import com.android.systemui.keyguard.ui.view.layout.items.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection +import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection 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 6b85cf719ef5..64a07fa9f723 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 @@ -22,9 +22,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.ui.view.layout.items.ClockSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R @@ -32,7 +30,6 @@ import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.Utils import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import dagger.Lazy import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -47,7 +44,6 @@ class ClockSectionTest : SysuiTestCase() { @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel @Mock private lateinit var splitShadeStateController: SplitShadeStateController - @Mock private lateinit var keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor> private var featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic() private lateinit var underTest: ClockSection @@ -94,7 +90,6 @@ class ClockSectionTest : SysuiTestCase() { smartspaceViewModel, mContext, splitShadeStateController, - keyguardBlueprintInteractor, featureFlags ) } 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 46a7735d92a0..f067871aa0e1 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 @@ -25,11 +25,16 @@ import com.android.keyguard.KeyguardClockSwitch.SMALL import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.KeyguardClockRepository +import com.android.systemui.keyguard.data.repository.KeyguardClockRepositoryImpl import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.plugins.ClockController +import com.android.systemui.plugins.ClockFaceConfig +import com.android.systemui.plugins.ClockFaceController import com.android.systemui.shared.clocks.ClockRegistry +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlin.test.Test @@ -50,7 +55,6 @@ class KeyguardClockViewModelTest : SysuiTestCase() { private lateinit var scheduler: TestCoroutineScheduler private lateinit var dispatcher: CoroutineDispatcher private lateinit var scope: TestScope - private lateinit var underTest: KeyguardClockViewModel private lateinit var keyguardInteractor: KeyguardInteractor private lateinit var keyguardRepository: KeyguardRepository @@ -58,6 +62,9 @@ class KeyguardClockViewModelTest : SysuiTestCase() { private lateinit var keyguardClockRepository: KeyguardClockRepository private lateinit var fakeSettings: FakeSettings @Mock private lateinit var clockRegistry: ClockRegistry + @Mock private lateinit var clock: ClockController + @Mock private lateinit var largeClock: ClockFaceController + @Mock private lateinit var clockFaceConfig: ClockFaceConfig @Mock private lateinit var eventController: ClockEventController @Before fun setup() { @@ -70,13 +77,21 @@ class KeyguardClockViewModelTest : SysuiTestCase() { scheduler = TestCoroutineScheduler() dispatcher = StandardTestDispatcher(scheduler) scope = TestScope(dispatcher) - keyguardClockRepository = KeyguardClockRepository(fakeSettings, clockRegistry, dispatcher) - keyguardClockInteractor = KeyguardClockInteractor(eventController, keyguardClockRepository) + setupMockClock() + keyguardClockRepository = + KeyguardClockRepositoryImpl( + fakeSettings, + clockRegistry, + eventController, + dispatcher, + scope.backgroundScope + ) + keyguardClockInteractor = KeyguardClockInteractor(keyguardClockRepository) underTest = KeyguardClockViewModel( keyguardInteractor, keyguardClockInteractor, - scope.backgroundScope + scope.backgroundScope, ) } @@ -86,7 +101,7 @@ class KeyguardClockViewModelTest : SysuiTestCase() { // When use double line clock is disabled, // should always return small fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 0) - keyguardRepository.setClockSize(LARGE) + keyguardClockRepository.setClockSize(LARGE) val value = collectLastValue(underTest.clockSize) assertThat(value()).isEqualTo(SMALL) } @@ -95,12 +110,19 @@ class KeyguardClockViewModelTest : SysuiTestCase() { fun testClockSize_dynamicClockSize() = scope.runTest { fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) - keyguardRepository.setClockSize(SMALL) + keyguardClockRepository.setClockSize(SMALL) var value = collectLastValue(underTest.clockSize) assertThat(value()).isEqualTo(SMALL) - keyguardRepository.setClockSize(LARGE) + keyguardClockRepository.setClockSize(LARGE) value = collectLastValue(underTest.clockSize) assertThat(value()).isEqualTo(LARGE) } + + private fun setupMockClock() { + whenever(clock.largeClock).thenReturn(largeClock) + whenever(largeClock.config).thenReturn(clockFaceConfig) + whenever(clockFaceConfig.hasCustomWeatherDataDisplay).thenReturn(false) + whenever(clockRegistry.createCurrentClock()).thenReturn(clock) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index a57feda64723..bc60364f27cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -21,6 +21,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.view.View import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.systemui.Flags as AConfigFlags import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION import com.android.systemui.SysUITestComponent @@ -32,7 +33,9 @@ import com.android.systemui.common.ui.data.repository.FakeConfigurationRepositor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository +import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor @@ -79,13 +82,13 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(JUnit4::class) class KeyguardRootViewModelTest : SysuiTestCase() { - private lateinit var underTest: KeyguardRootViewModel private lateinit var testScope: TestScope private lateinit var repository: FakeKeyguardRepository private lateinit var keyguardInteractor: KeyguardInteractor private lateinit var configurationRepository: FakeConfigurationRepository @Mock private lateinit var burnInInteractor: BurnInInteractor + @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel @Mock @@ -97,7 +100,9 @@ class KeyguardRootViewModelTest : SysuiTestCase() { private val enterFromTopAnimationAlpha = MutableStateFlow(0f) private val goneToAodTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1) private val dozeAmountTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1) + private val clockSize = MutableStateFlow(LARGE) private val startedKeyguardState = MutableStateFlow(KeyguardState.GONE) + private val featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic() @Before fun setUp() { @@ -107,7 +112,9 @@ class KeyguardRootViewModelTest : SysuiTestCase() { mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - val withDeps = KeyguardInteractorFactory.create() + featureFlags.set(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT, false) + + val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) keyguardInteractor = withDeps.keyguardInteractor repository = withDeps.repository configurationRepository = withDeps.configurationRepository @@ -124,6 +131,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() { whenever(keyguardTransitionInteractor.dozeAmountTransition) .thenReturn(dozeAmountTransitionStep) whenever(keyguardTransitionInteractor.startedKeyguardState).thenReturn(startedKeyguardState) + whenever(keyguardClockViewModel.clockSize).thenReturn(clockSize) underTest = KeyguardRootViewModel( @@ -139,9 +147,12 @@ class KeyguardRootViewModelTest : SysuiTestCase() { whenever(isPulseExpanding).thenReturn(emptyFlow()) }, burnInInteractor, + keyguardClockViewModel, goneToAodTransitionViewModel, aodToLockscreenTransitionViewModel, screenOffAnimationController = mock(), + // TODO(b/310989341): remove after change to aconfig + featureFlags ) underTest.clockControllerProvider = Provider { clockController } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index 1b4ba6433ded..cb90cc53f0f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -328,7 +328,7 @@ public class RecordingControllerTest extends SysuiTestCase { } @Override - public SystemUIDialog create(DialogDelegate<SystemUIDialog> delegate) { + public SystemUIDialog create(SystemUIDialog.Delegate delegate) { SystemUIDialog dialog = super.create(delegate); mLastDelegate = delegate; mLastCreatedDialog = dialog; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt index c848287aa53e..8f696e7f11ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt @@ -89,8 +89,9 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() { userContextProvider, onStartRecordingClicked, mediaProjectionMetricsLogger, + systemUIDialogFactory ) - dialog = systemUIDialogFactory.create(delegate) + dialog = delegate.createDialog() delegate.onCreate(dialog, savedInstanceState = null) whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index 569c8d9d350d..88c728fd1b66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -36,10 +36,8 @@ 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 dagger.Lazy import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Rule @@ -61,6 +59,7 @@ class BrightnessDialogTest : SysuiTestCase() { @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory @Mock private lateinit var brightnessController: BrightnessController @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper + @Mock private lateinit var shadeInteractorLazy: Lazy<ShadeInteractor> @Mock private lateinit var shadeInteractor: ShadeInteractor private val clock = FakeSystemClock() @@ -90,6 +89,7 @@ class BrightnessDialogTest : SysuiTestCase() { .thenReturn(brightnessSliderController) `when`(brightnessSliderController.rootView).thenReturn(View(context)) `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController) + whenever(shadeInteractorLazy.get()).thenReturn(shadeInteractor) whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false)) } @@ -180,20 +180,6 @@ class BrightnessDialogTest : SysuiTestCase() { assertThat(activityRule.activity.isFinishing()).isFalse() } - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun testFinishOnQSExpanded() = runTest { - val isQSExpanded = MutableStateFlow(false) - `when`(shadeInteractor.isQsExpanded).thenReturn(isQSExpanded) - activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)) - - assertThat(activityRule.activity.isFinishing()).isFalse() - - isQSExpanded.value = true - advanceUntilIdle() - assertThat(activityRule.activity.isFinishing()).isTrue() - } - class TestDialog( brightnessSliderControllerFactory: BrightnessSliderController.Factory, brightnessControllerFactory: BrightnessController.Factory, 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 03878b7bcf45..97378c3cc5a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -96,6 +96,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewConfigurator; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; @@ -343,6 +344,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected final int mMaxUdfpsBurnInOffsetY = 5; protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; + protected KeyguardClockInteractor mKeyguardClockInteractor; protected FakeKeyguardRepository mFakeKeyguardRepository; protected KeyguardInteractor mKeyguardInteractor; protected SceneTestUtils mUtils = new SceneTestUtils(this); @@ -696,6 +698,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { systemClock, mKeyguardBottomAreaViewModel, mKeyguardBottomAreaInteractor, + mKeyguardClockInteractor, mAlternateBouncerInteractor, mDreamingToLockscreenTransitionViewModel, mOccludedToLockscreenTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt index 6838e7676a23..abbd9be66b17 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/FakeKeyguardDataLayerModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.data import com.android.systemui.keyguard.data.repository.FakeCommandQueueModule +import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepositoryModule import com.android.systemui.keyguard.data.repository.FakeKeyguardRepositoryModule import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepositoryModule import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepositoryModule @@ -28,6 +29,7 @@ import dagger.Module FakeKeyguardRepositoryModule::class, FakeKeyguardTransitionRepositoryModule::class, FakeKeyguardSurfaceBehindRepositoryModule::class, + FakeKeyguardClockRepositoryModule::class, ] ) object FakeKeyguardDataLayerModule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt new file mode 100644 index 000000000000..21936c36179c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt @@ -0,0 +1,56 @@ +/* + * 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.keyguard.data.repository + +import com.android.keyguard.ClockEventController +import com.android.keyguard.KeyguardClockSwitch.ClockSize +import com.android.keyguard.KeyguardClockSwitch.LARGE +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.plugins.ClockId +import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID +import com.android.systemui.util.mockito.mock +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeKeyguardClockRepository @Inject constructor() : KeyguardClockRepository { + private val _clockSize = MutableStateFlow(LARGE) + override val clockSize: StateFlow<Int> = _clockSize + + private val _selectedClockSize = MutableStateFlow(SettingsClockSize.DYNAMIC) + override val selectedClockSize = _selectedClockSize + + private val _currentClockId = MutableStateFlow(DEFAULT_CLOCK_ID) + override val currentClockId: Flow<ClockId> = _currentClockId + + private val _currentClock = MutableStateFlow(null) + override val currentClock = _currentClock + override val clockEventController: ClockEventController + get() = mock() + + override fun setClockSize(@ClockSize size: Int) { + _clockSize.value = size + } +} + +@Module +interface FakeKeyguardClockRepositoryModule { + @Binds fun bindFake(fake: FakeKeyguardClockRepository): KeyguardClockRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 4068e408f0bd..75fe37eddd70 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point -import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel @@ -42,8 +41,6 @@ import kotlinx.coroutines.flow.asStateFlow class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _deferKeyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow() override val keyguardDone: Flow<KeyguardDone> = _deferKeyguardDone - private val _clockSize = MutableStateFlow<Int>(LARGE) - override val clockSize: Flow<Int> = _clockSize private val _clockShouldBeCentered = MutableStateFlow<Boolean>(true) override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered @@ -187,10 +184,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _deferKeyguardDone.emit(timing) } - override fun setClockSize(size: Int) { - _clockSize.value = size - } - override fun setClockShouldBeCentered(shouldBeCentered: Boolean) { _clockShouldBeCentered.value = shouldBeCentered } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryKosmos.kt new file mode 100644 index 000000000000..e6716ba32cda --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.keyguard.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.keyguardClockRepository: KeyguardClockRepository by + Kosmos.Fixture { fakeKeyguardClockRepository } +val Kosmos.fakeKeyguardClockRepository by Kosmos.Fixture { FakeKeyguardClockRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt new file mode 100644 index 000000000000..d791e949f853 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardClockRepository +import com.android.systemui.kosmos.Kosmos + +val Kosmos.keyguardClockInteractor by + Kosmos.Fixture { KeyguardClockInteractor(keyguardClockRepository = keyguardClockRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt new file mode 100644 index 000000000000..d8786830f536 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt @@ -0,0 +1,31 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope + +val Kosmos.keyguardClockViewModel by + Kosmos.Fixture { + KeyguardClockViewModel( + keyguardInteractor = keyguardInteractor, + keyguardClockInteractor = keyguardClockInteractor, + applicationScope = applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 663b8450e690..4f807e3ddb64 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.applicationContext import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.keyguard.domain.interactor.burnInInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor @@ -42,5 +43,7 @@ val Kosmos.keyguardRootViewModel by Fixture { goneToAodTransitionViewModel = goneToAodTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, screenOffAnimationController = screenOffAnimationController, + keyguardClockViewModel = keyguardClockViewModel, + featureFlags = FakeFeatureFlagsClassic(), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index 92ec4f22001b..a70b91da6145 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -24,6 +24,7 @@ import dagger.Module import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow /** Fake implementation of [ShadeRepository] */ @SysUISingleton diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING index 72eb665bee65..031984829e77 100644 --- a/ravenwood/TEST_MAPPING +++ b/ravenwood/TEST_MAPPING @@ -1,7 +1,21 @@ { - "presubmit": [ - // Let's only run this one as a smoke test. - // TODO: Enable it once the infra knows how to run it. - // { "name": "CtsUtilTestCasesRavenwood" } - ] + "presubmit": [ + { + "name": "RavenwoodMockitoTest_device" + } + ], + "ravenwood-presubmit": [ + { + "name": "RavenwoodMinimumTest", + "host": true + }, + { + "name": "RavenwoodMockitoTest", + "host": true + }, + { + "name": "CtsUtilTestCasesRavenwood", + "host": true + } + ] } diff --git a/ravenwood/minimum-test/Android.bp b/ravenwood/minimum-test/Android.bp new file mode 100644 index 000000000000..bf3583cebd2c --- /dev/null +++ b/ravenwood/minimum-test/Android.bp @@ -0,0 +1,24 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +// Minimum ravenwood test according to test-authors.md. +android_ravenwood_test { + name: "RavenwoodMinimumTest", + + static_libs: [ + "androidx.annotation_annotation", + "androidx.test.rules", + ], + + srcs: [ + "test/**/*.java", + ], + sdk_version: "test_current", + auto_gen_config: true, +} diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java new file mode 100644 index 000000000000..085c18622885 --- /dev/null +++ b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java @@ -0,0 +1,45 @@ +/* + * 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.ravenwood; + +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RavenwoodMinimumTest { + @Rule + public RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProcessApp() + .build(); + + @Test + public void testSimple() { + Assert.assertTrue(android.os.Process.isApplicationUid(android.os.Process.myUid())); + } + + @Test + @IgnoreUnderRavenwood + public void testIgnored() { + throw new RuntimeException("Shouldn't be executed under ravenwood"); + } +} diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md index 2b5bd9083a40..5adef534a2b2 100644 --- a/ravenwood/test-authors.md +++ b/ravenwood/test-authors.md @@ -31,6 +31,14 @@ android_ravenwood_test { * Write your unit test just like you would for an Android device: ``` +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + @RunWith(AndroidJUnit4.class) public class MyCodeTest { @Test @@ -43,6 +51,14 @@ public class MyCodeTest { * APIs available under Ravenwood are stateless by default. If your test requires explicit states (such as defining the UID you’re running under, or requiring a main `Looper` thread), add a `RavenwoodRule` to declare that: ``` +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + @RunWith(AndroidJUnit4.class) public class MyCodeTest { @Rule diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java index 2032a5021cd9..b4deeb0a6872 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java @@ -29,12 +29,14 @@ import android.companion.virtual.VirtualDeviceManager; import android.content.ComponentName; import android.content.Context; import android.hardware.display.DisplayManager; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.ArraySet; import android.util.IntArray; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -66,8 +68,8 @@ import java.util.function.Consumer; * TODO(241117292): Remove or cut down during simultaneous user refactoring. */ public class ProxyManager { - private static final boolean DEBUG = false; private static final String LOG_TAG = "ProxyManager"; + private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) && Build.IS_DEBUGGABLE; // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in // the infos of connection.setInstalledAndEnabledServices diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index e3797c98bebe..f55ecb05c55f 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -916,18 +916,27 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH && event.getPointerCount() == 2; mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); - if (isActivated() && event.getPointerCount() == 2) { - storePointerDownLocation(mSecondPointerDownLocation, event); - mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE, - ViewConfiguration.getTapTimeout()); - } else if (mIsTwoFingerCountReached) { - // Placing two-finger triple-taps behind isActivated to avoid - // blocking panning scaling state + if (event.getPointerCount() == 2) { if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) { // 3tap and hold afterLongTapTimeoutTransitionToDraggingState(event); } else { - afterMultiTapTimeoutTransitionToDelegatingState(); + if (mDetectTwoFingerTripleTap) { + // If mDetectTwoFingerTripleTap, delay transition to the delegating + // state for mMultiTapMaxDelay to ensure reachability of + // multi finger multi tap + afterMultiTapTimeoutTransitionToDelegatingState(); + } + + if (isActivated()) { + // If activated, delay transition to the panning scaling + // state for tap timeout to ensure reachability of + // multi finger multi tap + storePointerDownLocation(mSecondPointerDownLocation, event); + mHandler.sendEmptyMessageDelayed( + MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE, + ViewConfiguration.getTapTimeout()); + } } } else { transitionToDelegatingStateAndClear(); @@ -953,6 +962,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // (which is a rare combo to be used aside from magnification) if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) { transitionToViewportDraggingStateAndClear(event); + } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event) + && event.getPointerCount() == 2) { + transitionToViewportDraggingStateAndClear(event); } else if (isActivated() && event.getPointerCount() == 2) { if (mIsSinglePanningEnabled && overscrollState(event, mFirstPointerDownLocation) @@ -961,11 +973,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } //Primary pointer is swiping, so transit to PanningScalingState transitToPanningScalingStateAndClear(); - } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event) - && event.getPointerCount() == 2) { - // Placing two-finger triple-taps behind isActivated to avoid - // blocking panning scaling state - transitionToViewportDraggingStateAndClear(event); } else if (mIsSinglePanningEnabled && isActivated() && event.getPointerCount() == 1) { @@ -979,8 +986,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation) && distanceClosestPointerToPoint( - mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) { - //Second pointer is swiping, so transit to PanningScalingState + mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance + // If mCompleteTapCount is not zero, it means that it is a multi tap + // gesture. So, we should not transit to the PanningScalingState. + && mCompletedTapCount == 0) { + // Second pointer is swiping, so transit to PanningScalingState transitToPanningScalingStateAndClear(); } } @@ -988,6 +998,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH case ACTION_UP: { mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); + mHandler.removeMessages(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE); if (!mFullScreenMagnificationController.magnificationRegionContains( mDisplayId, event.getX(), event.getY())) { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index f2d9759a7cc9..f8f3d82556fa 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -886,9 +886,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL); } - synchronized (mStats) { - return mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, queries); - } + return mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, queries); } /** Register callbacks for statsd pulled atoms. */ @@ -2730,13 +2728,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub BatteryUsageStatsQuery query = builder.build(); synchronized (mStats) { mStats.prepareForDumpLocked(); - BatteryUsageStats batteryUsageStats = - mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query); - if (proto) { - batteryUsageStats.dumpToProto(fd); - } else { - batteryUsageStats.dump(pw, ""); - } + } + BatteryUsageStats batteryUsageStats = + mBatteryUsageStatsProvider.getBatteryUsageStats(mStats, query); + if (proto) { + batteryUsageStats.dumpToProto(fd); + } else { + batteryUsageStats.dump(pw, ""); } } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 344673793700..7780b3906b9f 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1180,6 +1180,8 @@ public class AppOpsService extends IAppOpsService.Stub { uidState.pkgOps.put(packageName, new Ops(packageName, uidState)); } + + createSandboxUidStateIfNotExistsForAppLocked(uid); } } } @@ -1261,6 +1263,8 @@ public class AppOpsService extends IAppOpsService.Stub { ops.put(code, new Op(uidState, packageName, code, uid)); } } + + createSandboxUidStateIfNotExistsForAppLocked(uid); } /** @@ -4011,6 +4015,11 @@ public class AppOpsService extends IAppOpsService.Stub { return uidState; } + private void createSandboxUidStateIfNotExistsForAppLocked(int uid) { + final int sandboxUid = Process.toSdkSandboxUid(uid); + getUidStateLocked(sandboxUid, true); + } + private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) { synchronized (this) { getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible); diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index 745222873698..95a047faef07 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -116,8 +116,10 @@ public final class BiometricContextProvider implements BiometricContext { service.setBiometicContextListener(new IBiometricContextListener.Stub() { @Override public void onFoldChanged(int foldState) { - mFoldState = foldState; - // no need to notify, not sent to HAL + if (mFoldState != foldState) { + mFoldState = foldState; + notifyChanged(); + } } @Override @@ -254,6 +256,7 @@ public final class BiometricContextProvider implements BiometricContext { + "isAwake: " + isAwake() + ", " + "isDisplayOn: " + isDisplayOn() + ", " + "dock: " + getDockedState() + ", " - + "rotation: " + getCurrentRotation() + "]"; + + "rotation: " + getCurrentRotation() + ", " + + "foldState: " + mFoldState + "]"; } } diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java index f78ca43253f6..b4e0dff615f5 100644 --- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java +++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java @@ -23,6 +23,7 @@ import android.hardware.biometrics.AuthenticateOptions; import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.common.AuthenticateReason; import android.hardware.biometrics.common.DisplayState; +import android.hardware.biometrics.common.FoldState; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.common.OperationReason; import android.hardware.biometrics.common.WakeReason; @@ -250,6 +251,7 @@ public class OperationContextExt { OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) { mAidlContext.isAod = biometricContext.isAod(); mAidlContext.displayState = toAidlDisplayState(biometricContext.getDisplayState()); + mAidlContext.foldState = toAidlFoldState(biometricContext.getFoldState()); mAidlContext.isCrypto = isCrypto; setFirstSessionId(biometricContext); @@ -276,6 +278,19 @@ public class OperationContextExt { return DisplayState.UNKNOWN; } + @FoldState + private static int toAidlFoldState(@IBiometricContextListener.FoldState int state) { + switch (state) { + case IBiometricContextListener.FoldState.FULLY_CLOSED: + return FoldState.FULLY_CLOSED; + case IBiometricContextListener.FoldState.FULLY_OPENED: + return FoldState.FULLY_OPENED; + case IBiometricContextListener.FoldState.HALF_OPENED: + return FoldState.HALF_OPENED; + } + return FoldState.UNKNOWN; + } + private void setFirstSessionId(@NonNull BiometricContext biometricContext) { if (mIsBP) { mSessionInfo = biometricContext.getBiometricPromptSessionInfo(); diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index b394fb5b3afe..56a94ec06ad4 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -56,6 +56,7 @@ import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; @@ -67,7 +68,6 @@ import android.os.Parcel; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; @@ -116,8 +116,6 @@ import java.util.function.Consumer; public class ClipboardService extends SystemService { private static final String TAG = "ClipboardService"; - private static final boolean IS_EMULATOR = - SystemProperties.getBoolean("ro.boot.qemu", false); @VisibleForTesting public static final long DEFAULT_CLIPBOARD_TIMEOUT_MILLIS = 3600000; @@ -193,7 +191,7 @@ public class ClipboardService extends SystemService { mAutofillInternal = LocalServices.getService(AutofillManagerInternal.class); final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard"); mPermissionOwner = permOwner; - if (IS_EMULATOR) { + if (Build.IS_EMULATOR) { mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> { synchronized (mLock) { Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 4bac872dbaa9..17f2fcc5b9d8 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -155,43 +155,45 @@ class LockSettingsShellCommand extends ShellCommand { try (final PrintWriter pw = getOutPrintWriter();) { pw.println("lockSettings service commands:"); pw.println(""); - pw.println("NOTE: when lock screen is set, all commands require the --old <CREDENTIAL>" - + " argument."); + pw.println("NOTE: when a secure lock screen is set, most commands require the"); + pw.println("--old <CREDENTIAL> option."); pw.println(""); pw.println(" help"); pw.println(" Prints this help text."); pw.println(""); - pw.println(" get-disabled [--old <CREDENTIAL>] [--user USER_ID]"); - pw.println(" Checks whether lock screen is disabled."); + pw.println(" get-disabled [--user USER_ID]"); + pw.println(" Prints true if the lock screen is completely disabled, i.e. set to None."); + pw.println(" Otherwise prints false."); pw.println(""); - pw.println(" set-disabled [--old <CREDENTIAL>] [--user USER_ID] <true|false>"); - pw.println(" When true, disables lock screen."); + pw.println(" set-disabled [--user USER_ID] <true|false>"); + pw.println(" Sets whether the lock screen is disabled. If the lock screen is secure, this"); + pw.println(" has no immediate effect. I.e. this can only change between Swipe and None."); pw.println(""); pw.println(" set-pattern [--old <CREDENTIAL>] [--user USER_ID] <PATTERN>"); - pw.println(" Sets the lock screen as pattern, using the given PATTERN to unlock."); + pw.println(" Sets a secure lock screen that uses the given PATTERN. PATTERN is a series"); + pw.println(" of digits 1-9 that identify the cells of the pattern."); pw.println(""); pw.println(" set-pin [--old <CREDENTIAL>] [--user USER_ID] <PIN>"); - pw.println(" Sets the lock screen as PIN, using the given PIN to unlock."); + pw.println(" Sets a secure lock screen that uses the given PIN."); pw.println(""); pw.println(" set-password [--old <CREDENTIAL>] [--user USER_ID] <PASSWORD>"); - pw.println(" Sets the lock screen as password, using the given PASSWORD to unlock."); + pw.println(" Sets a secure lock screen that uses the given PASSWORD."); pw.println(""); pw.println(" clear [--old <CREDENTIAL>] [--user USER_ID]"); - pw.println(" Clears the lock credentials."); + pw.println(" Clears the lock credential."); pw.println(""); pw.println(" verify [--old <CREDENTIAL>] [--user USER_ID]"); - pw.println(" Verifies the lock credentials."); + pw.println(" Verifies the lock credential."); pw.println(""); pw.println(" remove-cache [--user USER_ID]"); pw.println(" Removes cached unified challenge for the managed profile."); pw.println(""); pw.println(" set-resume-on-reboot-provider-package <package_name>"); - pw.println(" Sets the package name for server based resume on reboot service " - + "provider."); + pw.println(" Sets the package name for server based resume on reboot service provider."); pw.println(""); pw.println(" require-strong-auth [--user USER_ID] <reason>"); - pw.println(" Requires the strong authentication. The current supported reasons: " - + "STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN."); + pw.println(" Requires strong authentication. The current supported reasons:"); + pw.println(" STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN."); pw.println(""); } } diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING index 05a0e85d790f..e64704a84cdf 100644 --- a/services/core/java/com/android/server/power/TEST_MAPPING +++ b/services/core/java/com/android/server/power/TEST_MAPPING @@ -45,6 +45,17 @@ {"include-filter": "com.android.server.power"}, {"exclude-annotation": "org.junit.Ignore"} ] + }, + { + "name": "CtsStatsdAtomHostTestCases", + "options": [ + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"}, + {"include-filter": "android.cts.statsdatom.powermanager"} + ], + "file_patterns": [ + "(/|^)ThermalManagerService.java" + ] } ] } diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java index d17207b8f261..99653ae1cd72 100644 --- a/services/core/java/com/android/server/power/ThermalManagerService.java +++ b/services/core/java/com/android/server/power/ThermalManagerService.java @@ -16,8 +16,17 @@ package com.android.server.power; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; +import static com.android.internal.util.FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE_THRESHOLD; +import static com.android.internal.util.FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__FEATURE_NOT_SUPPORTED; +import static com.android.internal.util.FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__HAL_NOT_READY; +import static com.android.internal.util.FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__SUCCESS; +import static com.android.internal.util.FrameworkStatsLog.THERMAL_STATUS_CALLED__API_STATUS__HAL_NOT_READY; +import static com.android.internal.util.FrameworkStatsLog.THERMAL_STATUS_CALLED__API_STATUS__SUCCESS; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.StatsManager; import android.content.Context; import android.hardware.thermal.IThermal; import android.hardware.thermal.IThermalChangedCallback; @@ -48,11 +57,13 @@ import android.os.Temperature; import android.util.ArrayMap; import android.util.EventLog; import android.util.Slog; +import android.util.StatsEvent; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import com.android.server.FgThread; import com.android.server.SystemService; @@ -122,6 +133,8 @@ public class ThermalManagerService extends SystemService { @VisibleForTesting final TemperatureWatcher mTemperatureWatcher = new TemperatureWatcher(); + private final Context mContext; + public ThermalManagerService(Context context) { this(context, null); } @@ -129,6 +142,7 @@ public class ThermalManagerService extends SystemService { @VisibleForTesting ThermalManagerService(Context context, @Nullable ThermalHalWrapper halWrapper) { super(context); + mContext = context; mHalWrapper = halWrapper; if (halWrapper != null) { halWrapper.setCallback(this::onTemperatureChangedCallback); @@ -146,6 +160,9 @@ public class ThermalManagerService extends SystemService { if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { onActivityManagerReady(); } + if (phase == SystemService.PHASE_BOOT_COMPLETED) { + registerStatsCallbacks(); + } } private void onActivityManagerReady() { @@ -326,6 +343,31 @@ public class ThermalManagerService extends SystemService { } } + private void registerStatsCallbacks() { + final StatsManager statsManager = mContext.getSystemService(StatsManager.class); + if (statsManager != null) { + statsManager.setPullAtomCallback( + FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, + this::onPullAtom); + } + } + + private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) { + if (atomTag == FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS) { + final float[] thresholds; + synchronized (mTemperatureWatcher.mSamples) { + thresholds = Arrays.copyOf(mTemperatureWatcher.mHeadroomThresholds, + mTemperatureWatcher.mHeadroomThresholds.length); + } + data.add( + FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS, + thresholds)); + } + return android.app.StatsManager.PULL_SUCCESS; + } + @VisibleForTesting final IThermalService.Stub mService = new IThermalService.Stub() { @Override @@ -449,6 +491,12 @@ public class ThermalManagerService extends SystemService { synchronized (mLock) { final long token = Binder.clearCallingIdentity(); try { + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_STATUS_CALLED, + Binder.getCallingUid(), + mHalReady.get() + ? THERMAL_STATUS_CALLED__API_STATUS__SUCCESS + : THERMAL_STATUS_CALLED__API_STATUS__HAL_NOT_READY, + thermalSeverityToStatsdStatus(mStatus)); return mStatus; } finally { Binder.restoreCallingIdentity(token); @@ -493,6 +541,9 @@ public class ThermalManagerService extends SystemService { @Override public float getThermalHeadroom(int forecastSeconds) { if (!mHalReady.get()) { + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(), + FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__HAL_NOT_READY, + Float.NaN); return Float.NaN; } @@ -500,6 +551,9 @@ public class ThermalManagerService extends SystemService { if (DEBUG) { Slog.d(TAG, "Invalid forecastSeconds: " + forecastSeconds); } + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, getCallingUid(), + FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__INVALID_ARGUMENT, + Float.NaN); return Float.NaN; } @@ -509,12 +563,21 @@ public class ThermalManagerService extends SystemService { @Override public float[] getThermalHeadroomThresholds() { if (!mHalReady.get()) { + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED, + Binder.getCallingUid(), + THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__HAL_NOT_READY); throw new IllegalStateException("Thermal HAL connection is not initialized"); } if (!Flags.allowThermalHeadroomThresholds()) { + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED, + Binder.getCallingUid(), + THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__FEATURE_NOT_SUPPORTED); throw new UnsupportedOperationException("Thermal headroom thresholds not enabled"); } synchronized (mTemperatureWatcher.mSamples) { + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_THRESHOLDS_CALLED, + Binder.getCallingUid(), + THERMAL_HEADROOM_THRESHOLDS_CALLED__API_STATUS__SUCCESS); return Arrays.copyOf(mTemperatureWatcher.mHeadroomThresholds, mTemperatureWatcher.mHeadroomThresholds.length); } @@ -544,6 +607,27 @@ public class ThermalManagerService extends SystemService { }; + private static int thermalSeverityToStatsdStatus(int severity) { + switch (severity) { + case PowerManager.THERMAL_STATUS_NONE: + return FrameworkStatsLog.THERMAL_STATUS_CALLED__STATUS__NONE; + case PowerManager.THERMAL_STATUS_LIGHT: + return FrameworkStatsLog.THERMAL_STATUS_CALLED__STATUS__LIGHT; + case PowerManager.THERMAL_STATUS_MODERATE: + return FrameworkStatsLog.THERMAL_STATUS_CALLED__STATUS__MODERATE; + case PowerManager.THERMAL_STATUS_SEVERE: + return FrameworkStatsLog.THERMAL_STATUS_CALLED__STATUS__SEVERE; + case PowerManager.THERMAL_STATUS_CRITICAL: + return FrameworkStatsLog.THERMAL_STATUS_CALLED__STATUS__CRITICAL; + case PowerManager.THERMAL_STATUS_EMERGENCY: + return FrameworkStatsLog.THERMAL_STATUS_CALLED__STATUS__EMERGENCY; + case PowerManager.THERMAL_STATUS_SHUTDOWN: + return FrameworkStatsLog.THERMAL_STATUS_CALLED__STATUS__SHUTDOWN; + default: + return FrameworkStatsLog.THERMAL_STATUS_CALLED__STATUS__NONE; + } + } + private static void dumpItemsLocked(PrintWriter pw, String prefix, Collection<?> items) { for (Iterator iterator = items.iterator(); iterator.hasNext();) { @@ -1492,13 +1576,15 @@ public class ThermalManagerService extends SystemService { threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE]; if (!Float.isNaN(severeThreshold)) { mSevereThresholds.put(threshold.name, severeThreshold); - for (int severity = ThrottlingSeverity.LIGHT; - severity <= ThrottlingSeverity.SHUTDOWN; severity++) { - if (Flags.allowThermalHeadroomThresholds() - && threshold.hotThrottlingThresholds.length > severity) { - updateHeadroomThreshold(severity, - threshold.hotThrottlingThresholds[severity], - severeThreshold); + if (Flags.allowThermalHeadroomThresholds()) { + for (int severity = ThrottlingSeverity.LIGHT; + severity <= ThrottlingSeverity.SHUTDOWN; severity++) { + if (severity != ThrottlingSeverity.SEVERE + && threshold.hotThrottlingThresholds.length > severity) { + updateHeadroomThreshold(severity, + threshold.hotThrottlingThresholds[severity], + severeThreshold); + } } } } @@ -1506,11 +1592,15 @@ public class ThermalManagerService extends SystemService { } } - // For a older device with multiple SKIN sensors, we will set a severity's headroom + // For an older device with multiple SKIN sensors, we will set a severity's headroom // threshold based on the minimum value of all as a workaround. void updateHeadroomThreshold(int severity, float threshold, float severeThreshold) { if (!Float.isNaN(threshold)) { synchronized (mSamples) { + if (severity == ThrottlingSeverity.SEVERE) { + mHeadroomThresholds[severity] = 1.0f; + return; + } float headroom = normalizeTemperature(threshold, severeThreshold); if (Float.isNaN(mHeadroomThresholds[severity])) { mHeadroomThresholds[severity] = headroom; @@ -1620,6 +1710,10 @@ public class ThermalManagerService extends SystemService { // to sample, return early if (mSamples.isEmpty()) { Slog.e(TAG, "No temperature samples found"); + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, + Binder.getCallingUid(), + FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE, + Float.NaN); return Float.NaN; } @@ -1627,16 +1721,22 @@ public class ThermalManagerService extends SystemService { // so return early if (mSevereThresholds.isEmpty()) { Slog.e(TAG, "No temperature thresholds found"); + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, + Binder.getCallingUid(), + THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE_THRESHOLD, + Float.NaN); return Float.NaN; } float maxNormalized = Float.NaN; + int noThresholdSampleCount = 0; for (Map.Entry<String, ArrayList<Sample>> entry : mSamples.entrySet()) { String name = entry.getKey(); ArrayList<Sample> samples = entry.getValue(); Float threshold = mSevereThresholds.get(name); if (threshold == null) { + noThresholdSampleCount++; Slog.e(TAG, "No threshold found for " + name); continue; } @@ -1659,7 +1759,17 @@ public class ThermalManagerService extends SystemService { maxNormalized = normalized; } } - + if (noThresholdSampleCount == mSamples.size()) { + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, + Binder.getCallingUid(), + THERMAL_HEADROOM_CALLED__API_STATUS__NO_TEMPERATURE_THRESHOLD, + Float.NaN); + } else { + FrameworkStatsLog.write(FrameworkStatsLog.THERMAL_HEADROOM_CALLED, + Binder.getCallingUid(), + FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__SUCCESS, + maxNormalized); + } return maxNormalized; } } 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 b8d26d9cac42..698f6ea4b443 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -49,6 +49,7 @@ import android.os.BatteryUsageStatsQuery; import android.os.Binder; import android.os.BluetoothBatteryStats; import android.os.Build; +import android.os.ConditionVariable; import android.os.Handler; import android.os.IBatteryPropertiesRegistrar; import android.os.Looper; @@ -137,7 +138,6 @@ import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.net.module.util.NetworkCapabilitiesUtils; -import com.android.server.power.optimization.Flags; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; import libcore.util.EmptyArray; @@ -10543,7 +10543,7 @@ public class BatteryStatsImpl extends BatteryStats { final int batteryConsumerProcessState = mapUidProcessStateToBatteryConsumerProcessState(uidRunningState); - if (mBsi.mSystemReady && Flags.streamlinedBatteryStats()) { + if (mBsi.mSystemReady && mBsi.mPowerStatsCollectorEnabled) { mBsi.mHistory.recordProcessStateChange(elapsedRealtimeMs, uptimeMs, mUid, batteryConsumerProcessState); } @@ -11712,6 +11712,10 @@ public class BatteryStatsImpl extends BatteryStats { // Store the empty state to disk to ensure consistency writeSyncLocked(); + if (mPowerStatsCollectorEnabled) { + schedulePowerStatsSampleCollection(); + } + // Flush external data, gathering snapshots, but don't process it since it is pre-reset data mIgnoreNextExternalStats = true; mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ON_RESET); @@ -15762,6 +15766,16 @@ public class BatteryStatsImpl extends BatteryStats { } /** + * Schedules an immediate collection of PowerStats samples and awaits the result. + */ + public void collectPowerStatsSamples() { + schedulePowerStatsSampleCollection(); + ConditionVariable done = new ConditionVariable(); + mHandler.post(done::open); + done.block(); + } + + /** * Grabs one sample of PowerStats and prints it. */ public void dumpStatsSample(PrintWriter pw) { diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 391dd77e3506..096c5e6697b7 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -126,6 +126,10 @@ public class BatteryUsageStatsProvider { */ public List<BatteryUsageStats> getBatteryUsageStats(BatteryStatsImpl stats, List<BatteryUsageStatsQuery> queries) { + if (mPowerStatsExporterEnabled) { + stats.collectPowerStatsSamples(); + } + ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size()); synchronized (stats) { stats.prepareForDumpLocked(); @@ -168,15 +172,16 @@ public class BatteryUsageStatsProvider { & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0); final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); - final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder( - stats.getCustomEnergyConsumerNames(), includePowerModels, - includeProcessStateData, minConsumedPowerThreshold); - // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration - // of batteryUsageStats sessions to wall-clock adjustments - batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime()); - batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs); - + final BatteryUsageStats.Builder batteryUsageStatsBuilder; synchronized (stats) { + batteryUsageStatsBuilder = new BatteryUsageStats.Builder( + stats.getCustomEnergyConsumerNames(), includePowerModels, + includeProcessStateData, minConsumedPowerThreshold); + + // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration + // of batteryUsageStats sessions to wall-clock adjustments + batteryUsageStatsBuilder.setStatsStartTimestamp(stats.getStartClockTime()); + batteryUsageStatsBuilder.setStatsEndTimestamp(currentTimeMs); SparseArray<? extends BatteryStats.Uid> uidStats = stats.getUidStats(); for (int i = uidStats.size() - 1; i >= 0; i--) { final BatteryStats.Uid uid = uidStats.valueAt(i); diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java index 70c24d58bb2a..1f6f11320f1b 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java @@ -60,6 +60,7 @@ public class PowerStatsExporter { */ public void exportAggregatedPowerStats(BatteryUsageStats.Builder batteryUsageStatsBuilder, long monotonicStartTime, long monotonicEndTime) { + boolean hasStoredSpans = false; long maxEndTime = monotonicStartTime; List<PowerStatsSpan.Metadata> spans = mPowerStatsStore.getTableOfContents(); for (int i = spans.size() - 1; i >= 0; i--) { @@ -99,13 +100,14 @@ public class PowerStatsExporter { } List<PowerStatsSpan.Section> sections = span.getSections(); for (int k = 0; k < sections.size(); k++) { + hasStoredSpans = true; PowerStatsSpan.Section section = sections.get(k); populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, ((AggregatedPowerStatsSection) section).getAggregatedPowerStats()); } } - if (maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) { + if (!hasStoredSpans || maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) { mPowerStatsAggregator.aggregatePowerStats(maxEndTime, monotonicEndTime, stats -> populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats)); } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java index 7123bcb2d095..7bcdc7129d10 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsStore.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java @@ -171,7 +171,7 @@ public class PowerStatsStore { try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { return PowerStatsSpan.read(inputStream, parser, mSectionReader, sectionTypes); } catch (IOException | XmlPullParserException e) { - Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file); + Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file, e); } } finally { unlockStoreDirectory(); diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 2bf7075d081c..d172d3fd4139 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -1598,8 +1598,22 @@ public class TrustManagerService extends SystemService { fout.printf(" User \"%s\" (id=%d, flags=%#x)", user.name, user.id, user.flags); if (!user.supportsSwitchToByUser()) { - fout.println("(managed profile)"); - fout.println(" disabled because switching to this user is not possible."); + final boolean locked; + if (user.isProfile()) { + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)) { + fout.print(" (profile with separate challenge)"); + locked = isDeviceLockedInner(user.id); + } else { + fout.print(" (profile with unified challenge)"); + locked = isDeviceLockedInner(resolveProfileParent(user.id)); + } + } else { + fout.println(" (user that cannot be switched to)"); + locked = isDeviceLockedInner(user.id); + } + fout.println(": deviceLocked=" + dumpBool(locked)); + fout.println( + " Trust agents disabled because switching to this user is not possible."); return; } if (isCurrent) { diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java new file mode 100644 index 000000000000..e82dc37c2b6a --- /dev/null +++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java @@ -0,0 +1,448 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.graphics.Matrix.MSCALE_X; +import static android.graphics.Matrix.MSCALE_Y; +import static android.graphics.Matrix.MSKEW_X; +import static android.graphics.Matrix.MSKEW_Y; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TPL; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IntArray; +import android.util.Pair; +import android.util.Size; +import android.view.InputWindowHandle; +import android.window.ITrustedPresentationListener; +import android.window.TrustedPresentationThresholds; +import android.window.WindowInfosListener; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.server.wm.utils.RegionUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Optional; + +/** + * Class to handle TrustedPresentationListener registrations in a thread safe manner. This class + * also takes care of cleaning up listeners when the remote process dies. + */ +public class TrustedPresentationListenerController { + + // Should only be accessed by the posting to the handler + private class Listeners { + private final class ListenerDeathRecipient implements IBinder.DeathRecipient { + IBinder mListenerBinder; + int mInstances; + + ListenerDeathRecipient(IBinder listenerBinder) { + mListenerBinder = listenerBinder; + mInstances = 0; + try { + mListenerBinder.linkToDeath(this, 0); + } catch (RemoteException ignore) { + } + } + + void addInstance() { + mInstances++; + } + + // return true if there are no instances alive + boolean removeInstance() { + mInstances--; + if (mInstances > 0) { + return false; + } + mListenerBinder.unlinkToDeath(this, 0); + return true; + } + + public void binderDied() { + mHandler.post(() -> { + mUniqueListeners.remove(mListenerBinder); + removeListeners(mListenerBinder, Optional.empty()); + }); + } + } + + // tracks binder deaths for cleanup + ArrayMap<IBinder, ListenerDeathRecipient> mUniqueListeners = new ArrayMap<>(); + ArrayMap<IBinder /*window*/, ArrayList<TrustedPresentationInfo>> mWindowToListeners = + new ArrayMap<>(); + + void register(IBinder window, ITrustedPresentationListener listener, + TrustedPresentationThresholds thresholds, int id) { + var listenersForWindow = mWindowToListeners.computeIfAbsent(window, + iBinder -> new ArrayList<>()); + listenersForWindow.add(new TrustedPresentationInfo(thresholds, id, listener)); + + // register death listener + var listenerBinder = listener.asBinder(); + var deathRecipient = mUniqueListeners.computeIfAbsent(listenerBinder, + ListenerDeathRecipient::new); + deathRecipient.addInstance(); + } + + void unregister(ITrustedPresentationListener trustedPresentationListener, int id) { + var listenerBinder = trustedPresentationListener.asBinder(); + var deathRecipient = mUniqueListeners.get(listenerBinder); + if (deathRecipient == null) { + ProtoLog.e(WM_DEBUG_TPL, "unregister failed, couldn't find" + + " deathRecipient for %s with id=%d", trustedPresentationListener, id); + return; + } + + if (deathRecipient.removeInstance()) { + mUniqueListeners.remove(listenerBinder); + } + removeListeners(listenerBinder, Optional.of(id)); + } + + boolean isEmpty() { + return mWindowToListeners.isEmpty(); + } + + ArrayList<TrustedPresentationInfo> get(IBinder windowToken) { + return mWindowToListeners.get(windowToken); + } + + private void removeListeners(IBinder listenerBinder, Optional<Integer> id) { + for (int i = mWindowToListeners.size() - 1; i >= 0; i--) { + var listeners = mWindowToListeners.valueAt(i); + for (int j = listeners.size() - 1; j >= 0; j--) { + var listener = listeners.get(j); + if (listener.mListener.asBinder() == listenerBinder && (id.isEmpty() + || listener.mId == id.get())) { + listeners.remove(j); + } + } + if (listeners.isEmpty()) { + mWindowToListeners.removeAt(i); + } + } + } + } + + private final Object mHandlerThreadLock = new Object(); + private HandlerThread mHandlerThread; + private Handler mHandler; + + private WindowInfosListener mWindowInfosListener; + + Listeners mRegisteredListeners = new Listeners(); + + private InputWindowHandle[] mLastWindowHandles; + + private final Object mIgnoredWindowTokensLock = new Object(); + + private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>(); + + private void startHandlerThreadIfNeeded() { + synchronized (mHandlerThreadLock) { + if (mHandler == null) { + mHandlerThread = new HandlerThread("WindowInfosListenerForTpl"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + } + } + } + + void addIgnoredWindowTokens(IBinder token) { + synchronized (mIgnoredWindowTokensLock) { + mIgnoredWindowTokens.add(token); + } + } + + void removeIgnoredWindowTokens(IBinder token) { + synchronized (mIgnoredWindowTokensLock) { + mIgnoredWindowTokens.remove(token); + } + } + + void registerListener(IBinder window, ITrustedPresentationListener listener, + TrustedPresentationThresholds thresholds, int id) { + startHandlerThreadIfNeeded(); + mHandler.post(() -> { + ProtoLog.d(WM_DEBUG_TPL, "Registering listener=%s with id=%d for window=%s with %s", + listener, id, window, thresholds); + + mRegisteredListeners.register(window, listener, thresholds, id); + registerWindowInfosListener(); + // Update the initial state for the new registered listener + computeTpl(mLastWindowHandles); + }); + } + + void unregisterListener(ITrustedPresentationListener listener, int id) { + startHandlerThreadIfNeeded(); + mHandler.post(() -> { + ProtoLog.d(WM_DEBUG_TPL, "Unregistering listener=%s with id=%d", + listener, id); + + mRegisteredListeners.unregister(listener, id); + if (mRegisteredListeners.isEmpty()) { + unregisterWindowInfosListener(); + } + }); + } + + void dump(PrintWriter pw) { + final String innerPrefix = " "; + pw.println("TrustedPresentationListenerController:"); + pw.println(innerPrefix + "Active unique listeners (" + + mRegisteredListeners.mUniqueListeners.size() + "):"); + for (int i = 0; i < mRegisteredListeners.mWindowToListeners.size(); i++) { + pw.println( + innerPrefix + " window=" + mRegisteredListeners.mWindowToListeners.keyAt(i)); + final var listeners = mRegisteredListeners.mWindowToListeners.valueAt(i); + for (int j = 0; j < listeners.size(); j++) { + final var listener = listeners.get(j); + pw.println(innerPrefix + innerPrefix + " listener=" + listener.mListener.asBinder() + + " id=" + listener.mId + + " thresholds=" + listener.mThresholds); + } + } + } + + private void registerWindowInfosListener() { + if (mWindowInfosListener != null) { + return; + } + + mWindowInfosListener = new WindowInfosListener() { + @Override + public void onWindowInfosChanged(InputWindowHandle[] windowHandles, + DisplayInfo[] displayInfos) { + mHandler.post(() -> computeTpl(windowHandles)); + } + }; + mLastWindowHandles = mWindowInfosListener.register().first; + } + + private void unregisterWindowInfosListener() { + if (mWindowInfosListener == null) { + return; + } + + mWindowInfosListener.unregister(); + mWindowInfosListener = null; + mLastWindowHandles = null; + } + + private void computeTpl(InputWindowHandle[] windowHandles) { + mLastWindowHandles = windowHandles; + if (mLastWindowHandles == null || mLastWindowHandles.length == 0 + || mRegisteredListeners.isEmpty()) { + return; + } + + Rect tmpRect = new Rect(); + Matrix tmpInverseMatrix = new Matrix(); + float[] tmpMatrix = new float[9]; + Region coveredRegionsAbove = new Region(); + long currTimeMs = System.currentTimeMillis(); + ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length); + + ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates = + new ArrayMap<>(); + ArraySet<IBinder> ignoredWindowTokens; + synchronized (mIgnoredWindowTokensLock) { + ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens); + } + for (var windowHandle : mLastWindowHandles) { + if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) { + ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name); + continue; + } + tmpRect.set(windowHandle.frame); + var listeners = mRegisteredListeners.get(windowHandle.getWindowToken()); + if (listeners != null) { + Region region = new Region(); + region.op(tmpRect, coveredRegionsAbove, Region.Op.DIFFERENCE); + windowHandle.transform.invert(tmpInverseMatrix); + tmpInverseMatrix.getValues(tmpMatrix); + float scaleX = (float) Math.sqrt(tmpMatrix[MSCALE_X] * tmpMatrix[MSCALE_X] + + tmpMatrix[MSKEW_X] * tmpMatrix[MSKEW_X]); + float scaleY = (float) Math.sqrt(tmpMatrix[MSCALE_Y] * tmpMatrix[MSCALE_Y] + + tmpMatrix[MSKEW_Y] * tmpMatrix[MSKEW_Y]); + + float fractionRendered = computeFractionRendered(region, new RectF(tmpRect), + windowHandle.contentSize, + scaleX, scaleY); + + checkIfInThreshold(listeners, listenerUpdates, fractionRendered, windowHandle.alpha, + currTimeMs); + } + + coveredRegionsAbove.op(tmpRect, Region.Op.UNION); + ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s", + windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove); + } + + for (int i = 0; i < listenerUpdates.size(); i++) { + var updates = listenerUpdates.valueAt(i); + var listener = listenerUpdates.keyAt(i); + try { + listener.onTrustedPresentationChanged(updates.first.toArray(), + updates.second.toArray()); + } catch (RemoteException ignore) { + } + } + } + + private void addListenerUpdate( + ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates, + ITrustedPresentationListener listener, int id, boolean presentationState) { + var updates = listenerUpdates.get(listener); + if (updates == null) { + updates = new Pair<>(new IntArray(), new IntArray()); + listenerUpdates.put(listener, updates); + } + if (presentationState) { + updates.first.add(id); + } else { + updates.second.add(id); + } + } + + + private void checkIfInThreshold( + ArrayList<TrustedPresentationInfo> listeners, + ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates, + float fractionRendered, float alpha, long currTimeMs) { + ProtoLog.v(WM_DEBUG_TPL, "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d", + fractionRendered, alpha, currTimeMs); + for (int i = 0; i < listeners.size(); i++) { + var trustedPresentationInfo = listeners.get(i); + var listener = trustedPresentationInfo.mListener; + boolean lastState = trustedPresentationInfo.mLastComputedTrustedPresentationState; + boolean newState = + (alpha >= trustedPresentationInfo.mThresholds.mMinAlpha) && (fractionRendered + >= trustedPresentationInfo.mThresholds.mMinFractionRendered); + trustedPresentationInfo.mLastComputedTrustedPresentationState = newState; + + ProtoLog.v(WM_DEBUG_TPL, + "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f " + + "minFractionRendered=%f", + lastState, newState, alpha, trustedPresentationInfo.mThresholds.mMinAlpha, + fractionRendered, trustedPresentationInfo.mThresholds.mMinFractionRendered); + + if (lastState && !newState) { + // We were in the trusted presentation state, but now we left it, + // emit the callback if needed + if (trustedPresentationInfo.mLastReportedTrustedPresentationState) { + trustedPresentationInfo.mLastReportedTrustedPresentationState = false; + addListenerUpdate(listenerUpdates, listener, + trustedPresentationInfo.mId, /*presentationState*/ false); + ProtoLog.d(WM_DEBUG_TPL, "Adding untrusted state listener=%s with id=%d", + listener, trustedPresentationInfo.mId); + } + // Reset the timer + trustedPresentationInfo.mEnteredTrustedPresentationStateTime = -1; + } else if (!lastState && newState) { + // We were not in the trusted presentation state, but we entered it, begin the timer + // and make sure this gets called at least once more! + trustedPresentationInfo.mEnteredTrustedPresentationStateTime = currTimeMs; + mHandler.postDelayed(() -> { + computeTpl(mLastWindowHandles); + }, (long) (trustedPresentationInfo.mThresholds.mStabilityRequirementMs * 1.5)); + } + + // Has the timer elapsed, but we are still in the state? Emit a callback if needed + if (!trustedPresentationInfo.mLastReportedTrustedPresentationState && newState && ( + currTimeMs - trustedPresentationInfo.mEnteredTrustedPresentationStateTime + > trustedPresentationInfo.mThresholds.mStabilityRequirementMs)) { + trustedPresentationInfo.mLastReportedTrustedPresentationState = true; + addListenerUpdate(listenerUpdates, listener, + trustedPresentationInfo.mId, /*presentationState*/ true); + ProtoLog.d(WM_DEBUG_TPL, "Adding trusted state listener=%s with id=%d", + listener, trustedPresentationInfo.mId); + } + } + } + + private float computeFractionRendered(Region visibleRegion, RectF screenBounds, + Size contentSize, + float sx, float sy) { + ProtoLog.v(WM_DEBUG_TPL, + "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s " + + "scale=%f,%f", + visibleRegion, screenBounds, contentSize, sx, sy); + + if (contentSize.getWidth() == 0 || contentSize.getHeight() == 0) { + return -1; + } + if (screenBounds.width() == 0 || screenBounds.height() == 0) { + return -1; + } + + float fractionRendered = Math.min(sx * sy, 1.0f); + ProtoLog.v(WM_DEBUG_TPL, "fractionRendered scale=%f", fractionRendered); + + float boundsOverSourceW = screenBounds.width() / (float) contentSize.getWidth(); + float boundsOverSourceH = screenBounds.height() / (float) contentSize.getHeight(); + fractionRendered *= boundsOverSourceW * boundsOverSourceH; + ProtoLog.v(WM_DEBUG_TPL, "fractionRendered boundsOverSource=%f", fractionRendered); + // Compute the size of all the rects since they may be disconnected. + float[] visibleSize = new float[1]; + RegionUtils.forEachRect(visibleRegion, rect -> { + float size = rect.width() * rect.height(); + visibleSize[0] += size; + }); + + fractionRendered *= visibleSize[0] / (screenBounds.width() * screenBounds.height()); + return fractionRendered; + } + + private static class TrustedPresentationInfo { + boolean mLastComputedTrustedPresentationState = false; + boolean mLastReportedTrustedPresentationState = false; + long mEnteredTrustedPresentationStateTime = -1; + final TrustedPresentationThresholds mThresholds; + + final ITrustedPresentationListener mListener; + final int mId; + + private TrustedPresentationInfo(TrustedPresentationThresholds thresholds, int id, + ITrustedPresentationListener listener) { + mThresholds = thresholds; + mId = id; + mListener = listener; + checkValid(thresholds); + } + + private void checkValid(TrustedPresentationThresholds thresholds) { + if (thresholds.mMinAlpha <= 0 || thresholds.mMinFractionRendered <= 0 + || thresholds.mStabilityRequirementMs < 1) { + throw new IllegalArgumentException( + "TrustedPresentationThresholds values are invalid"); + } + } + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0d2c94d103fb..0c57036a3b02 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -303,9 +303,11 @@ import android.window.AddToSurfaceSyncGroupResult; import android.window.ClientWindowFrames; import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; +import android.window.ITrustedPresentationListener; import android.window.ScreenCapture; import android.window.SystemPerformanceHinter; import android.window.TaskSnapshot; +import android.window.TrustedPresentationThresholds; import android.window.WindowContainerToken; import android.window.WindowContextInfo; @@ -764,6 +766,9 @@ public class WindowManagerService extends IWindowManager.Stub private final SurfaceSyncGroupController mSurfaceSyncGroupController = new SurfaceSyncGroupController(); + final TrustedPresentationListenerController mTrustedPresentationListenerController = + new TrustedPresentationListenerController(); + @VisibleForTesting final class SettingsObserver extends ContentObserver { private final Uri mDisplayInversionEnabledUri = @@ -7171,6 +7176,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(separator); } mSystemPerformanceHinter.dump(pw, ""); + mTrustedPresentationListenerController.dump(pw); } } @@ -9771,4 +9777,17 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } } + + @Override + public void registerTrustedPresentationListener(IBinder window, + ITrustedPresentationListener listener, + TrustedPresentationThresholds thresholds, int id) { + mTrustedPresentationListenerController.registerListener(window, listener, thresholds, id); + } + + @Override + public void unregisterTrustedPresentationListener(ITrustedPresentationListener listener, + int id) { + mTrustedPresentationListenerController.unregisterListener(listener, id); + } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e1f1f662c5aa..7bc7e2cb780b 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1189,6 +1189,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow); parentWindow.addChild(this, sWindowSubLayerComparator); } + + if (token.mRoundedCornerOverlay) { + mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens( + getWindowToken()); + } } @Override @@ -2393,6 +2398,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } mWmService.postWindowRemoveCleanupLocked(this); + + mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens( + getWindowToken()); } @Override diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0047d06ae09c..59e95e7571d0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1491,8 +1491,6 @@ public final class SystemServer implements Dumpable { boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice", false); - boolean isEmulator = SystemProperties.get("ro.boot.qemu").equals("1"); - boolean isWatch = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WATCH); @@ -2326,7 +2324,7 @@ public final class SystemServer implements Dumpable { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST) || mPackageManager.hasSystemFeature( PackageManager.FEATURE_USB_ACCESSORY) - || isEmulator) { + || Build.IS_EMULATOR) { // Manage USB host and device support t.traceBegin("StartUsbService"); mSystemServiceManager.startService(USB_SERVICE_CLASS); diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java index 646f4862d75d..79b39b82ee56 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -20,7 +20,9 @@ import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.OP_COARSE_LOCATION; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; +import static android.app.AppOpsManager.OP_READ_DEVICE_IDENTIFIERS; import static android.app.AppOpsManager.OP_READ_SMS; +import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS; import static android.app.AppOpsManager.OP_WIFI_SCAN; import static android.app.AppOpsManager.OP_WRITE_SMS; import static android.os.UserHandle.getAppId; @@ -49,8 +51,10 @@ import static org.mockito.ArgumentMatchers.nullable; import android.app.AppOpsManager; import android.app.AppOpsManager.OpEntry; import android.app.AppOpsManager.PackageOps; +import android.app.SyncNotedAppOp; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Handler; import android.os.HandlerThread; @@ -58,6 +62,7 @@ import android.os.Process; import android.permission.PermissionManager; import android.provider.Settings; import android.util.ArrayMap; +import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -79,7 +84,6 @@ import org.junit.runner.RunWith; import org.mockito.quality.Strictness; import java.io.File; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -99,12 +103,15 @@ public class AppOpsServiceTest { private static final Context sContext = InstrumentationRegistry.getTargetContext(); private static final String sMyPackageName = sContext.getOpPackageName(); + private static final String sSdkSandboxPackageName = sContext.getPackageManager() + .getSdkSandboxPackageName(); private File mStorageFile; private File mRecentAccessesFile; private Handler mHandler; private AppOpsService mAppOpsService; private int mMyUid; + private int mSdkSandboxPackageUid; private long mTestStartMillis; private StaticMockitoSession mMockingSession; @@ -132,6 +139,7 @@ public class AppOpsServiceTest { handlerThread.start(); mHandler = new Handler(handlerThread.getLooper()); mMyUid = Process.myUid(); + mSdkSandboxPackageUid = resolveSdkSandboxPackageUid(); initializeStaticMocks(); @@ -152,6 +160,39 @@ public class AppOpsServiceTest { mMockingSession.finishMocking(); } + private static int resolveSdkSandboxPackageUid() { + try { + return sContext.getPackageManager().getPackageUid( + sSdkSandboxPackageName, + PackageManager.PackageInfoFlags.of(0) + ); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Can't resolve sandbox package uid", e); + return Process.INVALID_UID; + } + } + + private static void mockGetPackage( + PackageManagerInternal managerMock, + String packageName + ) { + AndroidPackage packageMock = mock(AndroidPackage.class); + when(managerMock.getPackage(packageName)).thenReturn(packageMock); + } + + private static void mockGetPackageStateInternal( + PackageManagerInternal managerMock, + String packageName, + int uid + ) { + PackageStateInternal packageStateInternalMock = mock(PackageStateInternal.class); + when(packageStateInternalMock.isPrivileged()).thenReturn(false); + when(packageStateInternalMock.getAppId()).thenReturn(uid); + when(packageStateInternalMock.getAndroidPackage()).thenReturn(mock(AndroidPackage.class)); + when(managerMock.getPackageStateInternal(packageName)) + .thenReturn(packageStateInternalMock); + } + private void initializeStaticMocks() { mMockingSession = mockitoSession() .strictness(Strictness.LENIENT) @@ -163,16 +204,11 @@ public class AppOpsServiceTest { // Mock LocalServices.getService(PackageManagerInternal.class).getPackageStateInternal // and getPackage dependency needed by AppOpsService PackageManagerInternal mockPackageManagerInternal = mock(PackageManagerInternal.class); - AndroidPackage mockMyPkg = mock(AndroidPackage.class); - when(mockMyPkg.getAttributions()).thenReturn(Collections.emptyList()); - PackageStateInternal mockMyPSInternal = mock(PackageStateInternal.class); - when(mockMyPSInternal.isPrivileged()).thenReturn(false); - when(mockMyPSInternal.getAppId()).thenReturn(mMyUid); - when(mockMyPSInternal.getAndroidPackage()).thenReturn(mockMyPkg); - - when(mockPackageManagerInternal.getPackageStateInternal(sMyPackageName)) - .thenReturn(mockMyPSInternal); - when(mockPackageManagerInternal.getPackage(sMyPackageName)).thenReturn(mockMyPkg); + mockGetPackage(mockPackageManagerInternal, sMyPackageName); + mockGetPackageStateInternal(mockPackageManagerInternal, sMyPackageName, mMyUid); + mockGetPackage(mockPackageManagerInternal, sSdkSandboxPackageName); + mockGetPackageStateInternal(mockPackageManagerInternal, sSdkSandboxPackageName, + mSdkSandboxPackageUid); when(mockPackageManagerInternal.getPackageUid(eq(sMyPackageName), anyLong(), eq(getUserId(mMyUid)))).thenReturn(mMyUid); doReturn(mockPackageManagerInternal).when( @@ -233,6 +269,21 @@ public class AppOpsServiceTest { assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED); } + @Test + public void testNoteOperationFromSdkSandbox() { + int sandboxUid = Process.toSdkSandboxUid(mMyUid); + + // Note an op that's allowed. + SyncNotedAppOp allowedResult = mAppOpsService.noteOperation(OP_TAKE_AUDIO_FOCUS, sandboxUid, + sSdkSandboxPackageName, null, false, null, false); + assertThat(allowedResult.getOpMode()).isEqualTo(MODE_ALLOWED); + + // Note another op that's not allowed. + SyncNotedAppOp erroredResult = mAppOpsService.noteOperation(OP_READ_DEVICE_IDENTIFIERS, + sandboxUid, sSdkSandboxPackageName, null, false, null, false); + assertThat(erroredResult.getOpMode()).isEqualTo(MODE_ERRORED); + } + /** * Tests the scenario where an operation's permission is controlled by another operation. * For example the results of a WIFI_SCAN can be used to infer the location of a user, so the diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index debc69604690..4fb9472021c5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -70,6 +70,8 @@ import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; +import libcore.junit.util.compat.CoreCompatChangeRule; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -269,19 +271,19 @@ public class FlexibilityControllerTest { @Test public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() { - JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L); + JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS); JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb); - js.enqueueTime = 100L; - assertEquals(150L, + js.enqueueTime = JobSchedulerService.sElapsedRealtimeClock.millis(); + assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2, mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20a,030,40"); - assertEquals(150L, + assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2, mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,40"); - assertEquals(150L, + assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2, mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,40,10,40"); - assertEquals(150L, + assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2, mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); } @@ -369,7 +371,7 @@ public class FlexibilityControllerTest { @Test public void testCurPercent() { - long deadline = 1000; + long deadline = 100 * MINUTE_IN_MILLIS; long nowElapsed; JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline); JobStatus js = createJobStatus("time", jb); @@ -377,17 +379,17 @@ public class FlexibilityControllerTest { assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js)); assertEquals(deadline + FROZEN_TIME, mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME)); - nowElapsed = 600 + FROZEN_TIME; + nowElapsed = FROZEN_TIME + 60 * MINUTE_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); - nowElapsed = 1400; + nowElapsed = FROZEN_TIME + 130 * MINUTE_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); - nowElapsed = 950 + FROZEN_TIME; + nowElapsed = FROZEN_TIME + 95 * MINUTE_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); @@ -395,8 +397,8 @@ public class FlexibilityControllerTest { nowElapsed = FROZEN_TIME; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - long delay = 100; - deadline = 1100; + long delay = MINUTE_IN_MILLIS; + deadline = 101 * MINUTE_IN_MILLIS; jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay); js = createJobStatus("time", jb); @@ -405,18 +407,18 @@ public class FlexibilityControllerTest { assertEquals(deadline + FROZEN_TIME, mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay)); - nowElapsed = 600 + FROZEN_TIME + delay; + nowElapsed = FROZEN_TIME + delay + 60 * MINUTE_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); - nowElapsed = 1400; + nowElapsed = FROZEN_TIME + 130 * MINUTE_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); - nowElapsed = 950 + FROZEN_TIME + delay; + nowElapsed = FROZEN_TIME + delay + 95 * MINUTE_IN_MILLIS; JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed)); @@ -541,20 +543,20 @@ public class FlexibilityControllerTest { @Test public void testGetLifeCycleEndElapsedLocked_NonPrefetch() { // deadline - JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L); + JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS); JobStatus js = createJobStatus("time", jb); - assertEquals(1000L + FROZEN_TIME, + assertEquals(HOUR_IN_MILLIS + FROZEN_TIME, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); // no deadline jb = createJob(0); js = createJobStatus("time", jb); - assertEquals(100L + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS, + assertEquals(FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L)); } @Test public void testGetLifeCycleEndElapsedLocked_Rescheduled() { - JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L); + JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS); JobStatus js = createJobStatus("time", jb); js = new JobStatus( js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 0, @@ -693,6 +695,7 @@ public class FlexibilityControllerTest { } @Test + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) public void testExceptions_ShortWindow() { JobInfo.Builder jb = createJob(0); jb.setMinimumLatency(1); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 92aa982c3dd1..8397b87706d6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -16,6 +16,8 @@ package com.android.server.job.controllers; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; @@ -261,7 +263,7 @@ public class JobStatusTest { public void testMediaBackupExemption_lateConstraint() { final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) .addTriggerContentUri(new JobInfo.TriggerContentUri(IMAGES_MEDIA_URI, 0)) - .setOverrideDeadline(12) + .setOverrideDeadline(HOUR_IN_MILLIS) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); when(mJobSchedulerInternal.getCloudMediaProviderPackage(eq(0))).thenReturn(TEST_PACKAGE); @@ -869,7 +871,7 @@ public class JobStatusTest { public void testWouldBeReadyWithConstraint_RequestedOverrideDeadline() { final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) - .setOverrideDeadline(300_000) + .setOverrideDeadline(HOUR_IN_MILLIS) .build(); final JobStatus job = createJobStatus(jobInfo); @@ -1025,7 +1027,7 @@ public class JobStatusTest { final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setRequiresCharging(true) - .setOverrideDeadline(300_000) + .setOverrideDeadline(HOUR_IN_MILLIS) .addTriggerContentUri(new JobInfo.TriggerContentUri( MediaStore.Images.Media.INTERNAL_CONTENT_URI, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)) diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java index 27efcfabea0c..8fb7bd2a6643 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java @@ -48,6 +48,8 @@ import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobSchedulerService.Constants; +import libcore.junit.util.compat.CoreCompatChangeRule; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -141,6 +143,7 @@ public class TimeControllerTest { } @Test + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) public void testMaybeStartTrackingJobLocked_AlreadySatisfied() { JobStatus delaySatisfied = createJobStatus( "testMaybeStartTrackingJobLocked_AlreadySatisfied", @@ -294,6 +297,7 @@ public class TimeControllerTest { runTestMaybeStartTrackingJobLocked_DeadlineInOrder(); } + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) private void runTestMaybeStartTrackingJobLocked_DeadlineInOrder() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -319,6 +323,7 @@ public class TimeControllerTest { } @Test + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) public void testMaybeStartTrackingJobLocked_DeadlineInOrder_SomeNotReady() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -357,6 +362,7 @@ public class TimeControllerTest { runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder(); } + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) private void runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -387,6 +393,7 @@ public class TimeControllerTest { } @Test + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_SomeNotReady() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -535,6 +542,7 @@ public class TimeControllerTest { runTestCheckExpiredDeadlinesAndResetAlarm(); } + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) private void runTestCheckExpiredDeadlinesAndResetAlarm() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -585,6 +593,7 @@ public class TimeControllerTest { } @Test + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) public void testCheckExpiredDeadlinesAndResetAlarm_SomeNotReady() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -740,6 +749,7 @@ public class TimeControllerTest { } @Test + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) public void testEvaluateStateLocked_Deadline() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 3b39160643d1..91140276cde0 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -588,6 +588,38 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTap_StateIsActivated_shouldInDelegating() { + assumeTrue(mMgh.mIsSinglePanningEnabled); + mMgh.setSinglePanningEnabled(false); + goFromStateIdleTo(STATE_ACTIVATED); + allowEventDelegation(); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); + send(upEvent()); + fastForward(ViewConfiguration.getDoubleTapTimeout()); + + assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTwoFingerTap_StateIsIdle_shouldInDelegating() { + assumeTrue(mMgh.mIsSinglePanningEnabled); + mMgh.setSinglePanningEnabled(false); + goFromStateIdleTo(STATE_IDLE); + allowEventDelegation(); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y)); + send(upEvent()); + fastForward(ViewConfiguration.getDoubleTapTimeout()); + + assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState); + } + + @Test public void testMultiTap_outOfDistanceSlop_shouldInIdle() { // All delay motion events should be sent, if multi-tap with out of distance slop. // STATE_IDLE will check if tapCount() < 2. @@ -719,6 +751,20 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + public void testTwoFingerDown_twoPointerDownAndActivatedState_panningState() { + goFromStateIdleTo(STATE_ACTIVATED); + PointF pointer1 = DEFAULT_POINT; + PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1)); + fastForward(ViewConfiguration.getTapTimeout()); + assertIn(STATE_PANNING); + + returnToNormalFrom(STATE_PANNING); + } + + @Test public void testActivatedWithTripleTap_invokeShowWindowPromptAction() { goFromStateIdleTo(STATE_ACTIVATED); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index 437510595ecb..1b9e6fb6e247 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -179,6 +179,29 @@ public class BiometricContextProviderTest { } @Test + public void testSubscribesToFoldState() throws RemoteException { + final List<Integer> actual = new ArrayList<>(); + final List<Integer> expected = List.of(FoldState.FULLY_CLOSED, FoldState.FULLY_OPENED, + FoldState.UNKNOWN, FoldState.HALF_OPENED); + mProvider.subscribe(mOpContext, ctx -> { + assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext()); + assertThat(mProvider.getFoldState()).isEqualTo(ctx.foldState); + actual.add(ctx.foldState); + }); + + for (int v : expected) { + mListener.onFoldChanged(v); + } + + assertThat(actual).containsExactly( + FoldState.FULLY_CLOSED, + FoldState.FULLY_OPENED, + FoldState.UNKNOWN, + FoldState.HALF_OPENED + ).inOrder(); + } + + @Test public void testSubscribesToDisplayState() throws RemoteException { final List<Integer> actual = new ArrayList<>(); final List<Integer> expected = List.of(AuthenticateOptions.DISPLAY_STATE_AOD, diff --git a/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java index 1726ec18e45b..a33f35aa11d3 100644 --- a/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java +++ b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java @@ -29,6 +29,8 @@ import androidx.test.filters.FlakyTest; import com.android.server.job.MockBiasJobService.TestEnvironment; import com.android.server.job.MockBiasJobService.TestEnvironment.Event; +import libcore.junit.util.compat.CoreCompatChangeRule; + import java.util.ArrayList; @TargetApi(24) @@ -61,6 +63,7 @@ public class BiasSchedulingTest extends AndroidTestCase { } @FlakyTest(bugId = 293589359) + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) public void testLowerBiasJobPreempted() throws Exception { for (int i = 0; i < JobConcurrencyManager.MAX_CONCURRENCY_LIMIT; ++i) { JobInfo job = new JobInfo.Builder(100 + i, sJobServiceComponent) @@ -92,6 +95,7 @@ public class BiasSchedulingTest extends AndroidTestCase { assertTrue("Lower bias jobs were not preempted.", wasJobHigherExecuted); } + @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS}) public void testHigherBiasJobNotPreempted() throws Exception { for (int i = 0; i < JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT; ++i) { JobInfo job = new JobInfo.Builder(100 + i, sJobServiceComponent) diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index 46ead854bded..3069b673f8fc 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -3,6 +3,7 @@ package com.android.server.job; import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static com.android.server.job.JobStore.JOB_FILE_SPLIT_PREFIX; @@ -141,7 +142,7 @@ public class JobStoreTest { final JobInfo task2 = new Builder(12, mComponent) .setMinimumLatency(5000L) .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) - .setOverrideDeadline(30000L) + .setOverrideDeadline(4 * HOUR_IN_MILLIS) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); @@ -194,7 +195,7 @@ public class JobStoreTest { final JobInfo task2 = new Builder(12, mComponent) .setMinimumLatency(5000L) .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) - .setOverrideDeadline(30000L) + .setOverrideDeadline(3 * HOUR_IN_MILLIS) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); @@ -224,7 +225,7 @@ public class JobStoreTest { final JobInfo task2 = new Builder(12, mComponent) .setMinimumLatency(5000L) .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) - .setOverrideDeadline(30000L) + .setOverrideDeadline(5 * HOUR_IN_MILLIS) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); @@ -330,7 +331,6 @@ public class JobStoreTest { @Test public void testMaybeWriteStatusToDisk() throws Exception { int taskId = 5; - long runByMillis = 20000L; // 20s long runFromMillis = 2000L; // 2s long initialBackoff = 10000L; // 10s @@ -338,7 +338,7 @@ public class JobStoreTest { .setRequiresCharging(true) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .setBackoffCriteria(initialBackoff, JobInfo.BACKOFF_POLICY_EXPONENTIAL) - .setOverrideDeadline(runByMillis) + .setOverrideDeadline(6 * HOUR_IN_MILLIS) .setMinimumLatency(runFromMillis) .setPersisted(true) .build(); @@ -379,7 +379,7 @@ public class JobStoreTest { final JobInfo task2 = new Builder(12, mComponent) .setMinimumLatency(5000L) .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) - .setOverrideDeadline(30000L) + .setOverrideDeadline(7 * HOUR_IN_MILLIS) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); @@ -542,7 +542,7 @@ public class JobStoreTest { @Test public void testBiasPersisted() throws Exception { JobInfo.Builder b = new Builder(92, mComponent) - .setOverrideDeadline(5000) + .setOverrideDeadline(8 * HOUR_IN_MILLIS) .setBias(42) .setPersisted(true); final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); @@ -613,7 +613,7 @@ public class JobStoreTest { @Test public void testPriorityPersisted() throws Exception { final JobInfo job = new Builder(92, mComponent) - .setOverrideDeadline(5000) + .setOverrideDeadline(9 * HOUR_IN_MILLIS) .setPriority(JobInfo.PRIORITY_MIN) .setPersisted(true) .build(); @@ -634,13 +634,13 @@ public class JobStoreTest { @Test public void testNonPersistedTaskIsNotPersisted() throws Exception { JobInfo.Builder b = new Builder(42, mComponent) - .setOverrideDeadline(10000) + .setOverrideDeadline(10 * HOUR_IN_MILLIS) .setPersisted(false); JobStatus jsNonPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); mTaskStoreUnderTest.add(jsNonPersisted); b = new Builder(43, mComponent) - .setOverrideDeadline(10000) + .setOverrideDeadline(11 * HOUR_IN_MILLIS) .setPersisted(true); JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java index 44dad593810a..32bbc7a618d1 100644 --- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java @@ -482,6 +482,35 @@ public class ThermalManagerServiceTest { } @Test + public void testGetThermalHeadroomThresholdsOnDefaultHalResult() throws Exception { + TemperatureWatcher watcher = mService.mTemperatureWatcher; + ArrayList<TemperatureThreshold> thresholds = new ArrayList<>(); + mFakeHal.mTemperatureThresholdList = thresholds; + watcher.updateThresholds(); + synchronized (watcher.mSamples) { + assertArrayEquals( + new float[]{Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, + Float.NaN}, + watcher.mHeadroomThresholds, 0.01f); + } + TemperatureThreshold nanThresholds = new TemperatureThreshold(); + nanThresholds.name = "nan"; + nanThresholds.type = Temperature.TYPE_SKIN; + nanThresholds.hotThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1]; + nanThresholds.coldThrottlingThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1]; + Arrays.fill(nanThresholds.hotThrottlingThresholds, Float.NaN); + Arrays.fill(nanThresholds.coldThrottlingThresholds, Float.NaN); + thresholds.add(nanThresholds); + watcher.updateThresholds(); + synchronized (watcher.mSamples) { + assertArrayEquals( + new float[]{Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, + Float.NaN}, + watcher.mHeadroomThresholds, 0.01f); + } + } + + @Test public void testTemperatureWatcherGetSlopeOf() throws RemoteException { TemperatureWatcher watcher = mService.mTemperatureWatcher; List<TemperatureWatcher.Sample> samples = new ArrayList<>(); diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index c3074bb0fee8..a8d3fa110844 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -99,7 +99,7 @@ android:theme="@style/WhiteBackgroundTheme" android:exported="true"/> - <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity" + <activity android:name="com.android.server.wm.TrustedPresentationListenerTest$TestActivity" android:exported="true" android:showWhenLocked="true" android:turnScreenOn="true" /> diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java index 3b9ed2652610..ef427bb15039 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java @@ -137,7 +137,7 @@ public class SurfaceControlViewHostTests { wasVisible = waitForWindowVisible(mView2); if (!wasVisible) { - dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows"); + dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-not visible"); } assertTrue("Failed to wait for view2", wasVisible); @@ -145,11 +145,21 @@ public class SurfaceControlViewHostTests { WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window, mScvh1.getInputTransferToken(), true); - assertTrue("Failed to gain focus for view1", waitForWindowFocus(mView1, true)); + + boolean gainedFocus = waitForWindowFocus(mView1, true); + if (!gainedFocus) { + dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view1 not focus"); + } + assertTrue("Failed to gain focus for view1", gainedFocus); WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window, mScvh2.getInputTransferToken(), true); - assertTrue("Failed to gain focus for view2", waitForWindowFocus(mView2, true)); + + gainedFocus = waitForWindowFocus(mView2, true); + if (!gainedFocus) { + dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view2 not focus"); + } + assertTrue("Failed to gain focus for view2", gainedFocus); } private static class TestWindowlessWindowManager extends WindowlessWindowManager { diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java deleted file mode 100644 index c5dd447b5b0c..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java +++ /dev/null @@ -1,154 +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.server.wm; - -import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule; -import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.app.Activity; -import android.platform.test.annotations.Presubmit; -import android.server.wm.CtsWindowInfoUtils; -import android.view.SurfaceControl; -import android.view.SurfaceControl.TrustedPresentationThresholds; - -import androidx.annotation.GuardedBy; -import androidx.test.ext.junit.rules.ActivityScenarioRule; - -import com.android.server.wm.utils.CommonUtils; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; - -import java.util.function.Consumer; - -/** - * TODO (b/287076178): Move these tests to - * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public - */ -@Presubmit -public class TrustedPresentationCallbackTest { - private static final String TAG = "TrustedPresentationCallbackTest"; - private static final int STABILITY_REQUIREMENT_MS = 500; - private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L; - - private static final float FRACTION_VISIBLE = 0.1f; - - private final Object mResultsLock = new Object(); - @GuardedBy("mResultsLock") - private boolean mResult; - @GuardedBy("mResultsLock") - private boolean mReceivedResults; - - @Rule - public TestName mName = new TestName(); - - @Rule - public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule( - TestActivity.class); - - private TestActivity mActivity; - - @Before - public void setup() { - mActivityRule.getScenario().onActivity(activity -> mActivity = activity); - } - - @After - public void tearDown() { - CommonUtils.waitUntilActivityRemoved(mActivity); - } - - @Test - public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException { - TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds( - 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds, - Runnable::run, inTrustedPresentationState -> { - synchronized (mResultsLock) { - mResult = inTrustedPresentationState; - mReceivedResults = true; - mResultsLock.notify(); - } - }); - t.apply(); - synchronized (mResultsLock) { - assertResults(); - } - } - - @Test - public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException { - TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds( - 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); - Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> { - synchronized (mResultsLock) { - mResult = inTrustedPresentationState; - mReceivedResults = true; - mResultsLock.notify(); - } - }; - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds, - Runnable::run, trustedPresentationCallback); - t.apply(); - - synchronized (mResultsLock) { - if (!mReceivedResults) { - mResultsLock.wait(WAIT_TIME_MS); - } - assertResults(); - // reset the state - mReceivedResults = false; - } - - mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t, - trustedPresentationCallback); - t.apply(); - - synchronized (mResultsLock) { - if (!mReceivedResults) { - mResultsLock.wait(WAIT_TIME_MS); - } - // Ensure we waited the full time and never received a notify on the result from the - // callback. - assertFalse("Should never have received a callback", mReceivedResults); - // results shouldn't have changed. - assertTrue(mResult); - } - } - - @GuardedBy("mResultsLock") - private void assertResults() throws InterruptedException { - mResultsLock.wait(WAIT_TIME_MS); - - if (!mReceivedResults) { - CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName()); - } - // Make sure we received the results and not just timed out - assertTrue("Timed out waiting for results", mReceivedResults); - assertTrue(mResult); - } - - public static class TestActivity extends Activity { - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationListenerTest.java new file mode 100644 index 000000000000..96b66bfd3bc0 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationListenerTest.java @@ -0,0 +1,267 @@ +/* + * 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.server.wm; + +import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule; +import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +import android.app.Activity; +import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; +import android.server.wm.CtsWindowInfoUtils; +import android.util.AndroidRuntimeException; +import android.util.Log; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.WindowManager; +import android.window.TrustedPresentationThresholds; + +import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +import com.android.server.wm.utils.CommonUtils; + +import junit.framework.Assert; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * TODO (b/287076178): Move these tests to + * {@link android.view.surfacecontrol.cts.TrustedPresentationListenerTest} when API is made public + */ +@Presubmit +public class TrustedPresentationListenerTest { + private static final String TAG = "TrustedPresentationListenerTest"; + private static final int STABILITY_REQUIREMENT_MS = 500; + private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L; + + private static final float FRACTION_VISIBLE = 0.1f; + + private final List<Boolean> mResults = Collections.synchronizedList(new ArrayList<>()); + private CountDownLatch mReceivedResults = new CountDownLatch(1); + + private TrustedPresentationThresholds mThresholds = new TrustedPresentationThresholds( + 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); + + @Rule + public TestName mName = new TestName(); + + @Rule + public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule( + TestActivity.class); + + private TestActivity mActivity; + + private SurfaceControlViewHost.SurfacePackage mSurfacePackage = null; + + @Before + public void setup() { + mActivityRule.getScenario().onActivity(activity -> mActivity = activity); + mDefaultListener = new Listener(mReceivedResults); + } + + @After + public void tearDown() { + if (mSurfacePackage != null) { + new SurfaceControl.Transaction().remove(mSurfacePackage.getSurfaceControl()).apply( + true); + mSurfacePackage.release(); + } + CommonUtils.waitUntilActivityRemoved(mActivity); + + } + + private class Listener implements Consumer<Boolean> { + final CountDownLatch mLatch; + + Listener(CountDownLatch latch) { + mLatch = latch; + } + + @Override + public void accept(Boolean inTrustedPresentationState) { + Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState); + mResults.add(inTrustedPresentationState); + mLatch.countDown(); + } + } + + private Consumer<Boolean> mDefaultListener; + + @Test + public void testAddTrustedPresentationListenerOnWindow() { + WindowManager windowManager = mActivity.getSystemService(WindowManager.class); + windowManager.registerTrustedPresentationListener( + mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run, + mDefaultListener); + assertResults(List.of(true)); + } + + @Test + public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException { + WindowManager windowManager = mActivity.getSystemService(WindowManager.class); + windowManager.registerTrustedPresentationListener( + mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run, + mDefaultListener); + assertResults(List.of(true)); + // reset the latch + mReceivedResults = new CountDownLatch(1); + + windowManager.unregisterTrustedPresentationListener(mDefaultListener); + mReceivedResults.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS); + // Ensure we waited the full time and never received a notify on the result from the + // callback. + assertEquals("Should never have received a callback", mReceivedResults.getCount(), 1); + // results shouldn't have changed. + assertEquals(mResults, List.of(true)); + } + + @Test + public void testRemovingUnknownListenerIsANoop() { + WindowManager windowManager = mActivity.getSystemService(WindowManager.class); + assertNotNull(windowManager); + windowManager.unregisterTrustedPresentationListener(mDefaultListener); + } + + @Test + public void testAddDuplicateListenerThrowsException() { + WindowManager windowManager = mActivity.getSystemService(WindowManager.class); + assertNotNull(windowManager); + windowManager.registerTrustedPresentationListener( + mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, + Runnable::run, mDefaultListener); + assertThrows(AndroidRuntimeException.class, + () -> windowManager.registerTrustedPresentationListener( + mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, + Runnable::run, mDefaultListener)); + } + + @Test + public void testAddDuplicateThresholds() { + mReceivedResults = new CountDownLatch(2); + mDefaultListener = new Listener(mReceivedResults); + WindowManager windowManager = mActivity.getSystemService(WindowManager.class); + windowManager.registerTrustedPresentationListener( + mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, + Runnable::run, mDefaultListener); + + Consumer<Boolean> mNewListener = new Listener(mReceivedResults); + + windowManager.registerTrustedPresentationListener( + mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, + Runnable::run, mNewListener); + assertResults(List.of(true, true)); + } + + private void waitForViewAttach(View view) { + final CountDownLatch viewAttached = new CountDownLatch(1); + view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(@NonNull View v) { + viewAttached.countDown(); + } + + @Override + public void onViewDetachedFromWindow(@NonNull View v) { + + } + }); + try { + viewAttached.await(2000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (!wait(viewAttached, 2000 /* waitTimeMs */)) { + fail("Couldn't attach view=" + view); + } + } + + @Test + public void testAddListenerToScvh() { + WindowManager windowManager = mActivity.getSystemService(WindowManager.class); + + var embeddedView = new View(mActivity); + mActivityRule.getScenario().onActivity(activity -> { + var attachedSurfaceControl = + mActivity.getWindow().getDecorView().getRootSurfaceControl(); + var scvh = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), + attachedSurfaceControl.getHostToken()); + mSurfacePackage = scvh.getSurfacePackage(); + scvh.setView(embeddedView, mActivity.getWindow().getDecorView().getWidth(), + mActivity.getWindow().getDecorView().getHeight()); + attachedSurfaceControl.buildReparentTransaction( + mSurfacePackage.getSurfaceControl()); + }); + + waitForViewAttach(embeddedView); + windowManager.registerTrustedPresentationListener(embeddedView.getWindowToken(), + mThresholds, + Runnable::run, mDefaultListener); + + assertResults(List.of(true)); + } + + private boolean wait(CountDownLatch latch, long waitTimeMs) { + while (true) { + long now = SystemClock.uptimeMillis(); + try { + return latch.await(waitTimeMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + long elapsedTime = SystemClock.uptimeMillis() - now; + waitTimeMs = Math.max(0, waitTimeMs - elapsedTime); + } + } + + } + + @GuardedBy("mResultsLock") + private void assertResults(List<Boolean> results) { + if (!wait(mReceivedResults, WAIT_TIME_MS)) { + try { + CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName()); + } catch (InterruptedException e) { + Log.d(TAG, "Couldn't dump windows", e); + } + Assert.fail("Timed out waiting for results mReceivedResults.count=" + + mReceivedResults.getCount() + "mReceivedResults=" + mReceivedResults); + } + + // Make sure we received the results + assertEquals(results.toArray(), mResults.toArray()); + } + + public static class TestActivity extends Activity { + } +} diff --git a/tests/NetworkSecurityConfigTest/OWNERS b/tests/NetworkSecurityConfigTest/OWNERS index aa87958f1d53..90e1bed9fb26 100644 --- a/tests/NetworkSecurityConfigTest/OWNERS +++ b/tests/NetworkSecurityConfigTest/OWNERS @@ -1 +1,2 @@ include /services/core/java/com/android/server/net/OWNERS +include /core/java/android/security/net/OWNERS diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING index 192b6f2b5e25..eca258c5a74d 100644 --- a/tools/hoststubgen/TEST_MAPPING +++ b/tools/hoststubgen/TEST_MAPPING @@ -1,11 +1,13 @@ { - "presubmit": [ - { "name": "tiny-framework-dump-test" }, - { "name": "hoststubgentest" }, - { "name": "hoststubgen-invoke-test" } - - // As a smoke test. - // TODO: Enable it once the infra knows how to run it. - // { "name": "CtsUtilTestCasesRavenwood" } - ] + "presubmit": [ + { "name": "tiny-framework-dump-test" }, + { "name": "hoststubgentest" }, + { "name": "hoststubgen-invoke-test" } + ], + "ravenwood-presubmit": [ + { + "name": "CtsUtilTestCasesRavenwood", + "host": true + } + ] } |