diff options
972 files changed, 16361 insertions, 6345 deletions
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp index e7adf203334e..f6213b9cf983 100644 --- a/ProtoLibraries.bp +++ b/ProtoLibraries.bp @@ -31,6 +31,7 @@ gensrcs { "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)", srcs: [ + ":aconfigd_protos", ":ipconnectivity-proto-src", ":libstats_atom_enum_protos", ":libstats_atom_message_protos", diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index c74c48ce6ba8..5f57c3973ab0 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -31,6 +31,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.app.Notification; import android.compat.Compatibility; import android.compat.annotation.ChangeId; @@ -1366,6 +1367,7 @@ public class JobInfo implements Parcelable { * @return This object for method chaining */ @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS) + @SuppressLint("BuilderSetStyle") @NonNull public Builder removeDebugTag(@NonNull String tag) { mDebugTags.remove(tag); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 384d78618c8b..ff73a4922977 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -80,6 +80,7 @@ import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; import android.os.storage.StorageManagerInternal; @@ -179,6 +180,8 @@ public class JobSchedulerService extends com.android.server.SystemService public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final boolean DEBUG_STANDBY = DEBUG || false; + public static final String TRACE_TRACK_NAME = "JobScheduler"; + /** The maximum number of jobs that we allow an app to schedule */ private static final int MAX_JOBS_PER_APP = 150; /** The number of the most recently completed jobs to keep track of for debugging purposes. */ @@ -4344,7 +4347,11 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean wasConsideredCharging = isConsideredCharging(); mChargingPolicy = newPolicy; - + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + "CHARGING POLICY CHANGED#" + mChargingPolicy); + } if (isConsideredCharging() != wasConsideredCharging) { for (int c = mControllers.size() - 1; c >= 0; --c) { mControllers.get(c).onBatteryStateChangedLocked(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 39d50f53d2c4..d65a66c83fcc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -535,29 +535,17 @@ public final class JobServiceContext implements ServiceConnection { sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount()); final String sourcePackage = job.getSourcePackageName(); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { - final String componentPackage = job.getServiceComponent().getPackageName(); - String traceTag = "*job*<" + job.getSourceUid() + ">" + sourcePackage; - if (!sourcePackage.equals(componentPackage)) { - traceTag += ":" + componentPackage; - } - traceTag += "/" + job.getServiceComponent().getShortClassName(); - if (!componentPackage.equals(job.serviceProcessName)) { - traceTag += "$" + job.serviceProcessName; - } - if (job.getNamespace() != null) { - traceTag += "@" + job.getNamespace(); - } - traceTag += "#" + job.getJobId(); - // Use the context's ID to distinguish traces since there'll only be one job // running per context. - Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", - traceTag, getId()); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, job.computeSystemTraceTag(), + getId()); } if (job.getAppTraceTag() != null) { // Use the job's ID to distinguish traces since the ID will be unique per app. - Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "JobScheduler", - job.getAppTraceTag(), job.getJobId()); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, + JobSchedulerService.TRACE_TRACK_NAME, job.getAppTraceTag(), + job.getJobId()); } try { mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid()); @@ -1605,12 +1593,12 @@ public final class JobServiceContext implements ServiceConnection { completedJob.getFilteredTraceTag(), completedJob.getFilteredDebugTags()); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", - getId()); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, getId()); } if (completedJob.getAppTraceTag() != null) { - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "JobScheduler", - completedJob.getJobId()); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, + JobSchedulerService.TRACE_TRACK_NAME, completedJob.getJobId()); } try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 7fca867356af..e3af1d894762 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -572,6 +572,9 @@ public final class JobStatus { /** The reason a job most recently went from ready to not ready. */ private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED; + /** The system trace tag for this job. */ + private String mSystemTraceTag; + /** * Core constructor for JobStatus instances. All other ctors funnel down to this one. * @@ -1058,6 +1061,38 @@ public final class JobStatus { return job.getTraceTag(); } + /** Returns a trace tag using debug information provided by job scheduler service. */ + @NonNull + public String computeSystemTraceTag() { + // Guarded by JobSchedulerService.mLock, no need for synchronization. + if (mSystemTraceTag != null) { + return mSystemTraceTag; + } + + mSystemTraceTag = computeSystemTraceTagInner(); + return mSystemTraceTag; + } + + @NonNull + private String computeSystemTraceTagInner() { + final String componentPackage = getServiceComponent().getPackageName(); + StringBuilder traceTag = new StringBuilder(128); + traceTag.append("*job*<").append(sourceUid).append(">").append(sourcePackageName); + if (!sourcePackageName.equals(componentPackage)) { + traceTag.append(":").append(componentPackage); + } + traceTag.append("/").append(getServiceComponent().getShortClassName()); + if (!componentPackage.equals(serviceProcessName)) { + traceTag.append("$").append(serviceProcessName); + } + if (mNamespace != null && !mNamespace.trim().isEmpty()) { + traceTag.append("@").append(mNamespace); + } + traceTag.append("#").append(getJobId()); + + return traceTag.toString(); + } + /** Returns whether this job was scheduled by one app on behalf of another. */ public boolean isProxyJob() { return mIsProxyJob; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index c240b3f423a9..a1c72fb4c06c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -50,6 +50,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.Trace; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArraySet; @@ -2181,6 +2182,12 @@ public final class QuotaController extends StateController { } scheduleCutoff(); } + } else { + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + "QC/- " + mPkg); + } } } @@ -2720,6 +2727,11 @@ public final class QuotaController extends StateController { if (timeRemainingMs <= 50) { // Less than 50 milliseconds left. Start process of shutting down jobs. if (DEBUG) Slog.d(TAG, pkg + " has reached its quota."); + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + pkg + "#" + MSG_REACHED_TIME_QUOTA); + } mStateChangedListener.onControllerStateChanged( maybeUpdateConstraintForPkgLocked( sElapsedRealtimeClock.millis(), @@ -2748,6 +2760,11 @@ public final class QuotaController extends StateController { pkg.userId, pkg.packageName); if (timeRemainingMs <= 0) { if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota."); + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + pkg + "#" + MSG_REACHED_EJ_TIME_QUOTA); + } mStateChangedListener.onControllerStateChanged( maybeUpdateConstraintForPkgLocked( sElapsedRealtimeClock.millis(), @@ -2772,6 +2789,12 @@ public final class QuotaController extends StateController { Slog.d(TAG, pkg + " has reached its count quota."); } + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + pkg + "#" + MSG_REACHED_COUNT_QUOTA); + } + mStateChangedListener.onControllerStateChanged( maybeUpdateConstraintForPkgLocked( sElapsedRealtimeClock.millis(), @@ -2928,6 +2951,11 @@ public final class QuotaController extends StateController { } mTempAllowlistGraceCache.delete(uid); mTopAppGraceCache.delete(uid); + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + "<" + uid + ">#" + MSG_END_GRACE_PERIOD); + } final ArraySet<String> packages = mService.getPackagesForUidLocked(uid); if (packages != null) { final int userId = UserHandle.getUserId(uid); diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 9b81bd441015..13d6ae5be3e8 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -50,7 +50,7 @@ non_updatable_exportable_droidstubs { }, api_lint: { enabled: true, - new_since: ":android.api.public.latest", + new_since: ":android.api.combined.public.latest", baseline_file: ":non-updatable-lint-baseline.txt", }, }, @@ -130,7 +130,7 @@ non_updatable_exportable_droidstubs { }, api_lint: { enabled: true, - new_since: ":android.api.system.latest", + new_since: ":android.api.combined.system.latest", baseline_file: ":non-updatable-system-lint-baseline.txt", }, }, @@ -185,7 +185,7 @@ non_updatable_exportable_droidstubs { }, api_lint: { enabled: true, - new_since: ":android.api.test.latest", + new_since: ":android.api.combined.test.latest", baseline_file: ":non-updatable-test-lint-baseline.txt", }, }, @@ -269,7 +269,7 @@ non_updatable_exportable_droidstubs { }, api_lint: { enabled: true, - new_since: ":android.api.module-lib.latest", + new_since: ":android.api.combined.module-lib.latest", baseline_file: ":non-updatable-module-lib-lint-baseline.txt", }, }, diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java index 488292d68620..f726361effd6 100644 --- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java +++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java @@ -292,13 +292,17 @@ public class AccessibilityNodeInfoDumper { int childCount = node.getChildCount(); for (int x = 0; x < childCount; x++) { AccessibilityNodeInfo childNode = node.getChild(x); - + if (childNode == null) { + continue; + } if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty() - || !safeCharSeqToString(childNode.getText()).isEmpty()) + || !safeCharSeqToString(childNode.getText()).isEmpty()) { return true; + } - if (childNafCheck(childNode)) + if (childNafCheck(childNode)) { return true; + } } return false; } diff --git a/core/api/current.txt b/core/api/current.txt index 53cf7d59f974..13958d24096b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -26592,7 +26592,7 @@ package android.media.session { method public long getFlags(); method @Nullable public android.media.MediaMetadata getMetadata(); method public String getPackageName(); - method @Nullable public android.media.session.MediaController.PlaybackInfo getPlaybackInfo(); + method @NonNull public android.media.session.MediaController.PlaybackInfo getPlaybackInfo(); method @Nullable public android.media.session.PlaybackState getPlaybackState(); method @Nullable public java.util.List<android.media.session.MediaSession.QueueItem> getQueue(); method @Nullable public CharSequence getQueueTitle(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 624227dc26f3..14ae3f543436 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -4260,6 +4260,12 @@ package android.window { field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR; } + public final class TaskFragmentParentInfo implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentParentInfo> CREATOR; + } + public final class TaskFragmentTransaction implements android.os.Parcelable { ctor public TaskFragmentTransaction(); method public void addChange(@Nullable android.window.TaskFragmentTransaction.Change); @@ -4284,8 +4290,8 @@ package android.window { method @Nullable public android.os.IBinder getActivityToken(); method @NonNull public android.os.Bundle getErrorBundle(); method @Nullable public android.os.IBinder getErrorCallbackToken(); - method @Nullable public android.content.res.Configuration getTaskConfiguration(); method @Nullable public android.window.TaskFragmentInfo getTaskFragmentInfo(); + method @Nullable public android.window.TaskFragmentParentInfo getTaskFragmentParentInfo(); method @Nullable public android.os.IBinder getTaskFragmentToken(); method public int getTaskId(); method public int getType(); @@ -4293,7 +4299,6 @@ package android.window { method @NonNull public android.window.TaskFragmentTransaction.Change setActivityToken(@NonNull android.os.IBinder); method @NonNull public android.window.TaskFragmentTransaction.Change setErrorBundle(@NonNull android.os.Bundle); method @NonNull public android.window.TaskFragmentTransaction.Change setErrorCallbackToken(@Nullable android.os.IBinder); - method @NonNull public android.window.TaskFragmentTransaction.Change setTaskConfiguration(@NonNull android.content.res.Configuration); method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentInfo(@NonNull android.window.TaskFragmentInfo); method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentToken(@NonNull android.os.IBinder); method @NonNull public android.window.TaskFragmentTransaction.Change setTaskId(int); diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 7ee3413550af..497d47adc7cc 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -2015,7 +2015,7 @@ public class AccountManager { * null for no callback * @param handler {@link Handler} identifying the callback thread, * null for the main thread - * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it + * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated whether it * succeeded. * @hide */ diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index a5dd4a7207c3..e1cb630f4d82 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5796,6 +5796,8 @@ public class Activity extends ContextThemeWrapper * @see #onRequestPermissionsResult */ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) + @SuppressLint("OnNameExpected") + // Suppress lint as this is an overload of the original API. public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) { final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager() : createDeviceContext(deviceId).getPackageManager(); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index d8df447982a0..8e99e46be6ac 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -1282,4 +1282,15 @@ public abstract class ActivityManagerInternal { */ public abstract void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid, int userId); + + /** + * It is similar {@link IActivityManager#killApplication(String, int, int, String, int)} but + * it immediately stop the package. + * + * <p>Note: Do not call this method from inside PMS's lock, otherwise it'll run into + * watchdog reset. + * @hide + */ + public abstract void killApplicationSync(String pkgName, int appId, int userId, + String reason, int exitInfoReason); } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 9ea55f5ff84c..c6a1546fb931 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -103,7 +103,8 @@ public class ActivityOptions extends ComponentOptions { @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = { MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED, MODE_BACKGROUND_ACTIVITY_START_ALLOWED, - MODE_BACKGROUND_ACTIVITY_START_DENIED}) + MODE_BACKGROUND_ACTIVITY_START_DENIED, + MODE_BACKGROUND_ACTIVITY_START_COMPAT}) public @interface BackgroundActivityStartMode {} /** * No explicit value chosen. The system will decide whether to grant privileges. @@ -117,6 +118,13 @@ public class ActivityOptions extends ComponentOptions { * Deny the {@link PendingIntent} to use the background activity start privileges. */ public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; + /** + * Special behavior for compatibility. + * Similar to {@link #MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED} + * + * @hide + */ + public static final int MODE_BACKGROUND_ACTIVITY_START_COMPAT = -1; /** * The package name that created the options. diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java index 397477d72a9a..0e8e2e30c26f 100644 --- a/core/java/android/app/ComponentOptions.java +++ b/core/java/android/app/ComponentOptions.java @@ -18,6 +18,7 @@ package android.app; import static android.app.ActivityOptions.BackgroundActivityStartMode; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; @@ -54,7 +55,7 @@ public class ComponentOptions { public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION = "android.pendingIntent.backgroundActivityAllowedByPermission"; - private @Nullable Boolean mPendingIntentBalAllowed = null; + private Integer mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; private boolean mPendingIntentBalAllowedByPermission = false; ComponentOptions() { @@ -65,12 +66,9 @@ public class ComponentOptions { // results they want, which is their loss. opts.setDefusable(true); - boolean pendingIntentBalAllowedIsSetExplicitly = - opts.containsKey(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED); - if (pendingIntentBalAllowedIsSetExplicitly) { - mPendingIntentBalAllowed = - opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED); - } + mPendingIntentBalAllowed = + opts.getInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); setPendingIntentBackgroundActivityLaunchAllowedByPermission( opts.getBoolean( KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false)); @@ -85,7 +83,8 @@ public class ComponentOptions { * @hide */ @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) { - mPendingIntentBalAllowed = allowed; + mPendingIntentBalAllowed = allowed ? MODE_BACKGROUND_ACTIVITY_START_ALLOWED + : MODE_BACKGROUND_ACTIVITY_START_DENIED; } /** @@ -98,11 +97,8 @@ public class ComponentOptions { * @hide */ @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() { - if (mPendingIntentBalAllowed == null) { - // cannot return null, so return the value used up to API level 33 for compatibility - return true; - } - return mPendingIntentBalAllowed; + // cannot return all detail, so return the value used up to API level 33 for compatibility + return mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_DENIED; } /** @@ -119,16 +115,15 @@ public class ComponentOptions { @BackgroundActivityStartMode int state) { switch (state) { case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: - mPendingIntentBalAllowed = null; - break; - case MODE_BACKGROUND_ACTIVITY_START_ALLOWED: - mPendingIntentBalAllowed = true; - break; case MODE_BACKGROUND_ACTIVITY_START_DENIED: - mPendingIntentBalAllowed = false; + case MODE_BACKGROUND_ACTIVITY_START_COMPAT: + case MODE_BACKGROUND_ACTIVITY_START_ALLOWED: + mPendingIntentBalAllowed = state; break; default: - throw new IllegalArgumentException(state + " is not valid"); + // Assume that future values are some variant of allowing the start. + mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_ALLOWED; + break; } return this; } @@ -141,13 +136,7 @@ public class ComponentOptions { * @see #setPendingIntentBackgroundActivityStartMode(int) */ public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() { - if (mPendingIntentBalAllowed == null) { - return MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; - } else if (mPendingIntentBalAllowed) { - return MODE_BACKGROUND_ACTIVITY_START_ALLOWED; - } else { - return MODE_BACKGROUND_ACTIVITY_START_DENIED; - } + return mPendingIntentBalAllowed; } /** @@ -170,8 +159,8 @@ public class ComponentOptions { /** @hide */ public Bundle toBundle() { Bundle b = new Bundle(); - if (mPendingIntentBalAllowed != null) { - b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed); + if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + b.putInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed); } if (mPendingIntentBalAllowedByPermission) { b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 9c806593b57d..0672064bd5ea 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5704,6 +5704,7 @@ public class Notification implements Parcelable p.headerless(resId == getBaseLayoutResource() || resId == getHeadsUpBaseLayoutResource() || resId == getCompactHeadsUpBaseLayoutResource() + || resId == getMessagingCompactHeadsUpLayoutResource() || resId == getMessagingLayoutResource() || resId == R.layout.notification_template_material_media); RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); @@ -6491,8 +6492,13 @@ public class Notification implements Parcelable // visual regressions. @SuppressWarnings("AndroidFrameworkCompatChange") private boolean bigContentViewRequired() { - if (!Flags.notificationExpansionOptional() - && mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { + if (Flags.notificationExpansionOptional()) { + // Notifications without a bigContentView, style, or actions do not need to expand + boolean exempt = mN.bigContentView == null + && mStyle == null && mActions.size() == 0; + return !exempt; + } + if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { return true; } // Notifications with contentView and without a bigContentView, style, or actions would @@ -7294,6 +7300,10 @@ public class Notification implements Parcelable return R.layout.notification_template_material_compact_heads_up_base; } + private int getMessagingCompactHeadsUpLayoutResource() { + return R.layout.notification_template_material_messaging_compact_heads_up; + } + private int getBigBaseLayoutResource() { return R.layout.notification_template_material_big_base; } @@ -9166,10 +9176,78 @@ public class Notification implements Parcelable @Nullable @Override public RemoteViews makeCompactHeadsUpContentView() { - // TODO(b/336229954): Apply minimal HUN treatment to Messaging Notifications. - return makeHeadsUpContentView(false); + final boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; + Icon conversationIcon = null; + Notification.Action remoteInputAction = null; + if (isConversationLayout) { + + conversationIcon = mShortcutIcon; + + // conversation icon is m + // Extract the conversation icon for one to one conversations from + // the latest incoming message since + // fixTitleAndTextExtras also uses it as data source for title and text + if (conversationIcon == null && !mIsGroupConversation) { + final Message message = findLatestIncomingMessage(); + if (message != null) { + final Person sender = message.mSender; + if (sender != null) { + conversationIcon = sender.getIcon(); + } + } + } + + if (Flags.compactHeadsUpNotificationReply()) { + // Get the first non-contextual inline reply action. + final List<Notification.Action> nonContextualActions = + mBuilder.getNonContextualActions(); + for (int i = 0; i < nonContextualActions.size(); i++) { + final Notification.Action action = nonContextualActions.get(i); + if (mBuilder.hasValidRemoteInput(action)) { + remoteInputAction = action; + break; + } + } + } + } + + // This method fills title and text + fixTitleAndTextExtras(mBuilder.mN.extras); + final StandardTemplateParams p = mBuilder.mParams.reset() + .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) + .highlightExpander(isConversationLayout) + .fillTextsFrom(mBuilder) + .hideTime(true) + .summaryText(""); + p.headerTextSecondary(p.mText); + TemplateBindResult bindResult = new TemplateBindResult(); + + RemoteViews contentView = mBuilder.applyStandardTemplate( + mBuilder.getMessagingCompactHeadsUpLayoutResource(), p, bindResult); + if (conversationIcon != null) { + contentView.setViewVisibility(R.id.icon, View.GONE); + contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE); + contentView.setBoolean(R.id.conversation_icon, "setApplyCircularCrop", true); + contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon); + } + + if (remoteInputAction != null) { + contentView.setViewVisibility(R.id.reply_action_container, View.VISIBLE); + + final RemoteViews inlineReplyButton = + mBuilder.generateActionButton(remoteInputAction, false, p); + // Clear the drawable + inlineReplyButton.setInt(R.id.action0, "setBackgroundResource", 0); + inlineReplyButton.setTextViewText(R.id.action0, + mBuilder.mContext.getString(R.string.notification_compact_heads_up_reply)); + contentView.addView(R.id.reply_action_container, inlineReplyButton); + } else { + contentView.setViewVisibility(R.id.reply_action_container, View.GONE); + } + return contentView; } + /** * @hide */ diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index b82a1e3d8dfa..e4310c1a5361 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -2972,6 +2972,7 @@ public class NotificationManager { android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.ACCESS_NOTIFICATIONS}) @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API) + @SuppressLint("UserHandle") public void registerCallNotificationEventListener(@NonNull String packageName, @NonNull UserHandle userHandle, @NonNull @CallbackExecutor Executor executor, @NonNull CallNotificationEventListener listener) { diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index a29c196d88de..0deb842aff61 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -378,6 +378,14 @@ } ], "file_patterns": ["(/|^)ContextImpl.java"] + }, + { + "file_patterns": [ + "(/|^)Activity.*.java", + "(/|^)PendingIntent.java", + "(/|^)ComtextImpl.java" + ], + "name": "CtsWindowManagerBackgroundActivityTestCases" } ], "postsubmit": [ diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 69f29f3ab081..c529f7d95190 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1698,7 +1698,7 @@ public class DevicePolicyManager { /** * A boolean extra indicating whether device encryption can be skipped as part of - * <a href="#managed-provisioning>provisioning</a>. + * <a href="#managed-provisioning">provisioning</a>. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} or an intent with action * {@link #ACTION_PROVISION_MANAGED_DEVICE} that starts device owner provisioning. @@ -10427,7 +10427,7 @@ public class DevicePolicyManager { @WorkerThread public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName, Bundle settings) { - if (!Flags.dmrhCanSetAppRestriction()) { + if (!Flags.dmrhSetAppRestrictions()) { throwIfParentInstance("setApplicationRestrictions"); } @@ -11835,7 +11835,7 @@ public class DevicePolicyManager { @WorkerThread public @NonNull Bundle getApplicationRestrictions( @Nullable ComponentName admin, String packageName) { - if (!Flags.dmrhCanSetAppRestriction()) { + if (!Flags.dmrhSetAppRestrictions()) { throwIfParentInstance("getApplicationRestrictions"); } @@ -14120,7 +14120,7 @@ public class DevicePolicyManager { public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) { throwIfParentInstance("getParentProfileInstance"); try { - if (Flags.dmrhCanSetAppRestriction()) { + if (Flags.dmrhSetAppRestrictions()) { UserManager um = mContext.getSystemService(UserManager.class); if (!um.isManagedProfile()) { throw new SecurityException("The current user does not have a parent profile."); diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 3d6ec19299cb..4154e667360b 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -244,10 +244,13 @@ flag { } flag { - name: "dmrh_can_set_app_restriction" + name: "dmrh_set_app_restrictions" namespace: "enterprise" description: "Allow DMRH to set application restrictions (both on the profile and the parent)" bug: "328758346" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 50c7b7f9798e..6ceae17d05fb 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -160,3 +160,10 @@ flag { description: "[Minimal HUN] Enables the compact heads up notification feature" bug: "270709257" } + +flag { + name: "compact_heads_up_notification_reply" + namespace: "systemui" + description: "[Minimal HUN] Enables the compact heads up notification reply capability for Conversation Notifications" + bug: "336229954" +}
\ No newline at end of file diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java index 5e1c1e053599..a37f51bb6944 100644 --- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java +++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java @@ -529,7 +529,6 @@ public final class OnDeviceIntelligenceManager { * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only * when passed via Binder IPC. Following restrictions apply : * <ul> - * <li> No Nested Bundles are allowed.</li> * <li> {@link PersistableBundle}s are allowed.</li> * <li> Any primitive types or their collections can be added as usual.</li> * <li>IBinder objects should *not* be added.</li> diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java index 99fa869cee93..1b718d436d6d 100644 --- a/core/java/android/app/prediction/AppPredictionContext.java +++ b/core/java/android/app/prediction/AppPredictionContext.java @@ -24,6 +24,8 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * Class that provides contextual information about the environment in which the app prediction is * used, such as package name, UI in which the app targets are shown, and number of targets. @@ -99,6 +101,13 @@ public final class AppPredictionContext implements Parcelable { } @Override + public int hashCode() { + int hashCode = Objects.hash(mUiSurface, mPackageName); + hashCode = 31 * hashCode + mPredictedTargetCount; + return hashCode; + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java index fef9e7020097..25c1a594ad81 100644 --- a/core/java/android/app/prediction/AppTarget.java +++ b/core/java/android/app/prediction/AppTarget.java @@ -167,6 +167,16 @@ public final class AppTarget implements Parcelable { } @Override + public int hashCode() { + int hashCode = Objects.hash(mId, mPackageName, mClassName, mUser); + if (mShortcutInfo != null) { + hashCode = 31 * hashCode + mShortcutInfo.getId().hashCode(); + } + hashCode = 31 * hashCode + mRank; + return hashCode; + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java index 91da8ec71dae..e36d87899b62 100644 --- a/core/java/android/app/prediction/AppTargetEvent.java +++ b/core/java/android/app/prediction/AppTargetEvent.java @@ -24,6 +24,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * A representation of an app target event. @@ -116,6 +117,13 @@ public final class AppTargetEvent implements Parcelable { } @Override + public int hashCode() { + int hashCode = Objects.hash(mTarget, mLocation); + hashCode = 31 * hashCode + mAction; + return hashCode; + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/app/prediction/OWNERS b/core/java/android/app/prediction/OWNERS index fe012da8e307..73168fb90dbc 100644 --- a/core/java/android/app/prediction/OWNERS +++ b/core/java/android/app/prediction/OWNERS @@ -1,2 +1,4 @@ +pinyaoting@google.com +hyunyoungs@google.com adamcohen@google.com sunnygoyal@google.com diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java index cda286742d28..9b53461568ca 100644 --- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java +++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java @@ -33,11 +33,13 @@ import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import android.window.ActivityWindowInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.util.concurrent.RejectedExecutionException; import java.util.function.BiConsumer; /** @@ -47,6 +49,8 @@ import java.util.function.BiConsumer; */ public class ClientTransactionListenerController { + private static final String TAG = "ClientTransactionListenerController"; + private static ClientTransactionListenerController sController; private final Object mLock = new Object(); @@ -179,10 +183,14 @@ public class ClientTransactionListenerController { } // Dispatch the display changed callbacks. - final int displayCount = configUpdatedDisplayIds.size(); - for (int i = 0; i < displayCount; i++) { - final int displayId = configUpdatedDisplayIds.valueAt(i); - onDisplayChanged(displayId); + try { + final int displayCount = configUpdatedDisplayIds.size(); + for (int i = 0; i < displayCount; i++) { + final int displayId = configUpdatedDisplayIds.valueAt(i); + onDisplayChanged(displayId); + } + } catch (RejectedExecutionException e) { + Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down"); } } @@ -222,7 +230,11 @@ public class ClientTransactionListenerController { } if (changedDisplayId != INVALID_DISPLAY) { - onDisplayChanged(changedDisplayId); + try { + onDisplayChanged(changedDisplayId); + } catch (RejectedExecutionException e) { + Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down"); + } } } @@ -235,9 +247,11 @@ public class ClientTransactionListenerController { /** * Called when receives a {@link Configuration} changed event that is updating display-related * window configuration. + * + * @throws RejectedExecutionException if the display listener handler is closing. */ @VisibleForTesting - public void onDisplayChanged(int displayId) { + public void onDisplayChanged(int displayId) throws RejectedExecutionException { mDisplayManager.handleDisplayChangeFromWindowManager(displayId); } } diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index 3e23762a4f70..b29b52d67310 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -127,4 +127,15 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "impulse_velocity_strategy_for_touch_navigation" + is_exported: true + namespace: "virtual_devices" + description: "Use impulse velocity strategy during conversion of touch navigation flings into Dpad events" + bug: "338426241" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 9e316a2d3560..c8cae822570e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4353,13 +4353,6 @@ public class Intent implements Parcelable, Cloneable { "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH"; /** - * Activity Action: Shows the contrast setting dialog. - * @hide - */ - public static final String ACTION_SHOW_CONTRAST_DIALOG = - "com.android.intent.action.SHOW_CONTRAST_DIALOG"; - - /** * Broadcast Action: A global button was pressed. Includes a single * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that * caused the broadcast. diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index b074e8b0c31b..2e252c12c51d 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -60,6 +60,10 @@ import android.util.AndroidException; * {@link android.app.PendingIntent#getIntentSender() PendingIntent.getIntentSender()}. */ public class IntentSender implements Parcelable { + private static final Bundle SEND_INTENT_DEFAULT_OPTIONS = + ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT).toBundle(); + @UnsupportedAppUsage private final IIntentSender mTarget; IBinder mWhitelistToken; @@ -161,7 +165,8 @@ public class IntentSender implements Parcelable { */ public void sendIntent(Context context, int code, Intent intent, OnFinished onFinished, Handler handler) throws SendIntentException { - sendIntent(context, code, intent, onFinished, handler, null, null /* options */); + sendIntent(context, code, intent, onFinished, handler, null, + SEND_INTENT_DEFAULT_OPTIONS); } /** @@ -194,7 +199,7 @@ public class IntentSender implements Parcelable { OnFinished onFinished, Handler handler, String requiredPermission) throws SendIntentException { sendIntent(context, code, intent, onFinished, handler, requiredPermission, - null /* options */); + SEND_INTENT_DEFAULT_OPTIONS); } /** diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING index a2cfbf5aa5bd..41a4288eae5c 100644 --- a/core/java/android/content/TEST_MAPPING +++ b/core/java/android/content/TEST_MAPPING @@ -56,6 +56,10 @@ } ], "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"] + }, + { + "name": "CtsWindowManagerBackgroundActivityTestCases", + "file_patterns": ["(/|^)IntentSender.java"] } ], "ravenwood-presubmit": [ @@ -63,5 +67,7 @@ "name": "CtsContentTestCasesRavenwood", "host": true } + ], + "postsubmit": [ ] } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 83285e0b8e8b..c506c9741635 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -270,11 +270,20 @@ public abstract class PackageManager { /** * Application level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for a app to inform the installer that a file containing the app's android - * safety label data is bundled into the APK at the given path. + * safety label data is bundled into the APK as a raw resource. + * + * <p>For example: + * <pre> + * <application> + * <property + * android:name="android.content.PROPERTY_ANDROID_SAFETY_LABEL" + * android:resource="@raw/app-metadata"/> + * </application> + * </pre> * @hide */ - public static final String PROPERTY_ANDROID_SAFETY_LABEL_PATH = - "android.content.SAFETY_LABEL_PATH"; + public static final String PROPERTY_ANDROID_SAFETY_LABEL = + "android.content.PROPERTY_ANDROID_SAFETY_LABEL"; /** * A property value set within the manifest. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 4b579e7db9f8..1f6730b9e3f9 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2628,6 +2628,15 @@ public class PackageParser { return Build.VERSION_CODES.CUR_DEVELOPMENT; } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + targetCode)) { + Slog.w(TAG, "Package requires development platform " + targetCode + + ", returning current version " + Build.VERSION.SDK_INT); + return Build.VERSION.SDK_INT; + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + targetCode @@ -2699,6 +2708,15 @@ public class PackageParser { return Build.VERSION_CODES.CUR_DEVELOPMENT; } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + minCode)) { + Slog.w(TAG, "Package requires min development platform " + minCode + + ", returning current version " + Build.VERSION.SDK_INT); + return Build.VERSION.SDK_INT; + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + minCode diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 83742eb7ae84..e2a131c0d527 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -247,3 +247,13 @@ flag { description: "Allow MAIN user to access blocked number provider" bug: "338579331" } + +flag { + name: "restrict_quiet_mode_credential_bug_fix_to_managed_profiles" + namespace: "profile_experiences" + description: "Use user states to check the state of quiet mode for managed profiles only" + bug: "332812630" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java index 153dd9a93490..c7403c0ea98c 100644 --- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java @@ -316,6 +316,15 @@ public class FrameworkParsingPackageUtils { return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + minCode)) { + Slog.w(TAG, "Parsed package requires min development platform " + minCode + + ", returning current version " + Build.VERSION.SDK_INT); + return input.success(Build.VERSION.SDK_INT); + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, @@ -368,19 +377,27 @@ public class FrameworkParsingPackageUtils { return input.success(targetVers); } + // If it's a pre-release SDK and the codename matches this platform, it + // definitely targets this SDK. + if (matchTargetCode(platformSdkCodenames, targetCode)) { + return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + targetCode)) { + Slog.w(TAG, "Parsed package requires development platform " + targetCode + + ", returning current version " + Build.VERSION.SDK_INT); + return input.success(Build.VERSION.SDK_INT); + } + try { if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) { return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); } } catch (IllegalArgumentException e) { - // isAtMost() throws it when encountering an older SDK codename - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, e.getMessage()); - } - - // If it's a pre-release SDK and the codename matches this platform, it - // definitely targets this SDK. - if (matchTargetCode(platformSdkCodenames, targetCode)) { - return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK"); } // Otherwise, we're looking at an incompatible pre-release SDK. diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index d683d72f17be..1eb466cb10a3 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -731,6 +731,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * commits, or is rolled back, either explicitly or by a call to * {@link #yieldIfContendedSafely}. */ + // TODO(340874899) Provide an Executor overload public void beginTransactionWithListener( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, true); @@ -760,6 +761,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * transaction begins, commits, or is rolled back, either * explicitly or by a call to {@link #yieldIfContendedSafely}. */ + // TODO(340874899) Provide an Executor overload public void beginTransactionWithListenerNonExclusive( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, false); @@ -785,6 +787,8 @@ public final class SQLiteDatabase extends SQLiteClosable { * } * </pre> */ + // TODO(340874899) Provide an Executor overload + @SuppressLint("ExecutorRegistration") @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public void beginTransactionWithListenerReadOnly( @Nullable SQLiteTransactionListener transactionListener) { diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 2b7d8f155bff..708f8a173aba 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -39,7 +39,6 @@ import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Point; import android.hardware.CameraExtensionSessionStats; -import android.hardware.CameraIdRemapping; import android.hardware.CameraStatus; import android.hardware.ICameraService; import android.hardware.ICameraServiceListener; @@ -1996,17 +1995,6 @@ public final class CameraManager { } /** - * Remaps Camera Ids in the CameraService. - * - * @hide - */ - @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA) - public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping) - throws CameraAccessException, SecurityException, IllegalArgumentException { - CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping); - } - - /** * Injects session params into existing clients in the CameraService. * * @param cameraId The camera id of client to inject session params into. @@ -2117,13 +2105,6 @@ public final class CameraManager { private final Object mLock = new Object(); - /** - * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state - * in the CameraService every time we connect to it, including when the CameraService - * Binder dies and we reconnect to it. - */ - @Nullable private CameraIdRemapping mActiveCameraIdRemapping; - // Access only through getCameraService to deal with binder death private ICameraService mCameraService; private boolean mHasOpenCloseListenerPermission = false; @@ -2275,41 +2256,6 @@ public final class CameraManager { } catch (RemoteException e) { // Camera service died in all probability } - - if (mActiveCameraIdRemapping != null) { - try { - cameraService.remapCameraIds(mActiveCameraIdRemapping); - } catch (ServiceSpecificException e) { - // Unexpected failure, ignore and continue. - Log.e(TAG, "Unable to remap camera Ids in the camera service"); - } catch (RemoteException e) { - // Camera service died in all probability - } - } - } - - /** Updates the cameraIdRemapping state in the CameraService. */ - public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping) - throws CameraAccessException, SecurityException { - synchronized (mLock) { - ICameraService cameraService = getCameraService(); - if (cameraService == null) { - throw new CameraAccessException( - CameraAccessException.CAMERA_DISCONNECTED, - "Camera service is currently unavailable."); - } - - try { - cameraService.remapCameraIds(cameraIdRemapping); - mActiveCameraIdRemapping = cameraIdRemapping; - } catch (ServiceSpecificException e) { - throw ExceptionUtils.throwAsPublicException(e); - } catch (RemoteException e) { - throw new CameraAccessException( - CameraAccessException.CAMERA_DISCONNECTED, - "Camera service is currently unavailable."); - } - } } /** Injects session params into an existing client for cameraid. */ diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 7754e328bbf9..de26384211a4 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -2359,7 +2359,10 @@ public abstract class CameraMetadata<TKey> { * FPS.</p> * <p>If the session configuration is not supported, the AE mode reported in the * CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p> - * <p>The application can observe the CapturerResult field + * <p>When this AE mode is enabled, the CaptureResult field + * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} will be present and not null. Otherwise, the + * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} field will not be present in the CaptureResult.</p> + * <p>The application can observe the CaptureResult field * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} to determine when low light boost is 'ACTIVE' or * 'INACTIVE'.</p> * <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 5765a73fb3c7..1460515c2438 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2819,6 +2819,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>When low light boost is enabled by setting the AE mode to * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light * boost when the light level threshold is exceeded.</p> + * <p>This field is present in the CaptureResult when the AE mode is set to + * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'. Otherwise, the field is not present.</p> * <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can * indicate when it is not being applied by returning 'INACTIVE'.</p> * <p>This key will be absent from the CaptureResult if AE mode is not set to diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java index 66b0a426f35d..3a271b44eef2 100644 --- a/core/java/android/hardware/usb/DeviceFilter.java +++ b/core/java/android/hardware/usb/DeviceFilter.java @@ -18,6 +18,7 @@ package android.hardware.usb; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.usb.flags.Flags; import android.service.usb.UsbDeviceFilterProto; import android.util.Slog; @@ -57,9 +58,12 @@ public class DeviceFilter { public final String mProductName; // USB device serial number string (or null for unspecified) public final String mSerialNumber; + // USB interface name (or null for unspecified). This will be used when matching devices using + // the available interfaces. + public final String mInterfaceName; public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol, - String manufacturer, String product, String serialnum) { + String manufacturer, String product, String serialnum, String interfaceName) { mVendorId = vid; mProductId = pid; mClass = clasz; @@ -68,6 +72,7 @@ public class DeviceFilter { mManufacturerName = manufacturer; mProductName = product; mSerialNumber = serialnum; + mInterfaceName = interfaceName; } public DeviceFilter(UsbDevice device) { @@ -79,6 +84,7 @@ public class DeviceFilter { mManufacturerName = device.getManufacturerName(); mProductName = device.getProductName(); mSerialNumber = device.getSerialNumber(); + mInterfaceName = null; } public DeviceFilter(@NonNull DeviceFilter filter) { @@ -90,6 +96,7 @@ public class DeviceFilter { mManufacturerName = filter.mManufacturerName; mProductName = filter.mProductName; mSerialNumber = filter.mSerialNumber; + mInterfaceName = filter.mInterfaceName; } public static DeviceFilter read(XmlPullParser parser) @@ -102,7 +109,7 @@ public class DeviceFilter { String manufacturerName = null; String productName = null; String serialNumber = null; - + String interfaceName = null; int count = parser.getAttributeCount(); for (int i = 0; i < count; i++) { String name = parser.getAttributeName(i); @@ -114,6 +121,8 @@ public class DeviceFilter { productName = value; } else if ("serial-number".equals(name)) { serialNumber = value; + } else if ("interface-name".equals(name)) { + interfaceName = value; } else { int intValue; int radix = 10; @@ -144,7 +153,7 @@ public class DeviceFilter { } return new DeviceFilter(vendorId, productId, deviceClass, deviceSubclass, deviceProtocol, - manufacturerName, productName, serialNumber); + manufacturerName, productName, serialNumber, interfaceName); } public void write(XmlSerializer serializer) throws IOException { @@ -173,13 +182,25 @@ public class DeviceFilter { if (mSerialNumber != null) { serializer.attribute(null, "serial-number", mSerialNumber); } + if (mInterfaceName != null) { + serializer.attribute(null, "interface-name", mInterfaceName); + } serializer.endTag(null, "usb-device"); } - private boolean matches(int clasz, int subclass, int protocol) { - return ((mClass == -1 || clasz == mClass) && - (mSubclass == -1 || subclass == mSubclass) && - (mProtocol == -1 || protocol == mProtocol)); + private boolean matches(int usbClass, int subclass, int protocol) { + return ((mClass == -1 || usbClass == mClass) + && (mSubclass == -1 || subclass == mSubclass) + && (mProtocol == -1 || protocol == mProtocol)); + } + + private boolean matches(int usbClass, int subclass, int protocol, String interfaceName) { + if (Flags.enableInterfaceNameDeviceFilter()) { + return matches(usbClass, subclass, protocol) + && (mInterfaceName == null || mInterfaceName.equals(interfaceName)); + } else { + return matches(usbClass, subclass, protocol); + } } public boolean matches(UsbDevice device) { @@ -204,7 +225,7 @@ public class DeviceFilter { for (int i = 0; i < count; i++) { UsbInterface intf = device.getInterface(i); if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), - intf.getInterfaceProtocol())) return true; + intf.getInterfaceProtocol(), intf.getName())) return true; } return false; @@ -320,11 +341,12 @@ public class DeviceFilter { @Override public String toString() { - return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId + - ",mClass=" + mClass + ",mSubclass=" + mSubclass + - ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName + - ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + - "]"; + return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId + + ",mClass=" + mClass + ",mSubclass=" + mSubclass + + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName + + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + + ",mInterfaceName=" + mInterfaceName + + "]"; } /** diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig index 94df16030cdb..40e5ffb141ab 100644 --- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig +++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig @@ -16,3 +16,11 @@ flag { description: "Feature flag for the api to check if a port supports mode change" bug: "323470419" } + +flag { + name: "enable_interface_name_device_filter" + is_exported: true + namespace: "usb" + description: "Feature flag to enable interface name as a parameter for device filter" + bug: "312828160" +} diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 594ec18d9996..334b2316b268 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -173,6 +173,12 @@ public class NetworkPolicyManager { public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby"; /** @hide */ public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_METERED_ALLOW = "metered_allow"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_METERED_DENY_USER = "metered_deny_user"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN = "metered_deny_admin"; private static final boolean ALLOW_PLATFORM_APP_POLICY = true; diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 05a3e182135c..fedc97dcf989 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1387,7 +1387,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param scheme name or {@code null} if this is a relative Uri */ public Builder scheme(String scheme) { - this.scheme = scheme; + if (scheme != null) { + this.scheme = scheme.replaceAll("://", ""); + } else { + this.scheme = null; + } return this; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index d45a17f7194e..91ad22f51345 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -120,6 +120,8 @@ import java.util.concurrent.Executor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * StorageManager is the interface to the systems storage service. The storage @@ -2512,6 +2514,9 @@ public class StorageManager { return userId * PER_USER_RANGE + projectId; } + private static final Pattern PATTERN_USER_ID = Pattern.compile( + "(?i)^/storage/emulated/([0-9]+)"); + /** * Let StorageManager know that the quota type for a file on external storage should * be updated. Android tracks quotas for various media types. Consequently, this should be @@ -2541,26 +2546,35 @@ public class StorageManager { @SystemApi public void updateExternalStorageFileQuotaType(@NonNull File path, @QuotaType int quotaType) throws IOException { + if (!path.exists()) return; + long projectId; final String filePath = path.getCanonicalPath(); - int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE; - // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are also - // returned by enabling FLAG_INCLUDE_SHARED_PROFILE. - if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) { - volFlags |= FLAG_INCLUDE_SHARED_PROFILE; - } - final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags); - final StorageVolume volume = getStorageVolume(availableVolumes, path); - if (volume == null) { - Log.w(TAG, "Failed to update quota type for " + filePath); - return; - } - if (!volume.isEmulated()) { - // We only support quota tracking on emulated filesystems - return; + + final int userId; + final Matcher matcher = PATTERN_USER_ID.matcher(filePath); + if (matcher.find()) { + userId = Integer.parseInt(matcher.group(1)); + } else { // fallback + int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE; + // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are + // also returned by enabling FLAG_INCLUDE_SHARED_PROFILE. + if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) { + volFlags |= FLAG_INCLUDE_SHARED_PROFILE; + } + final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags); + final StorageVolume volume = getStorageVolume(availableVolumes, path); + if (volume == null) { + Log.w(TAG, "Failed to update quota type for " + filePath); + return; + } + if (!volume.isEmulated()) { + // We only support quota tracking on emulated filesystems + return; + } + userId = volume.getOwner().getIdentifier(); } - final int userId = volume.getOwner().getIdentifier(); if (userId < 0) { throw new IllegalStateException("Failed to update quota type for " + filePath); } diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS index d2f4b50eaa1b..857bacd34714 100644 --- a/core/java/android/permission/OWNERS +++ b/core/java/android/permission/OWNERS @@ -1,14 +1,13 @@ # Bug component: 137825 -augale@google.com evanseverson@google.com fayey@google.com jaysullivan@google.com joecastro@google.com -kvakil@google.com mrulhania@google.com ntmyren@google.com rmacgregor@google.com theianchen@google.com yutingfang@google.com zhanghai@google.com +kiranmr@google.com diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 90ec5aad235d..4f5b67c34845 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11173,6 +11173,35 @@ public final class Settings { "visual_query_accessibility_detection_enabled"; /** + * Timeout to be used for unbinding to the configured remote + * {@link android.service.ondeviceintelligence.OnDeviceIntelligenceService} if there are no + * requests in the queue. A value of -1 represents to never unbind. + * + * @hide + */ + public static final String ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS = + "on_device_intelligence_unbind_timeout_ms"; + + + /** + * Timeout that represents maximum idle time before which a callback should be populated. + * + * @hide + */ + public static final String ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS = + "on_device_intelligence_idle_timeout_ms"; + + /** + * Timeout to be used for unbinding to the configured remote + * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} if there + * are no requests in the queue. A value of -1 represents to never unbind. + * + * @hide + */ + public static final String ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS = + "on_device_inference_unbind_timeout_ms"; + + /** * Control whether Night display is currently activated. * @hide */ diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING index d5ac7a7de461..2eb285dda0a2 100644 --- a/core/java/android/provider/TEST_MAPPING +++ b/core/java/android/provider/TEST_MAPPING @@ -8,6 +8,9 @@ } ] }, + { + "name": "CtsMediaProviderTestCases" + }, { "name": "CalendarProviderTests" }, diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 76889dfc300a..88da8ebc3f95 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Notification; import android.app.NotificationChannel; @@ -37,9 +38,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; - import com.android.internal.os.SomeArgs; - import java.lang.annotation.Retention; import java.util.List; @@ -116,6 +115,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS */ protected Handler mHandler; + @SuppressLint("OnNameExpected") @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java index 07367df7bc91..caa0a9c78d47 100644 --- a/core/java/android/service/notification/NotificationStats.java +++ b/core/java/android/service/notification/NotificationStats.java @@ -19,6 +19,7 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Flags; import android.app.RemoteInput; @@ -229,6 +230,7 @@ public final class NotificationStats implements Parcelable { /** * Records that the user has replied to a notification that has a smart reply at least once. */ + @SuppressLint("GetterSetterNames") @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void setSmartReplied() { mSmartReplied = true; diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 1d7091c52d7a..910c4626ea96 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -1007,6 +1007,7 @@ public final class ZenPolicy implements Parcelable { /** * Set whether priority channels are permitted to break through DND. */ + @SuppressLint("BuilderSetStyle") @FlaggedApi(Flags.FLAG_MODES_API) public @NonNull Builder allowPriorityChannels(boolean allow) { mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE; diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java index 8237b20260ea..144c1cda3470 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java @@ -120,6 +120,11 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { */ public static final String MODEL_UNLOADED_BUNDLE_KEY = "model_unloaded"; + /** + * @hide + */ + public static final String DEVICE_CONFIG_UPDATE_BUNDLE_KEY = "device_config_update"; + private IRemoteStorageService mRemoteStorageService; /** diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java index b9ab82cb63a9..4de7b62521d1 100644 --- a/core/java/android/tracing/perfetto/DataSource.java +++ b/core/java/android/tracing/perfetto/DataSource.java @@ -85,7 +85,7 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan new TracingContext<>(this, instanceIndex); fun.trace(ctx); - ctx.flush(); + nativeWritePackets(mNativeObj, ctx.getAndClearAllPendingTracePackets()); } while (nativePerfettoDsTraceIterateNext(mNativeObj)); } finally { nativePerfettoDsTraceIterateBreak(mNativeObj); @@ -130,7 +130,8 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan * @param params Params to initialize the datasource with. */ public void register(DataSourceParams params) { - nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy); + nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy, + params.willNotifyOnStop, params.noFlush); } /** @@ -163,8 +164,8 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan return this.createInstance(inputStream, instanceIndex); } - private static native void nativeRegisterDataSource( - long dataSourcePtr, int bufferExhaustedPolicy); + private static native void nativeRegisterDataSource(long dataSourcePtr, + int bufferExhaustedPolicy, boolean willNotifyOnStop, boolean noFlush); private static native long nativeCreate(DataSource thiz, String name); private static native void nativeFlushAll(long nativeDataSourcePointer); @@ -179,4 +180,6 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan private static native boolean nativePerfettoDsTraceIterateNext(long dataSourcePtr); private static native void nativePerfettoDsTraceIterateBreak(long dataSourcePtr); private static native int nativeGetPerfettoDsInstanceIndex(long dataSourcePtr); + + private static native void nativeWritePackets(long dataSourcePtr, byte[][] packetData); } diff --git a/core/java/android/tracing/perfetto/DataSourceParams.java b/core/java/android/tracing/perfetto/DataSourceParams.java index 6cd04e3d9a8b..e50f9d722fad 100644 --- a/core/java/android/tracing/perfetto/DataSourceParams.java +++ b/core/java/android/tracing/perfetto/DataSourceParams.java @@ -46,12 +46,67 @@ public class DataSourceParams { // after a while. public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1; - public static DataSourceParams DEFAULTS = - new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP); + public static DataSourceParams DEFAULTS = new DataSourceParams.Builder().build(); - public DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy) { + private DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy, + boolean willNotifyOnStop, boolean noFlush) { this.bufferExhaustedPolicy = bufferExhaustedPolicy; + this.willNotifyOnStop = willNotifyOnStop; + this.noFlush = noFlush; } public final @PerfettoDsBufferExhausted int bufferExhaustedPolicy; + public final boolean willNotifyOnStop; + public final boolean noFlush; + + /** + * DataSource Parameters builder + * + * @hide + */ + public static final class Builder { + /** + * Specify behavior when running out of shared memory buffer space. + */ + public Builder setBufferExhaustedPolicy(@PerfettoDsBufferExhausted int value) { + this.mBufferExhaustedPolicy = value; + return this; + } + + /** + * If true, the data source is expected to ack the stop request through the + * NotifyDataSourceStopped() IPC. If false, the service won't wait for an ack. + * Set this parameter to false when dealing with potentially frozen producers + * that wouldn't be able to quickly ack the stop request. + * + * Default value: true + */ + public Builder setWillNotifyOnStop(boolean value) { + this.mWillNotifyOnStop = value; + return this; + } + + /** + * If true, the service won't emit flush requests for this data source. This + * allows the service to reduce the flush-related IPC traffic and better deal + * with frozen producers (see go/perfetto-frozen). + */ + public Builder setNoFlush(boolean value) { + this.mNoFlush = value; + return this; + } + + /** + * Build the DataSource parameters. + */ + public DataSourceParams build() { + return new DataSourceParams( + this.mBufferExhaustedPolicy, this.mWillNotifyOnStop, this.mNoFlush); + } + + private @PerfettoDsBufferExhausted int mBufferExhaustedPolicy = + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP; + private boolean mWillNotifyOnStop = true; + private boolean mNoFlush = false; + } } diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java index 6b7df5441427..98cb4c838eed 100644 --- a/core/java/android/tracing/perfetto/TracingContext.java +++ b/core/java/android/tracing/perfetto/TracingContext.java @@ -59,19 +59,6 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance, T } /** - * Forces a commit of the thread-local tracing data written so far to the - * service. This is almost never required (tracing data is periodically - * committed as trace pages are filled up) and has a non-negligible - * performance hit (requires an IPC + refresh of the current thread-local - * chunk). The only case when this should be used is when handling OnStop() - * asynchronously, to ensure sure that the data is committed before the - * Stop timeout expires. - */ - public void flush() { - nativeFlush(mDataSource.mNativeObj, getAndClearAllPendingTracePackets()); - } - - /** * Can optionally be used to store custom per-sequence * session data, which is not reset when incremental state is cleared * (e.g. configuration options). @@ -109,7 +96,7 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance, T return incrementalState; } - private byte[][] getAndClearAllPendingTracePackets() { + protected byte[][] getAndClearAllPendingTracePackets() { byte[][] res = new byte[mTracePackets.size()][]; for (int i = 0; i < mTracePackets.size(); i++) { ProtoOutputStream tracePacket = mTracePackets.get(i); @@ -120,8 +107,6 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance, T return res; } - private static native void nativeFlush(long dataSourcePtr, byte[][] packetData); - private static native Object nativeGetCustomTls(long nativeDsPtr); private static native void nativeSetCustomTls(long nativeDsPtr, Object tlsState); diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java index 35b137a322e3..c5b6aa7118cb 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -129,7 +129,7 @@ public class FocusFinder { } ViewGroup effective = null; ViewParent nextParent = focused.getParent(); - do { + while (nextParent instanceof ViewGroup) { if (nextParent == root) { return effective != null ? effective : root; } @@ -143,7 +143,7 @@ public class FocusFinder { effective = vg; } nextParent = nextParent.getParent(); - } while (nextParent instanceof ViewGroup); + } return root; } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 9ff29a81a5c6..4837ee5a797c 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -26,11 +26,13 @@ import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFI import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; +import android.app.Activity; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.StrictMode; import android.os.SystemClock; @@ -299,6 +301,11 @@ public class GestureDetector { private VelocityTracker mVelocityTracker; /** + * Determines strategy for velocity calculation + */ + private @VelocityTracker.VelocityTrackerStrategy int mVelocityTrackerStrategy; + + /** * Consistency verifier for debugging purposes. */ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier = @@ -347,17 +354,17 @@ public class GestureDetector { /** * Creates a GestureDetector with the supplied listener. - * This variant of the constructor should be used from a non-UI thread + * This variant of the constructor should be used from a non-UI thread * (as it allows specifying the Handler). - * + * * @param listener the listener invoked for all the callbacks, this must * not be null. * @param handler the handler to use * * @throws NullPointerException if {@code listener} is null. * - * @deprecated Use {@link #GestureDetector(android.content.Context, - * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead. + * @deprecated Use {@link #GestureDetector(Context, GestureDetector.OnGestureListener, Handler)} + * instead. */ @Deprecated public GestureDetector(@NonNull OnGestureListener listener, @Nullable Handler handler) { @@ -367,15 +374,14 @@ public class GestureDetector { /** * Creates a GestureDetector with the supplied listener. * You may only use this constructor from a UI thread (this is the usual situation). - * @see android.os.Handler#Handler() - * + * @see Handler#Handler() + * * @param listener the listener invoked for all the callbacks, this must * not be null. - * + * * @throws NullPointerException if {@code listener} is null. * - * @deprecated Use {@link #GestureDetector(android.content.Context, - * android.view.GestureDetector.OnGestureListener)} instead. + * @deprecated Use {@link #GestureDetector(Context, GestureDetector.OnGestureListener)} instead. */ @Deprecated public GestureDetector(@NonNull OnGestureListener listener) { @@ -384,10 +390,10 @@ public class GestureDetector { /** * Creates a GestureDetector with the supplied listener. - * You may only use this constructor from a {@link android.os.Looper} thread. - * @see android.os.Handler#Handler() + * You may only use this constructor from a {@link Looper} thread. + * @see Handler#Handler() * - * @param context An {@link android.app.Activity} or a {@link Context} created from + * @param context An {@link Activity} or a {@link Context} created from * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or @@ -404,10 +410,10 @@ public class GestureDetector { /** * Creates a GestureDetector with the supplied listener that runs deferred events on the - * thread associated with the supplied {@link android.os.Handler}. - * @see android.os.Handler#Handler() + * thread associated with the supplied {@link Handler}. + * @see Handler#Handler() * - * @param context An {@link android.app.Activity} or a {@link Context} created from + * @param context An {@link Activity} or a {@link Context} created from * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or @@ -419,6 +425,31 @@ public class GestureDetector { */ public GestureDetector(@Nullable @UiContext Context context, @NonNull OnGestureListener listener, @Nullable Handler handler) { + this(context, listener, handler, VelocityTracker.VELOCITY_TRACKER_STRATEGY_DEFAULT); + } + + /** + * Creates a GestureDetector with the supplied listener that runs deferred events on the + * thread associated with the supplied {@link Handler}. + * @see Handler#Handler() + * + * @param context An {@link Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} + * @param listener the listener invoked for all the callbacks, this must + * not be null. If the listener implements the {@link OnDoubleTapListener} or + * {@link OnContextClickListener} then it will also be set as the listener for + * these callbacks (for example when using the {@link SimpleOnGestureListener}). + * @param handler the handler to use for running deferred listener events. + * @param velocityTrackerStrategy strategy to use for velocity calculation of scroll/fling + * events. + * + * @throws NullPointerException if {@code listener} is null. + * + * @hide + */ + public GestureDetector(@Nullable @UiContext Context context, + @NonNull OnGestureListener listener, @Nullable Handler handler, + @VelocityTracker.VelocityTrackerStrategy int velocityTrackerStrategy) { if (handler != null) { mHandler = new GestureHandler(handler); } else { @@ -431,15 +462,16 @@ public class GestureDetector { if (listener instanceof OnContextClickListener) { setContextClickListener((OnContextClickListener) listener); } + mVelocityTrackerStrategy = velocityTrackerStrategy; init(context); } - + /** * Creates a GestureDetector with the supplied listener that runs deferred events on the - * thread associated with the supplied {@link android.os.Handler}. - * @see android.os.Handler#Handler() + * thread associated with the supplied {@link Handler}. + * @see Handler#Handler() * - * @param context An {@link android.app.Activity} or a {@link Context} created from + * @param context An {@link Activity} or a {@link Context} created from * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. @@ -547,7 +579,7 @@ public class GestureDetector { mCurrentMotionEvent = MotionEvent.obtain(ev); if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker = VelocityTracker.obtain(mVelocityTrackerStrategy); } mVelocityTracker.addMovement(ev); diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java index 9503f4925e14..8c14de6156a3 100644 --- a/core/java/android/view/ImeBackAnimationController.java +++ b/core/java/android/view/ImeBackAnimationController.java @@ -210,18 +210,9 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { mInsetsController.setPredictiveBackImeHideAnimInProgress(true); notifyHideIme(); } - if (mStartRootScrollY != 0) { - // RootView is panned, ensure that it is scrolled back to the intended scroll position - if (triggerBack) { - // requesting ime as invisible - mInsetsController.setRequestedVisibleTypes(0, ime()); - // changes the animation state and notifies RootView of changed insets, which - // causes it to reset its scrollY to 0f (animated) - mInsetsController.onAnimationStateChanged(ime(), /*running*/ true); - } else { - // This causes RootView to update its scroll back to the panned position - mInsetsController.getHost().notifyInsetsChanged(); - } + if (mStartRootScrollY != 0 && !triggerBack) { + // This causes RootView to update its scroll back to the panned position + mInsetsController.getHost().notifyInsetsChanged(); } } @@ -237,6 +228,12 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { // the IME away mInsetsController.getHost().getInputMethodManager() .notifyImeHidden(mInsetsController.getHost().getWindowToken(), statsToken); + + // requesting IME as invisible during post-commit + mInsetsController.setRequestedVisibleTypes(0, ime()); + // Changes the animation state. This also notifies RootView of changed insets, which causes + // it to reset its scrollY to 0f (animated) if it was panned + mInsetsController.onAnimationStateChanged(ime(), /*running*/ true); } private void reset() { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index f1cb4103f008..d7f2b01f46ea 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1262,10 +1262,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mHost.getInputMethodManager(), null /* icProto */); } + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_USER, + ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION, + mHost.isHandlingPointerEvent() /* fromUser */); controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs, interpolator, animationType, getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack), - false /* useInsetsAnimationThread */, null /* statsToken */); + false /* useInsetsAnimationThread */, statsToken); } private void controlAnimationUnchecked(@InsetsType int types, @@ -1567,7 +1570,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return; } final ImeTracker.Token statsToken = runner.getStatsToken(); - if (shown) { + if (runner.getAnimationType() == ANIMATION_TYPE_USER) { + ImeTracker.forLogging().onUserFinished(statsToken, shown); + } else if (shown) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW); ImeTracker.forLogging().onShown(statsToken); @@ -1838,6 +1843,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0); mHost.dispatchWindowInsetsAnimationStart(animation, bounds); mStartingAnimation = true; + if (runner.getAnimationType() == ANIMATION_TYPE_USER) { + ImeTracker.forLogging().onDispatched(runner.getStatsToken()); + } runner.setReadyDispatched(true); listener.onReady(runner, types); mStartingAnimation = false; diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index d31f82398cdf..27176a4c2094 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -264,7 +264,6 @@ public final class VelocityTracker { /** * Obtains a velocity tracker with the specified strategy. - * For testing and comparison purposes only. * * @param strategy The strategy Id, VELOCITY_TRACKER_STRATEGY_DEFAULT to use the default. * @return The velocity tracker. @@ -272,6 +271,9 @@ public final class VelocityTracker { * @hide */ public static VelocityTracker obtain(int strategy) { + if (strategy == VELOCITY_TRACKER_STRATEGY_DEFAULT) { + return obtain(); + } return new VelocityTracker(strategy); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 155c0537b5b5..0715474f1c13 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -427,12 +427,6 @@ public final class ViewRootImpl implements ViewParent, private static final long NANOS_PER_SEC = 1000000000; - // If the ViewRootImpl has been idle for more than 200ms, clear the preferred - // frame rate category and frame rate. - private static final int IDLE_TIME_MILLIS = 250; - - private static final long NANOS_PER_MILLI = 1_000_000; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); @@ -665,8 +659,6 @@ public final class ViewRootImpl implements ViewParent, private int mMinusOneFrameIntervalMillis = 0; // VRR interval between the previous and the frame before private int mMinusTwoFrameIntervalMillis = 0; - // VRR has the invalidation idle message been posted? - private boolean mInvalidationIdleMessagePosted = false; /** * Update the Choreographer's FrameInfo object with the timing information for the current @@ -929,15 +921,6 @@ public final class ViewRootImpl implements ViewParent, private String mFrameRateCategoryView; /** - * The resolved pointer icon type requested by this window. - * A null value indicates the resolved pointer icon has not yet been calculated. - */ - // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete. - @Nullable - private Integer mPointerIconType = null; - private PointerIcon mCustomPointerIcon = null; - - /** * The resolved pointer icon requested by this window. * A null value indicates the resolved pointer icon has not yet been calculated. */ @@ -4278,10 +4261,6 @@ public final class ViewRootImpl implements ViewParent, // when the values are applicable. if (mDrawnThisFrame) { mDrawnThisFrame = false; - if (!mInvalidationIdleMessagePosted) { - mInvalidationIdleMessagePosted = true; - mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); - } setCategoryFromCategoryCounts(); updateInfrequentCount(); setPreferredFrameRate(mPreferredFrameRate); @@ -6442,7 +6421,6 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_SYNTHESIZE_INPUT_EVENT = 24; private static final int MSG_DISPATCH_WINDOW_SHOWN = 25; private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26; - private static final int MSG_UPDATE_POINTER_ICON = 27; private static final int MSG_POINTER_CAPTURE_CHANGED = 28; private static final int MSG_INSETS_CONTROL_CHANGED = 29; private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 30; @@ -6507,8 +6485,6 @@ public final class ViewRootImpl implements ViewParent, return "MSG_SYNTHESIZE_INPUT_EVENT"; case MSG_DISPATCH_WINDOW_SHOWN: return "MSG_DISPATCH_WINDOW_SHOWN"; - case MSG_UPDATE_POINTER_ICON: - return "MSG_UPDATE_POINTER_ICON"; case MSG_POINTER_CAPTURE_CHANGED: return "MSG_POINTER_CAPTURE_CHANGED"; case MSG_INSETS_CONTROL_CHANGED: @@ -6523,8 +6499,6 @@ public final class ViewRootImpl implements ViewParent, return "MSG_WINDOW_TOUCH_MODE_CHANGED"; case MSG_KEEP_CLEAR_RECTS_CHANGED: return "MSG_KEEP_CLEAR_RECTS_CHANGED"; - case MSG_CHECK_INVALIDATION_IDLE: - return "MSG_CHECK_INVALIDATION_IDLE"; case MSG_REFRESH_POINTER_ICON: return "MSG_REFRESH_POINTER_ICON"; case MSG_TOUCH_BOOST_TIMEOUT: @@ -6761,10 +6735,6 @@ public final class ViewRootImpl implements ViewParent, final int deviceId = msg.arg1; handleRequestKeyboardShortcuts(receiver, deviceId); } break; - case MSG_UPDATE_POINTER_ICON: { - MotionEvent event = (MotionEvent) msg.obj; - resetPointerIcon(event); - } break; case MSG_POINTER_CAPTURE_CHANGED: { final boolean hasCapture = msg.arg1 != 0; handlePointerCaptureChanged(hasCapture); @@ -6789,30 +6759,6 @@ public final class ViewRootImpl implements ViewParent, mNumPausedForSync = 0; scheduleTraversals(); break; - case MSG_CHECK_INVALIDATION_IDLE: { - long delta; - if (mIsTouchBoosting || mIsFrameRateBoosting || mInsetsAnimationRunning) { - delta = 0; - } else { - delta = System.nanoTime() / NANOS_PER_MILLI - mLastUpdateTimeMillis; - } - if (delta >= IDLE_TIME_MILLIS) { - mFrameRateCategoryHighCount = 0; - mFrameRateCategoryHighHintCount = 0; - mFrameRateCategoryNormalCount = 0; - mFrameRateCategoryLowCount = 0; - mPreferredFrameRate = 0; - mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; - setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE); - setPreferredFrameRate(0f); - mInvalidationIdleMessagePosted = false; - } else { - mInvalidationIdleMessagePosted = true; - mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, - IDLE_TIME_MILLIS - delta); - } - break; - } case MSG_TOUCH_BOOST_TIMEOUT: /** * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). @@ -7874,14 +7820,12 @@ public final class ViewRootImpl implements ViewParent, || action == MotionEvent.ACTION_HOVER_EXIT) { // Other apps or the window manager may change the icon type outside of // this app, therefore the icon type has to be reset on enter/exit event. - mPointerIconType = null; mResolvedPointerIcon = null; } if (action != MotionEvent.ACTION_HOVER_EXIT) { // Resolve the pointer icon if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) { - mPointerIconType = null; mResolvedPointerIcon = null; } } @@ -7944,12 +7888,6 @@ public final class ViewRootImpl implements ViewParent, return mAttachInfo.mHandlingPointerEvent; } - private void resetPointerIcon(MotionEvent event) { - mPointerIconType = null; - mResolvedPointerIcon = null; - updatePointerIcon(event); - } - /** * If there is pointer that is showing a PointerIcon in this window, refresh the icon for that @@ -8609,48 +8547,55 @@ public final class ViewRootImpl implements ViewParent, private int mPendingKeyMetaState; - private final GestureDetector mGestureDetector = new GestureDetector(mContext, - new GestureDetector.OnGestureListener() { - @Override - public boolean onDown(@NonNull MotionEvent e) { - // This can be ignored since it's not clear what KeyEvent this will - // belong to. - return true; - } - - @Override - public void onShowPress(@NonNull MotionEvent e) { + private final GestureDetector mGestureDetector; - } + SyntheticTouchNavigationHandler() { + super(true); + int gestureDetectorVelocityStrategy = + android.companion.virtual.flags.Flags + .impulseVelocityStrategyForTouchNavigation() + ? VelocityTracker.VELOCITY_TRACKER_STRATEGY_IMPULSE + : VelocityTracker.VELOCITY_TRACKER_STRATEGY_DEFAULT; + mGestureDetector = new GestureDetector(mContext, + new GestureDetector.OnGestureListener() { + @Override + public boolean onDown(@NonNull MotionEvent e) { + // This can be ignored since it's not clear what KeyEvent this will + // belong to. + return true; + } - @Override - public boolean onSingleTapUp(@NonNull MotionEvent e) { - dispatchTap(e.getEventTime()); - return true; - } + @Override + public void onShowPress(@NonNull MotionEvent e) { + } - @Override - public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2, - float distanceX, float distanceY) { - // Scroll doesn't translate to DPAD events so should be ignored. - return true; - } + @Override + public boolean onSingleTapUp(@NonNull MotionEvent e) { + dispatchTap(e.getEventTime()); + return true; + } - @Override - public void onLongPress(@NonNull MotionEvent e) { - // Long presses don't translate to DPAD events so should be ignored. - } + @Override + public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2, + float distanceX, float distanceY) { + // Scroll doesn't translate to DPAD events so should be ignored. + return true; + } - @Override - public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, - float velocityX, float velocityY) { - dispatchFling(velocityX, velocityY, e2.getEventTime()); - return true; - } - }); + @Override + public void onLongPress(@NonNull MotionEvent e) { + // Long presses don't translate to DPAD events so should be ignored. + } - SyntheticTouchNavigationHandler() { - super(true); + @Override + public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, + float velocityX, float velocityY) { + dispatchFling(velocityX, velocityY, e2.getEventTime()); + return true; + } + }, + /* handler= */ null, + gestureDetectorVelocityStrategy); } public void process(MotionEvent event) { @@ -13034,10 +12979,6 @@ public final class ViewRootImpl implements ViewParent, private void removeVrrMessages() { mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); - if (mInvalidationIdleMessagePosted) { - mInvalidationIdleMessagePosted = false; - mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); - } } /** diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index f454a6abf6a0..3091bf48a09e 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -754,6 +754,19 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onDispatched */ + static void onDispatched(@NonNull ImeTracker.Token statsToken) { + final IImeTracker service = getImeTrackerService(); + if (service == null) { + return; + } + try { + service.onDispatched(statsToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */ @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index d992febc375e..edc99218c71e 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -71,24 +71,40 @@ public interface ImeTracker { /** The type of the IME request. */ @IntDef(prefix = { "TYPE_" }, value = { TYPE_SHOW, - TYPE_HIDE + TYPE_HIDE, + TYPE_USER, }) @Retention(RetentionPolicy.SOURCE) @interface Type {} - /** IME show request type. */ + /** + * IME show request type. + * + * @see android.view.InsetsController#ANIMATION_TYPE_SHOW + */ int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW; - /** IME hide request type. */ + /** + * IME hide request type. + * + * @see android.view.InsetsController#ANIMATION_TYPE_HIDE + */ int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE; + /** + * IME user-controlled animation request type. + * + * @see android.view.InsetsController#ANIMATION_TYPE_USER + */ + int TYPE_USER = ImeProtoEnums.TYPE_USER; + /** The status of the IME request. */ @IntDef(prefix = { "STATUS_" }, value = { STATUS_RUN, STATUS_CANCEL, STATUS_FAIL, STATUS_SUCCESS, - STATUS_TIMEOUT + STATUS_TIMEOUT, }) @Retention(RetentionPolicy.SOURCE) @interface Status {} @@ -117,7 +133,7 @@ public interface ImeTracker { @IntDef(prefix = { "ORIGIN_" }, value = { ORIGIN_CLIENT, ORIGIN_SERVER, - ORIGIN_IME + ORIGIN_IME, }) @Retention(RetentionPolicy.SOURCE) @interface Origin {} @@ -400,20 +416,36 @@ public interface ImeTracker { void onCancelled(@Nullable Token token, @Phase int phase); /** - * Called when the IME show request is successful. + * Called when the show IME request is successful. * * @param token the token tracking the current IME request or {@code null} otherwise. */ void onShown(@Nullable Token token); /** - * Called when the IME hide request is successful. + * Called when the hide IME request is successful. * * @param token the token tracking the current IME request or {@code null} otherwise. */ void onHidden(@Nullable Token token); /** + * Called when the user-controlled IME request was dispatched to the requesting app. The + * user animation can take an undetermined amount of time, so it shouldn't be tracked. + * + * @param token the token tracking the current IME request or {@code null} otherwise. + */ + void onDispatched(@Nullable Token token); + + /** + * Called when the animation of the user-controlled IME request finished. + * + * @param token the token tracking the current IME request or {@code null} otherwise. + * @param shown whether the end state of the animation was shown or hidden. + */ + void onUserFinished(@Nullable Token token, boolean shown); + + /** * Returns whether the current IME request was created due to a user interaction. This can * only be {@code true} when running on the view's UI thread. * @@ -482,13 +514,6 @@ public interface ImeTracker { /** Whether the stack trace at the request call site should be logged. */ private boolean mLogStackTrace; - private void reloadSystemProperties() { - mLogProgress = SystemProperties.getBoolean( - "persist.debug.imetracker", false); - mLogStackTrace = SystemProperties.getBoolean( - "persist.debug.imerequest.logstacktrace", false); - } - @NonNull @Override public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin, @@ -497,7 +522,7 @@ public interface ImeTracker { final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type, origin, reason, fromUser); - Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide") + Log.i(TAG, token.mTag + ": " + getOnStartPrefix(type) + " at " + Debug.originToString(origin) + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason) + " fromUser " + fromUser, @@ -552,6 +577,45 @@ public interface ImeTracker { Log.i(TAG, token.mTag + ": onHidden"); } + + @Override + public void onDispatched(@Nullable Token token) { + if (token == null) return; + IInputMethodManagerGlobalInvoker.onDispatched(token); + + Log.i(TAG, token.mTag + ": onDispatched"); + } + + @Override + public void onUserFinished(@Nullable Token token, boolean shown) { + if (token == null) return; + // This is already sent to ImeTrackerService to mark it finished during onDispatched. + + Log.i(TAG, token.mTag + ": onUserFinished " + (shown ? "shown" : "hidden")); + } + + /** + * Gets the prefix string for {@link #onStart} based on the given request type. + * + * @param type request type for which to create the prefix string with. + */ + @NonNull + private static String getOnStartPrefix(@Type int type) { + return switch (type) { + case TYPE_SHOW -> "onRequestShow"; + case TYPE_HIDE -> "onRequestHide"; + case TYPE_USER -> "onRequestUser"; + default -> "onRequestUnknown"; + }; + } + + /** Reloads the system properties related to this class. */ + private void reloadSystemProperties() { + mLogProgress = SystemProperties.getBoolean( + "persist.debug.imetracker", false); + mLogStackTrace = SystemProperties.getBoolean( + "persist.debug.imerequest.logstacktrace", false); + } }; /** The singleton IME tracker instance for instrumenting jank metrics. */ diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 7885cd95ca25..11ee286652a1 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -54,6 +54,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -431,6 +432,14 @@ public final class InputMethodInfo implements Parcelable { * @hide */ public InputMethodInfo(InputMethodInfo source) { + this(source, Collections.emptyList()); + } + + /** + * @hide + */ + public InputMethodInfo(@NonNull InputMethodInfo source, + @NonNull List<InputMethodSubtype> additionalSubtypes) { mId = source.mId; mSettingsActivityName = source.mSettingsActivityName; mLanguageSettingsActivityName = source.mLanguageSettingsActivityName; @@ -445,7 +454,19 @@ public final class InputMethodInfo implements Parcelable { mIsVrOnly = source.mIsVrOnly; mIsVirtualDeviceOnly = source.mIsVirtualDeviceOnly; mService = source.mService; - mSubtypes = source.mSubtypes; + if (additionalSubtypes.isEmpty()) { + mSubtypes = source.mSubtypes; + } else { + final ArrayList<InputMethodSubtype> subtypes = source.mSubtypes.toList(); + final int additionalSubtypeCount = additionalSubtypes.size(); + for (int i = 0; i < additionalSubtypeCount; ++i) { + final InputMethodSubtype additionalSubtype = additionalSubtypes.get(i); + if (!subtypes.contains(additionalSubtype)) { + subtypes.add(additionalSubtype); + } + } + mSubtypes = new InputMethodSubtypeArray(subtypes); + } mHandledConfigChanges = source.mHandledConfigChanges; mSupportsStylusHandwriting = source.mSupportsStylusHandwriting; mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting; diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java index c243a22b27b1..e2d343f45e5d 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java +++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java @@ -25,6 +25,7 @@ import android.util.Slog; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -163,6 +164,18 @@ public class InputMethodSubtypeArray { } /** + * @return A list of {@link InputMethodInfo} copied from this array. + */ + @NonNull + public ArrayList<InputMethodSubtype> toList() { + final ArrayList<InputMethodSubtype> list = new ArrayList<>(mCount); + for (int i = 0; i < mCount; ++i) { + list.add(get(i)); + } + return list; + } + + /** * Return the number of {@link InputMethodSubtype} objects. */ public int getCount() { diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig index 3a39631f8c81..503e542f715a 100644 --- a/core/java/android/widget/flags/notification_widget_flags.aconfig +++ b/core/java/android/widget/flags/notification_widget_flags.aconfig @@ -47,3 +47,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "messaging_child_request_layout" + namespace: "systemui" + description: "MessagingChild always needs to be measured during MessagingLinearLayout onMeasure." + bug: "324537506" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 31e3a342d21a..af3d7eb87f71 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -56,6 +56,8 @@ import com.android.internal.R; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.DecorView; +import java.io.Closeable; +import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.function.Consumer; @@ -568,6 +570,12 @@ public final class SplashScreenView extends FrameLayout { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); releaseAnimationSurfaceHost(); + if (mIconView instanceof ImageView imageView + && imageView.getDrawable() instanceof Closeable closeableDrawable) { + try { + closeableDrawable.close(); + } catch (IOException ignore) { } + } } @Override diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index d0f3c3eb9455..6f7660a0fb3d 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -84,8 +84,8 @@ public final class TaskFragmentOperation implements Parcelable { /** * Sets the activity navigation to be isolated, where the activity navigation on the * TaskFragment is separated from the rest activities in the Task. Activities cannot be - * started on an isolated TaskFragment unless the activities are launched from the same - * TaskFragment or explicitly requested to. + * started on an isolated TaskFragment unless explicitly requested to. That said, new launched + * activities should be positioned as a sibling to the TaskFragment with higher z-ordering. */ public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11; @@ -149,6 +149,18 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18; + /** + * Sets the TaskFragment to be pinned. + * <p> + * If a TaskFragment is pinned, the TaskFragment should be the top-most TaskFragment among other + * sibling TaskFragments. Any newly launched and embeddable activity should not be placed in the + * pinned TaskFragment, unless the activity is launched from the pinned TaskFragment or + * explicitly requested to. Non-embeddable activities are not restricted to. + * <p> + * See {@link #OP_TYPE_REORDER_TO_FRONT} on how to reorder a pinned TaskFragment to the top. + */ + public static final int OP_TYPE_SET_PINNED = 19; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -170,6 +182,7 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_DIM_ON_TASK, OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH, OP_TYPE_SET_DECOR_SURFACE_BOOSTED, + OP_TYPE_SET_PINNED, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java index a77c23475c60..15554167c702 100644 --- a/core/java/android/window/TaskFragmentParentInfo.java +++ b/core/java/android/window/TaskFragmentParentInfo.java @@ -18,6 +18,8 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.TestApi; import android.app.WindowConfiguration; import android.content.res.Configuration; import android.os.Parcel; @@ -27,10 +29,13 @@ import android.view.SurfaceControl; import java.util.Objects; /** - * The information about the parent Task of a particular TaskFragment + * The information about the parent Task of a particular TaskFragment. + * * @hide */ -public class TaskFragmentParentInfo implements Parcelable { +@SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. +@TestApi +public final class TaskFragmentParentInfo implements Parcelable { @NonNull private final Configuration mConfiguration = new Configuration(); @@ -42,6 +47,7 @@ public class TaskFragmentParentInfo implements Parcelable { @Nullable private final SurfaceControl mDecorSurface; + /** @hide */ public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId, boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) { mConfiguration.setTo(configuration); @@ -51,6 +57,7 @@ public class TaskFragmentParentInfo implements Parcelable { mDecorSurface = decorSurface; } + /** @hide */ public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { mConfiguration.setTo(info.getConfiguration()); mDisplayId = info.mDisplayId; @@ -59,7 +66,11 @@ public class TaskFragmentParentInfo implements Parcelable { mDecorSurface = info.mDecorSurface; } - /** The {@link Configuration} of the parent Task */ + /** + * The {@link Configuration} of the parent Task + * + * @hide + */ @NonNull public Configuration getConfiguration() { return mConfiguration; @@ -68,19 +79,27 @@ public class TaskFragmentParentInfo implements Parcelable { /** * The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the * Task is detached from previously associated display. + * + * @hide */ public int getDisplayId() { return mDisplayId; } - /** Whether the parent Task is visible or not */ + /** + * Whether the parent Task is visible or not + * + * @hide + */ public boolean isVisible() { return mVisible; } /** * Whether the parent Task has any direct child activity, which is not embedded in any - * TaskFragment, or not + * TaskFragment, or not. + * + * @hide */ public boolean hasDirectActivity() { return mHasDirectActivity; @@ -93,6 +112,8 @@ public class TaskFragmentParentInfo implements Parcelable { * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer( * Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should * be dispatched to the client. + * + * @hide */ public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) { if (that == null) { @@ -103,6 +124,7 @@ public class TaskFragmentParentInfo implements Parcelable { && mDecorSurface == that.mDecorSurface; } + /** @hide */ @Nullable public SurfaceControl getDecorSurface() { return mDecorSurface; @@ -156,6 +178,7 @@ public class TaskFragmentParentInfo implements Parcelable { return result; } + @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. @Override public void writeToParcel(@NonNull Parcel dest, int flags) { mConfiguration.writeToParcel(dest, flags); @@ -173,6 +196,8 @@ public class TaskFragmentParentInfo implements Parcelable { mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR); } + @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. + @NonNull public static final Creator<TaskFragmentParentInfo> CREATOR = new Creator<TaskFragmentParentInfo>() { @Override @@ -186,6 +211,7 @@ public class TaskFragmentParentInfo implements Parcelable { } }; + @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. @Override public int describeContents() { return 0; diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java index 4dada108c4c6..32e3f5ad10ca 100644 --- a/core/java/android/window/TaskFragmentTransaction.java +++ b/core/java/android/window/TaskFragmentTransaction.java @@ -24,7 +24,6 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.content.Intent; -import android.content.res.Configuration; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -248,13 +247,6 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } - // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release. - /** Configuration of the parent Task. */ - @NonNull - public Change setTaskConfiguration(@NonNull Configuration configuration) { - return this; - } - /** * If the {@link #TYPE_TASK_FRAGMENT_ERROR} is from a {@link WindowContainerTransaction} * from the {@link TaskFragmentOrganizer}, it may come with an error callback token to @@ -299,12 +291,11 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } - // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release. /** * Sets info of the parent Task of the embedded TaskFragment. * @see TaskFragmentParentInfo * - * @hide pending unhide + * @hide */ @NonNull public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { @@ -338,12 +329,6 @@ public final class TaskFragmentTransaction implements Parcelable { return mTaskId; } - // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release. - @Nullable - public Configuration getTaskConfiguration() { - return mTaskFragmentParentInfo.getConfiguration(); - } - @Nullable public IBinder getErrorCallbackToken() { return mErrorCallbackToken; @@ -365,8 +350,10 @@ public final class TaskFragmentTransaction implements Parcelable { return mActivityToken; } - // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release. - /** @hide pending unhide */ + /** + * Obtains the {@link TaskFragmentParentInfo} for this transaction. + */ + @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. @Nullable public TaskFragmentParentInfo getTaskFragmentParentInfo() { return mTaskFragmentParentInfo; diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index 41b6d31661ce..a2e3d40e5c6b 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -16,6 +16,7 @@ package android.window; +import android.annotation.IntDef; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -32,6 +33,9 @@ import android.os.SystemClock; import android.view.Surface; import android.view.WindowInsetsController; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Represents a task snapshot. * @hide @@ -68,6 +72,21 @@ public class TaskSnapshot implements Parcelable { private final boolean mHasImeSurface; // Must be one of the named color spaces, otherwise, always use SRGB color space. private final ColorSpace mColorSpace; + private int mInternalReferences; + + /** This snapshot object is being broadcast. */ + public static final int REFERENCE_BROADCAST = 1; + /** This snapshot object is in the cache. */ + public static final int REFERENCE_CACHE = 1 << 1; + /** This snapshot object is being persistent. */ + public static final int REFERENCE_PERSIST = 1 << 2; + @IntDef(flag = true, prefix = { "REFERENCE_" }, value = { + REFERENCE_BROADCAST, + REFERENCE_CACHE, + REFERENCE_PERSIST + }) + @Retention(RetentionPolicy.SOURCE) + @interface ReferenceFlags {} public TaskSnapshot(long id, long captureTime, @NonNull ComponentName topActivityComponent, HardwareBuffer snapshot, @@ -296,7 +315,28 @@ public class TaskSnapshot implements Parcelable { + " mWindowingMode=" + mWindowingMode + " mAppearance=" + mAppearance + " mIsTranslucent=" + mIsTranslucent - + " mHasImeSurface=" + mHasImeSurface; + + " mHasImeSurface=" + mHasImeSurface + + " mInternalReferences=" + mInternalReferences; + } + + /** + * Adds a reference when the object is held somewhere. + * Only used in core. + */ + public synchronized void addReference(@ReferenceFlags int usage) { + mInternalReferences |= usage; + } + + /** + * Removes a reference when the object is not held from somewhere. The snapshot will be closed + * once the reference becomes zero. + * Only used in core. + */ + public synchronized void removeReference(@ReferenceFlags int usage) { + mInternalReferences &= ~usage; + if (mInternalReferences == 0 && mSnapshot != null && !mSnapshot.isClosed()) { + mSnapshot.close(); + } } public static final @NonNull Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() { diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index cd13c4abac0b..4b2beb903325 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -9,6 +9,16 @@ flag { } flag { + name: "disable_thin_letterboxing_policy" + namespace: "large_screen_experiences_app_compat" + description: "Whether reachability is disabled in case of thin letterboxing" + bug: "341027847" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "allows_screen_size_decoupled_from_status_bar_and_cutout" namespace: "large_screen_experiences_app_compat" description: "When necessary, configuration decoupled from status bar and display cutout" diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java index 6864bf7bae16..8e18f842a055 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java @@ -24,6 +24,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__ENABLED; @@ -32,6 +33,7 @@ import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON_LONG_PRESS; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__QUICK_SETTINGS; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; @@ -48,7 +50,6 @@ import android.content.ComponentName; import android.content.Context; import android.provider.Settings; -import com.android.internal.accessibility.common.ShortcutConstants; import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; import com.android.internal.util.FrameworkStatsLog; @@ -248,6 +249,8 @@ public final class AccessibilityStatsLogUtils { } case HARDWARE: return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY; + case QUICK_SETTINGS: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__QUICK_SETTINGS; } return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; } diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java index faaf7d5cef18..8132652ed6f4 100644 --- a/core/java/com/android/internal/content/om/OverlayConfigParser.java +++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java @@ -24,6 +24,8 @@ import android.content.pm.PackagePartitions; import android.content.pm.PackagePartitions.SystemPartition; import android.os.Build; import android.os.FileUtils; +import android.os.SystemProperties; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Xml; @@ -241,6 +243,18 @@ public final class OverlayConfigParser { } } + @FunctionalInterface + public interface SysPropWrapper{ + /** + * Get system property + * + * @param property the key to look up. + * + * @return The property value if found, empty string otherwise. + */ + String get(String property); + } + /** * Retrieves overlays configured within the partition in increasing priority order. * @@ -320,6 +334,76 @@ public final class OverlayConfigParser { } /** + * Expand the property inside a rro configuration path. + * + * A RRO configuration can contain a property, this method expands + * the property to its value. + * + * Only read only properties allowed, prefixed with ro. Other + * properties will raise exception. + * + * Only a single property in the path is allowed. + * + * Example "${ro.boot.hardware.sku}/config.xml" would expand to + * "G020N/config.xml" + * + * @param configPath path to expand + * @param sysPropWrapper method used for reading properties + * + * @return The expanded path. Returns null if configPath is null. + */ + @VisibleForTesting + public static String expandProperty(String configPath, + SysPropWrapper sysPropWrapper) { + if (configPath == null) { + return null; + } + + int propStartPos = configPath.indexOf("${"); + if (propStartPos == -1) { + // No properties inside the string, return as is + return configPath; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(configPath.substring(0, propStartPos)); + + // Read out the end position + int propEndPos = configPath.indexOf("}", propStartPos); + if (propEndPos == -1) { + throw new IllegalStateException("Malformed property, unmatched braces, in: " + + configPath); + } + + // Confirm that there is only one property inside the string + if (configPath.indexOf("${", propStartPos + 2) != -1) { + throw new IllegalStateException("Only a single property supported in path: " + + configPath); + } + + final String propertyName = configPath.substring(propStartPos + 2, propEndPos); + if (!propertyName.startsWith("ro.")) { + throw new IllegalStateException("Only read only properties can be used when " + + "merging RRO config files: " + propertyName); + } + final String propertyValue = sysPropWrapper.get(propertyName); + if (TextUtils.isEmpty(propertyValue)) { + throw new IllegalStateException("Property is empty or doesn't exist: " + propertyName); + } + Log.d(TAG, String.format("Using property in overlay config path: \"%s\"", propertyName)); + sb.append(propertyValue); + + // propEndPos points to '}', need to step to next character, might be outside of string + propEndPos = propEndPos + 1; + // Append the remainder, if exists + if (propEndPos < configPath.length()) { + sb.append(configPath.substring(propEndPos)); + } + + return sb.toString(); + } + + /** * Parses a <merge> tag within an overlay configuration file. * * Merge tags allow for other configuration files to be "merged" at the current parsing @@ -331,10 +415,21 @@ public final class OverlayConfigParser { @Nullable OverlayScanner scanner, @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos, @NonNull ParsingContext parsingContext) { - final String path = parser.getAttributeValue(null, "path"); + final String path; + + try { + SysPropWrapper sysPropWrapper = p -> { + return SystemProperties.get(p, ""); + }; + path = expandProperty(parser.getAttributeValue(null, "path"), sysPropWrapper); + } catch (IllegalStateException e) { + throw new IllegalStateException(String.format("<merge> path expand error in %s at %s", + configFile, parser.getPositionDescription()), e); + } + if (path == null) { - throw new IllegalStateException(String.format("<merge> without path in %s at %s" - + configFile, parser.getPositionDescription())); + throw new IllegalStateException(String.format("<merge> without path in %s at %s", + configFile, parser.getPositionDescription())); } if (path.startsWith("/")) { diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl index ab4edb65780b..ebae39ee3241 100644 --- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl +++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl @@ -64,20 +64,28 @@ interface IImeTracker { oneway void onCancelled(in ImeTracker.Token statsToken, int phase); /** - * Called when the IME show request is successful. + * Called when the show IME request is successful. * * @param statsToken the token tracking the current IME request. */ oneway void onShown(in ImeTracker.Token statsToken); /** - * Called when the IME hide request is successful. + * Called when the hide IME request is successful. * * @param statsToken the token tracking the current IME request. */ oneway void onHidden(in ImeTracker.Token statsToken); /** + * Called when the user-controlled IME request was dispatched to the requesting app. The + * user animation can take an undetermined amount of time, so it shouldn't be tracked. + * + * @param statsToken the token tracking the current IME request. + */ + oneway void onDispatched(in ImeTracker.Token statsToken); + + /** * Checks whether there are any pending IME visibility requests. * * @return {@code true} iff there are pending IME visibility requests. diff --git a/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java index 91b80ddaa836..24cd1c9cd7de 100644 --- a/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java +++ b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java @@ -52,8 +52,14 @@ final class ImeTracingPerfettoImpl extends ImeTracing { ImeTracingPerfettoImpl() { Producer.init(InitArguments.DEFAULTS); - mDataSource.register( - new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .setNoFlush(true) + .setWillNotifyOnStop(false) + .build(); + mDataSource.register(params); } diff --git a/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java b/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java new file mode 100644 index 000000000000..92d453ba0e9f --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.inputmethod; + +import android.annotation.BinderThread; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; + +/** + * An internal interface that mirrors {@link IInlineSuggestionsRequestCallback}. + * + * <p>This interface is used to forward incoming IPCs from + * {@link com.android.server.inputmethod.AutofillSuggestionsController} to + * {@link com.android.server.autofill.AutofillInlineSuggestionsRequestSession}.</p> + */ +public interface InlineSuggestionsRequestCallback { + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsUnsupported()}. + */ + @BinderThread + void onInlineSuggestionsUnsupported(); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest( + * InlineSuggestionsRequest, IInlineSuggestionsResponseCallback)}. + */ + @BinderThread + void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodStartInput(AutofillId)}. + */ + @BinderThread + void onInputMethodStartInput(AutofillId imeFieldId); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodShowInputRequested(boolean)}. + */ + @BinderThread + void onInputMethodShowInputRequested(boolean requestResult); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodStartInputView()}. + */ + @BinderThread + void onInputMethodStartInputView(); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodFinishInputView()}. + */ + @BinderThread + void onInputMethodFinishInputView(); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodFinishInput()}. + */ + @BinderThread + void onInputMethodFinishInput(); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsSessionInvalidated()}. + */ + @BinderThread + void onInlineSuggestionsSessionInvalidated(); +} diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index a0aad31d2e04..2a5593f6d584 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -297,6 +297,8 @@ public final class InputMethodDebug { return "SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT"; case SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION: return "SHOW_SOFT_INPUT_IMM_DEPRECATION"; + case SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION: + return "CONTROL_WINDOW_INSETS_ANIMATION"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index da738a01ec39..eb6a81031321 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -88,6 +88,7 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL, SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT, SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION, + SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION, }) public @interface SoftInputShowHideReason { /** Default, undefined reason. */ @@ -397,4 +398,10 @@ public @interface SoftInputShowHideReason { * {@link InputMethodManager#showSoftInputFromInputMethod(IBinder, int)}. */ int SHOW_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IMM_DEPRECATION; + + /** + * Show / Hide soft input by application-controlled animation in + * {@link android.view.InsetsController#controlWindowInsetsAnimation}. + */ + int CONTROL_WINDOW_INSETS_ANIMATION = ImeProtoEnums.REASON_CONTROL_WINDOW_INSETS_ANIMATION; } diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 9f9aae53e0af..24971f51aabf 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -19,6 +19,7 @@ package com.android.internal.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.BatteryConsumer; +import android.os.BatteryStats; import android.os.Bundle; import android.os.Parcel; import android.os.PersistableBundle; @@ -34,8 +35,12 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for @@ -75,6 +80,10 @@ public final class PowerStats { */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public static class Descriptor { + public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device"; + public static final String EXTRA_STATE_STATS_FORMAT = "format-state"; + public static final String EXTRA_UID_STATS_FORMAT = "format-uid"; + public static final String XML_TAG_DESCRIPTOR = "descriptor"; private static final String XML_ATTR_ID = "id"; private static final String XML_ATTR_NAME = "name"; @@ -120,6 +129,10 @@ public final class PowerStats { */ public final PersistableBundle extras; + private PowerStatsFormatter mDeviceStatsFormatter; + private PowerStatsFormatter mStateStatsFormatter; + private PowerStatsFormatter mUidStatsFormatter; + public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId, int statsArrayLength, @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength, int uidStatsArrayLength, @@ -131,7 +144,7 @@ public final class PowerStats { public Descriptor(int customPowerComponentId, String name, int statsArrayLength, @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength, - int uidStatsArrayLength, PersistableBundle extras) { + int uidStatsArrayLength, @NonNull PersistableBundle extras) { if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) { throw new IllegalArgumentException( "statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH); @@ -154,6 +167,39 @@ public final class PowerStats { } /** + * Returns a custom formatter for this type of power stats. + */ + public PowerStatsFormatter getDeviceStatsFormatter() { + if (mDeviceStatsFormatter == null) { + mDeviceStatsFormatter = new PowerStatsFormatter( + extras.getString(EXTRA_DEVICE_STATS_FORMAT)); + } + return mDeviceStatsFormatter; + } + + /** + * Returns a custom formatter for this type of power stats, specifically per-state stats. + */ + public PowerStatsFormatter getStateStatsFormatter() { + if (mStateStatsFormatter == null) { + mStateStatsFormatter = new PowerStatsFormatter( + extras.getString(EXTRA_STATE_STATS_FORMAT)); + } + return mStateStatsFormatter; + } + + /** + * Returns a custom formatter for this type of power stats, specifically per-UID stats. + */ + public PowerStatsFormatter getUidStatsFormatter() { + if (mUidStatsFormatter == null) { + mUidStatsFormatter = new PowerStatsFormatter( + extras.getString(EXTRA_UID_STATS_FORMAT)); + } + return mUidStatsFormatter; + } + + /** * Returns the label associated with the give state key, e.g. "5G-high" for the * state of Mobile Radio representing the 5G mode and high signal power. */ @@ -491,20 +537,22 @@ public final class PowerStats { StringBuilder sb = new StringBuilder(); sb.append("duration=").append(durationMs).append(" ").append(descriptor.name); if (stats.length > 0) { - sb.append("=").append(Arrays.toString(stats)); + sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats)); } if (descriptor.stateStatsArrayLength != 0) { + PowerStatsFormatter formatter = descriptor.getStateStatsFormatter(); for (int i = 0; i < stateStats.size(); i++) { - sb.append(" ["); + sb.append(" ("); sb.append(descriptor.getStateLabel(stateStats.keyAt(i))); - sb.append("]="); - sb.append(Arrays.toString(stateStats.valueAt(i))); + sb.append(") "); + sb.append(formatter.format(stateStats.valueAt(i))); } } + PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter(); for (int i = 0; i < uidStats.size(); i++) { sb.append(uidPrefix) .append(UserHandle.formatUid(uidStats.keyAt(i))) - .append(": ").append(Arrays.toString(uidStats.valueAt(i))); + .append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i))); } return sb.toString(); } @@ -513,26 +561,29 @@ public final class PowerStats { * Prints the contents of the stats snapshot. */ public void dump(IndentingPrintWriter pw) { - pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')'); + pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')'); pw.increaseIndent(); pw.print("duration", durationMs).println(); + if (descriptor.statsArrayLength != 0) { - pw.print("stats", Arrays.toString(stats)).println(); + pw.println(descriptor.getDeviceStatsFormatter().format(stats)); } if (descriptor.stateStatsArrayLength != 0) { + PowerStatsFormatter formatter = descriptor.getStateStatsFormatter(); for (int i = 0; i < stateStats.size(); i++) { - pw.print("state "); + pw.print(" ("); pw.print(descriptor.getStateLabel(stateStats.keyAt(i))); - pw.print(": "); - pw.print(Arrays.toString(stateStats.valueAt(i))); + pw.print(") "); + pw.print(formatter.format(stateStats.valueAt(i))); pw.println(); } } + PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter(); for (int i = 0; i < uidStats.size(); i++) { pw.print("UID "); - pw.print(uidStats.keyAt(i)); + pw.print(UserHandle.formatUid(uidStats.keyAt(i))); pw.print(": "); - pw.print(Arrays.toString(uidStats.valueAt(i))); + pw.print(uidStatsFormatter.format(uidStats.valueAt(i))); pw.println(); } pw.decreaseIndent(); @@ -542,4 +593,126 @@ public final class PowerStats { public String toString() { return "PowerStats: " + formatForBatteryHistory(" UID "); } + + public static class PowerStatsFormatter { + private static class Section { + public String label; + public int position; + public int length; + public boolean optional; + public boolean typePower; + } + + private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0; + private static final Pattern SECTION_PATTERN = + Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*"); + private final List<Section> mSections; + + public PowerStatsFormatter(String format) { + mSections = parseFormat(format); + } + + /** + * Produces a formatted string representing the supplied array, with labels + * and other adornments specific to the power stats layout. + */ + public String format(long[] stats) { + return format(mSections, stats); + } + + private List<Section> parseFormat(String format) { + if (format == null || format.isBlank()) { + return null; + } + + ArrayList<Section> sections = new ArrayList<>(); + Matcher matcher = SECTION_PATTERN.matcher(format); + for (int position = 0; position < format.length(); position = matcher.end()) { + if (!matcher.find() || matcher.start() != position) { + Slog.wtf(TAG, "Bad power stats format '" + format + "'"); + return null; + } + Section section = new Section(); + section.label = matcher.group(1); + section.position = Integer.parseUnsignedInt(matcher.group(2)); + String length = matcher.group("L"); + if (length != null) { + section.length = Integer.parseUnsignedInt(length); + } else { + section.length = 1; + } + String flags = matcher.group("F"); + if (flags != null) { + for (int i = 0; i < flags.length(); i++) { + char flag = flags.charAt(i); + switch (flag) { + case '?': + section.optional = true; + break; + case 'p': + section.typePower = true; + break; + default: + Slog.e(TAG, + "Unsupported format option '" + flag + "' in " + format); + break; + } + } + } + sections.add(section); + } + + return sections; + } + + private String format(List<Section> sections, long[] stats) { + if (sections == null) { + return Arrays.toString(stats); + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0, count = sections.size(); i < count; i++) { + Section section = sections.get(i); + if (section.length == 0) { + continue; + } + + if (section.optional) { + boolean nonZero = false; + for (int offset = 0; offset < section.length; offset++) { + if (stats[section.position + offset] != 0) { + nonZero = true; + break; + } + } + if (!nonZero) { + continue; + } + } + + if (!sb.isEmpty()) { + sb.append(' '); + } + sb.append(section.label).append(": "); + if (section.length != 1) { + sb.append('['); + } + for (int offset = 0; offset < section.length; offset++) { + if (offset != 0) { + sb.append(", "); + } + if (section.typePower) { + sb.append(BatteryStats.formatCharge( + stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER)); + } else { + sb.append(stats[section.position + offset]); + } + } + if (section.length != 1) { + sb.append(']'); + } + } + return sb.toString(); + } + } } diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index 97ce96ec30f6..1dcd893eb143 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -1908,12 +1908,16 @@ public class ParsingPackageUtils { } else if (parser.getName().equals("package")) { final TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestQueriesPackage); - final String packageName = sa.getNonConfigurationString( - R.styleable.AndroidManifestQueriesPackage_name, 0); - if (TextUtils.isEmpty(packageName)) { - return input.error("Package name is missing from package tag."); + try { + final String packageName = sa.getNonConfigurationString( + R.styleable.AndroidManifestQueriesPackage_name, 0); + if (TextUtils.isEmpty(packageName)) { + return input.error("Package name is missing from package tag."); + } + pkg.addQueriesPackage(packageName.intern()); + } finally { + sa.recycle(); } - pkg.addQueriesPackage(packageName.intern()); } else if (parser.getName().equals("provider")) { final TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestQueriesProvider); diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index ee33eb4f014b..37b72880dd0c 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -131,8 +131,13 @@ public class PerfettoProtoLogImpl implements IProtoLog { Runnable cacheUpdater ) { Producer.init(InitArguments.DEFAULTS); - mDataSource.register(new DataSourceParams( - DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + DataSourceParams + .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .build(); + mDataSource.register(params); this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; this.mViewerConfigReader = viewerConfigReader; this.mLogGroups = logGroups; @@ -186,8 +191,6 @@ public class PerfettoProtoLogImpl implements IProtoLog { } os.end(outProtologViewerConfigToken); - - ctx.flush(); } catch (IOException e) { Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e); } diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java new file mode 100644 index 000000000000..1340156321b2 --- /dev/null +++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.ravenwood; + +/** + * Class to interact with the Ravenwood environment. + */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass +public class RavenwoodEnvironment { + private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment(); + + private RavenwoodEnvironment() { + } + + /** + * @return the singleton instance. + */ + public static RavenwoodEnvironment getInstance() { + return sInstance; + } + + /** + * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment. + * + * <p>Using this allows code to behave differently on a real device and on Ravenwood, but + * generally speaking, that's a bad idea because we want the test target code to behave + * differently. + * + * <p>This should be only used when different behavior is absolutely needed. + * + * <p>If someone needs it without having access to the SDK, the following hack would work too. + * <code>System.getProperty("java.class.path").contains("ravenwood")</code> + */ + @android.ravenwood.annotation.RavenwoodReplace + public boolean isRunningOnRavenwood() { + return false; + } + + public boolean isRunningOnRavenwood$ravenwood() { + return true; + } +} diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index e07acac52f2c..3d8237ea5389 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -16,6 +16,8 @@ package com.android.internal.widget; +import static android.widget.flags.Flags.messagingChildRequestLayout; + import android.annotation.Nullable; import android.annotation.Px; import android.content.Context; @@ -92,6 +94,10 @@ public class MessagingLinearLayout extends ViewGroup { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.hide = true; + // Child always needs to be measured to calculate hide property correctly in onMeasure. + if (messagingChildRequestLayout()) { + child.requestLayout(); + } if (child instanceof MessagingChild) { MessagingChild messagingChild = (MessagingChild) child; // Whenever we encounter the message first, it's always first in the layout diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 4031b2fd335c..0f4615a12ea2 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -16,18 +16,27 @@ package com.android.internal.widget; +import android.annotation.Nullable; import android.app.Flags; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.util.AttributeSet; +import android.view.RemotableViewMethod; import android.widget.RemoteViews; -import androidx.annotation.Nullable; - /** * An image view that holds the icon displayed on the left side of a notification row. */ @RemoteViews.RemoteView public class NotificationRowIconView extends CachingIconView { + private boolean mApplyCircularCrop = false; + public NotificationRowIconView(Context context) { super(context); } @@ -57,4 +66,82 @@ public class NotificationRowIconView extends CachingIconView { super.onFinishInflate(); } + + @Nullable + @Override + Drawable loadSizeRestrictedIcon(@Nullable Icon icon) { + final Drawable original = super.loadSizeRestrictedIcon(icon); + final Drawable result; + if (mApplyCircularCrop) { + result = makeCircularDrawable(original); + } else { + result = original; + } + + return result; + } + + /** + * Enables circle crop that makes given image circular + */ + @RemotableViewMethod(asyncImpl = "setApplyCircularCropAsync") + public void setApplyCircularCrop(boolean applyCircularCrop) { + mApplyCircularCrop = applyCircularCrop; + } + + /** + * Async version of {@link NotificationRowIconView#setApplyCircularCrop} + */ + public Runnable setApplyCircularCropAsync(boolean applyCircularCrop) { + mApplyCircularCrop = applyCircularCrop; + return () -> { + }; + } + + @Nullable + private Drawable makeCircularDrawable(@Nullable Drawable original) { + if (original == null) { + return original; + } + + final Bitmap source = drawableToBitmap(original); + + int size = Math.min(source.getWidth(), source.getHeight()); + + Bitmap squared = Bitmap.createScaledBitmap(source, size, size, /* filter= */ false); + Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + + final Canvas canvas = new Canvas(result); + final Paint paint = new Paint(); + paint.setShader( + new BitmapShader(squared, BitmapShader.TileMode.CLAMP, + BitmapShader.TileMode.CLAMP)); + paint.setAntiAlias(true); + float radius = size / 2f; + canvas.drawCircle(radius, radius, radius, paint); + return new BitmapDrawable(getResources(), result); + } + + private static Bitmap drawableToBitmap(Drawable drawable) { + if (drawable instanceof BitmapDrawable bitmapDrawable) { + final Bitmap bitmap = bitmapDrawable.getBitmap(); + if (bitmap.getConfig() == Bitmap.Config.HARDWARE) { + return bitmap.copy(Bitmap.Config.ARGB_8888, false); + } else { + return bitmap; + } + } + + int width = drawable.getIntrinsicWidth(); + width = width > 0 ? width : 1; + int height = drawable.getIntrinsicHeight(); + height = height > 0 ? height : 1; + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + return bitmap; + } } diff --git a/core/java/com/android/internal/widget/TEST_MAPPING b/core/java/com/android/internal/widget/TEST_MAPPING new file mode 100644 index 000000000000..91cecfda7a16 --- /dev/null +++ b/core/java/com/android/internal/widget/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + // v2/sysui/suite/test-mapping-sysui-screenshot-test + "sysui-screenshot-test": [ + { + "name": "SystemUIGoogleScreenshotTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.Postsubmit" + } + ] + } + ] +}
\ No newline at end of file diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp index f82ebfe8c947..17129d8913d7 100644 --- a/core/jni/android_tracing_PerfettoDataSource.cpp +++ b/core/jni/android_tracing_PerfettoDataSource.cpp @@ -244,8 +244,8 @@ static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy)); } -void nativeFlush(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) { - ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p)", (void*)ds_ptr); +void nativeWritePackets(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) { + ALOG(LOG_DEBUG, LOG_TAG, "nativeWritePackets(%p)", (void*)ds_ptr); sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ds_ptr); datasource->WritePackets(env, packets); } @@ -256,10 +256,12 @@ void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) { } void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr, - jint buffer_exhausted_policy) { + jint buffer_exhausted_policy, jboolean will_notify_on_stop, + jboolean no_flush) { sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr); struct PerfettoDsParams params = PerfettoDsParamsDefault(); + params.will_notify_on_stop = will_notify_on_stop; params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy; params.user_arg = reinterpret_cast<void*>(datasource.get()); @@ -325,13 +327,15 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr, datasource_instance->onStart(env); }; - params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx, - struct PerfettoDsOnFlushArgs*) { - JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + if (!no_flush) { + params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, + void* inst_ctx, struct PerfettoDsOnFlushArgs*) { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); - auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx); - datasource_instance->onFlush(env); - }; + auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx); + datasource_instance->onFlush(env); + }; + } params.on_stop_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, void* user_arg, void* inst_ctx, struct PerfettoDsOnStopArgs*) { @@ -422,7 +426,7 @@ const JNINativeMethod gMethods[] = { (void*)nativeCreate}, {"nativeFlushAll", "(J)V", (void*)nativeFlushAll}, {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer}, - {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource}, + {"nativeRegisterDataSource", "(JIZZ)V", (void*)nativeRegisterDataSource}, {"nativeGetPerfettoInstanceLocked", "(JI)Landroid/tracing/perfetto/DataSourceInstance;", (void*)nativeGetPerfettoInstanceLocked}, {"nativeReleasePerfettoInstanceLocked", "(JI)V", @@ -431,11 +435,12 @@ const JNINativeMethod gMethods[] = { {"nativePerfettoDsTraceIterateBegin", "(J)Z", (void*)nativePerfettoDsTraceIterateBegin}, {"nativePerfettoDsTraceIterateNext", "(J)Z", (void*)nativePerfettoDsTraceIterateNext}, {"nativePerfettoDsTraceIterateBreak", "(J)V", (void*)nativePerfettoDsTraceIterateBreak}, - {"nativeGetPerfettoDsInstanceIndex", "(J)I", (void*)nativeGetPerfettoDsInstanceIndex}}; + {"nativeGetPerfettoDsInstanceIndex", "(J)I", (void*)nativeGetPerfettoDsInstanceIndex}, + + {"nativeWritePackets", "(J[[B)V", (void*)nativeWritePackets}}; const JNINativeMethod gMethodsTracingContext[] = { /* name, signature, funcPtr */ - {"nativeFlush", "(J[[B)V", (void*)nativeFlush}, {"nativeGetCustomTls", "(J)Ljava/lang/Object;", (void*)nativeGetCustomTls}, {"nativeGetIncrementalState", "(J)Ljava/lang/Object;", (void*)nativeGetIncrementalState}, {"nativeSetCustomTls", "(JLjava/lang/Object;)V", (void*)nativeSetCustomTls}, diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index c92435f61ab1..654d83c827c9 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -607,7 +607,7 @@ message ImeInsetsSourceProviderProto { optional InsetsSourceProviderProto insets_source_provider = 1; optional WindowStateProto ime_target_from_ime = 2; - optional bool is_ime_layout_drawn = 3; + optional bool is_ime_layout_drawn = 3 [deprecated=true]; } message BackNavigationProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 70d923b8a9bb..8541704ecfb9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -299,6 +299,9 @@ <protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" /> + <protected-broadcast android:name="android.hardware.hdmi.action.OSD_MESSAGE" /> + <protected-broadcast android:name="android.hardware.hdmi.action.ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI" /> + <protected-broadcast android:name="android.hardware.usb.action.USB_STATE" /> <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" /> <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED" /> diff --git a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml new file mode 100644 index 000000000000..3b288d7038e1 --- /dev/null +++ b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_header_height" + android:clipChildren="false" + android:tag="compactMessagingHUN" + android:gravity="center_vertical" + android:theme="@style/Theme.DeviceDefault.Notification" + android:importantForAccessibility="no"> + <com.android.internal.widget.NotificationRowIconView + android:id="@+id/icon" + android:layout_width="@dimen/notification_icon_circle_size" + android:layout_height="@dimen/notification_icon_circle_size" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="@dimen/notification_icon_circle_start" + android:background="@drawable/notification_icon_circle" + android:padding="@dimen/notification_icon_circle_padding" + android:maxDrawableWidth="@dimen/notification_icon_circle_size" + android:maxDrawableHeight="@dimen/notification_icon_circle_size" + /> + <com.android.internal.widget.NotificationRowIconView + android:id="@+id/conversation_icon" + android:layout_width="@dimen/notification_icon_circle_size" + android:layout_height="@dimen/notification_icon_circle_size" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="@dimen/notification_icon_circle_start" + android:maxDrawableWidth="@dimen/notification_icon_circle_size" + android:maxDrawableHeight="@dimen/notification_icon_circle_size" + android:scaleType="centerCrop" + android:importantForAccessibility="no" + /> + <FrameLayout + android:id="@+id/alternate_expand_target" + android:layout_width="@dimen/notification_content_margin_start" + android:layout_height="match_parent" + android:layout_gravity="start" + android:importantForAccessibility="no" + android:focusable="false" + /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/notification_content_margin_start" + android:orientation="horizontal" + > + <NotificationTopLineView + android:id="@+id/notification_top_line" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_centerVertical="true" + android:layout_weight="1" + android:clipChildren="false" + android:gravity="center_vertical" + android:theme="@style/Theme.DeviceDefault.Notification" + > + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:ellipsize="end" + android:fadingEdge="horizontal" + android:singleLine="true" + android:textAlignment="viewStart" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" + /> + <include layout="@layout/notification_top_line_views" /> + </NotificationTopLineView> + <FrameLayout + android:id="@+id/reply_action_container" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_action_list_height" + android:gravity="center_vertical" + android:orientation="horizontal" /> + <FrameLayout + android:id="@+id/expand_button_touch_container" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="@dimen/notification_content_margin_end" + > + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + /> + </FrameLayout> + </LinearLayout> +</FrameLayout> diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml index 2c35c9b888cf..78299ab0ea99 100644 --- a/core/res/res/layout/side_fps_toast.xml +++ b/core/res/res/layout/side_fps_toast.xml @@ -25,6 +25,7 @@ android:layout_height="wrap_content" android:layout_width="0dp" android:layout_weight="6" + android:paddingBottom="10dp" android:text="@string/fp_power_button_enrollment_title" android:textColor="@color/side_fps_text_color" android:paddingLeft="20dp"/> @@ -36,6 +37,7 @@ android:layout_height="wrap_content" android:layout_width="0dp" android:layout_weight="3" + android:paddingBottom="10dp" android:text="@string/fp_power_button_enrollment_button_text" style="?android:attr/buttonBarNegativeButtonStyle" android:textColor="@color/side_fps_button_color" diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index 31acd9af164c..52662149b23a 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -93,4 +93,7 @@ <!-- If this is true, allow wake from theater mode from motion. --> <bool name="config_allowTheaterModeWakeFromMotion">true</bool> + + <!-- True if the device supports system decorations on secondary displays. --> + <bool name="config_supportsSystemDecorsOnSecondaryDisplays">false</bool> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5bd20332f381..f5b45660c547 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4723,6 +4723,8 @@ <!-- The broadcast intent name for notifying when the on-device model has been unloaded --> <string name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" translatable="false"></string> + <!-- The DeviceConfig namespace for the default system on-device sandboxed inference service. --> + <string name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace" translatable="false"></string> <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for wearable sensing. --> diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml index 8d9736273145..80cf0881e8cc 100644 --- a/core/res/res/values/config_battery_stats.xml +++ b/core/res/res/values/config_battery_stats.xml @@ -27,16 +27,18 @@ <!-- Whether to reset Battery Stats on unplug if the battery was significantly charged --> <bool name="config_batteryStatsResetOnUnplugAfterSignificantCharge">true</bool> - <!-- CPU power stats collection throttle period in milliseconds. Since power stats collection - is a relatively expensive operation, this throttle period may need to be adjusted for low-power - devices--> - <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer> - - <!-- Mobile Radio power stats collection throttle period in milliseconds. --> - <integer name="config_defaultPowerStatsThrottlePeriodMobileRadio">3600000</integer> - - <!-- Mobile Radio power stats collection throttle period in milliseconds. --> - <integer name="config_defaultPowerStatsThrottlePeriodWifi">3600000</integer> + <!-- Power stats collection throttle periods in milliseconds. Since power stats collection + is a relatively expensive operation, these throttle period may need to be adjusted for low-power + devices. + + The syntax of this config string is as follows: + <pre> + power-component-name1:throttle-period-millis1 power-component-name2:throttle-period-ms2 ... + </pre> + Use "*" for the power-component-name to represent the default for all power components + not mentioned by name. + --> + <string name="config_powerStatsThrottlePeriods">cpu:60000 *:300000</string> <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power stats aggregation procedure is performed and the results stored in PowerStatsStore. --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 59e4161b2e0b..28678c1c5fb0 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -794,6 +794,9 @@ <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] --> <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string> + <!-- Text for inline reply button for compact conversation heads ups --> + <string name="notification_compact_heads_up_reply">Reply</string> + <!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen --> <string name="notification_hidden_text">New notification</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d004b050fae8..a6bb8d7e28e1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2353,6 +2353,7 @@ <java-symbol type="layout" name="notification_template_material_base" /> <java-symbol type="layout" name="notification_template_material_heads_up_base" /> <java-symbol type="layout" name="notification_template_material_compact_heads_up_base" /> + <java-symbol type="layout" name="notification_template_material_messaging_compact_heads_up" /> <java-symbol type="layout" name="notification_template_material_big_base" /> <java-symbol type="layout" name="notification_template_material_big_picture" /> <java-symbol type="layout" name="notification_template_material_inbox" /> @@ -3628,6 +3629,7 @@ <java-symbol type="drawable" name="lockscreen_selected" /> <java-symbol type="string" name="notification_header_divider_symbol_with_spaces" /> + <java-symbol type="string" name="notification_compact_heads_up_reply" /> <java-symbol type="color" name="notification_primary_text_color_light" /> <java-symbol type="color" name="notification_primary_text_color_dark" /> @@ -3945,6 +3947,7 @@ <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" /> <java-symbol type="string" name="config_onDeviceIntelligenceModelLoadedBroadcastKey" /> <java-symbol type="string" name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" /> + <java-symbol type="string" name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace" /> <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> @@ -4522,6 +4525,7 @@ <java-symbol type="id" name="expand_button_container" /> <java-symbol type="id" name="expand_button_a11y_container" /> <java-symbol type="id" name="expand_button_touch_container" /> + <java-symbol type="id" name="reply_action_container" /> <java-symbol type="id" name="messaging_group_content_container" /> <java-symbol type="id" name="expand_button_and_content_container" /> <java-symbol type="id" name="conversation_header" /> @@ -5240,9 +5244,7 @@ <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" /> <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" /> - <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" /> - <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodMobileRadio" /> - <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodWifi" /> + <java-symbol type="string" name="config_powerStatsThrottlePeriods" /> <java-symbol type="integer" name="config_powerStatsAggregationPeriod" /> <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 436ba15235c9..eb3c84a3ba0c 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -260,6 +260,7 @@ android_ravenwood_test { "src/com/android/internal/os/**/*.java", "src/com/android/internal/util/**/*.java", "src/com/android/internal/power/EnergyConsumerStatsTest.java", + "src/com/android/internal/ravenwood/**/*.java", // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests, // to avoid having a dependency to FrameworksCoreTests. diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index b6f4429aec8a..ee1d1e1b975c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -65,6 +66,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.RejectedExecutionException; import java.util.function.BiConsumer; /** @@ -221,4 +223,17 @@ public class ClientTransactionListenerControllerTest { 123 /* newDisplayId */, true /* shouldReportConfigChange*/); inOrder.verify(mController).onContextConfigurationPostChanged(context); } + + @Test + public void testDisplayListenerHandlerClosed() { + doReturn(123).when(mActivity).getDisplayId(); + doThrow(new RejectedExecutionException()).when(mController).onDisplayChanged(123); + + mController.onContextConfigurationPreChanged(mActivity); + mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200)); + mController.onContextConfigurationPostChanged(mActivity); + + // No crash + verify(mController).onDisplayChanged(123); + } } diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 2a4ca79d997e..57cb1586bcd0 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -18,6 +18,7 @@ package android.net; import android.content.ContentUris; import android.os.Parcel; +import android.platform.test.annotations.AsbSecurityTest; import androidx.test.filters.SmallTest; @@ -86,6 +87,16 @@ public class UriTest extends TestCase { assertNull(u.getHost()); } + @AsbSecurityTest(cveBugId = 261721900) + @SmallTest + public void testSchemeSanitization() { + Uri uri = new Uri.Builder() + .scheme("http://https://evil.com:/te:st/") + .authority("google.com").path("one/way").build(); + assertEquals("httphttpsevil.com:/te:st/", uri.getScheme()); + assertEquals("httphttpsevil.com:/te:st/://google.com/one/way", uri.toString()); + } + @SmallTest public void testStringUri() { assertEquals("bob lee", diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java index 6ae3d6597934..df9a89e07404 100644 --- a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java +++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java @@ -674,8 +674,6 @@ public class DataSourceTest { protoOutputStream.write(SINGLE_INT, singleIntValue); protoOutputStream.end(payloadToken); protoOutputStream.end(forTestingToken); - - ctx.flush(); }), (args) -> {} ); diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java index 57bbb1cc9b57..5917cc181b47 100644 --- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java +++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java @@ -37,6 +37,7 @@ import static org.testng.Assert.assertEquals; import android.content.Context; import android.graphics.Insets; import android.platform.test.annotations.Presubmit; +import android.util.SparseArray; import android.view.animation.BackGestureInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputMethodManager; @@ -54,6 +55,8 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.lang.reflect.Field; + /** * Tests for {@link ImeBackAnimationController}. * @@ -104,6 +107,13 @@ public class ImeBackAnimationControllerTest { when(mInsetsController.getHost()).thenReturn(mViewRootInsetsControllerHost); when(mViewRootInsetsControllerHost.getInputMethodManager()).thenReturn( inputMethodManager); + try { + Field field = InsetsController.class.getDeclaredField("mSourceConsumers"); + field.setAccessible(true); + field.set(mInsetsController, new SparseArray<InsetsSourceConsumer>()); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Unable to set mSourceConsumers", e); + } }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index bc0ae9f31904..0b1b40c8ba8b 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -623,28 +623,6 @@ public class ViewFrameRateTest { assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory()); } - @Test - @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, - FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY - }) - public void idleDetected() throws Throwable { - waitForFrameRateCategoryToSettle(); - mActivityRule.runOnUiThread(() -> { - mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH); - mMovingView.setFrameContentVelocity(Float.MAX_VALUE); - mMovingView.invalidate(); - runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH, - mViewRoot.getLastPreferredFrameRateCategory())); - }); - waitForAfterDraw(); - - // Wait for idle timeout - Thread.sleep(500); - assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); - assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, - mViewRoot.getLastPreferredFrameRateCategory()); - } - private void runAfterDraw(@NonNull Runnable runnable) { Handler handler = new Handler(Looper.getMainLooper()); mAfterDrawLatch = new CountDownLatch(1); diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java new file mode 100644 index 000000000000..4eccbe5f1dc4 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.content.res; + +import static com.android.internal.content.om.OverlayConfigParser.SysPropWrapper; + +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.content.om.OverlayConfigParser; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class OverlayConfigParserTest { + @Test(expected = IllegalStateException.class) + public void testMergePropNotRoProp() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("${persist.value}/path", sysProp); + } + + @Test(expected = IllegalStateException.class) + public void testMergePropMissingEndBracket() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("${ro.value/path", sysProp); + } + + @Test(expected = IllegalStateException.class) + public void testMergeOnlyPropStart() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("path/${", sysProp); + } + + @Test(expected = IllegalStateException.class) + public void testMergePropInProp() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("path/${${ro.value}}", sysProp); + } + + /** + * The path is only allowed to contain one property. + */ + @Test(expected = IllegalStateException.class) + public void testMergePropMultipleProps() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("${ro.value}/path${ro.value2}/path", sysProp); + } + + @Test + public void testMergePropOneProp() { + final SysPropWrapper sysProp = p -> { + if ("ro.value".equals(p)) { + return "dummy_value"; + } else { + return "invalid"; + } + }; + + // Property in the beginnig of the string + String result = OverlayConfigParser.expandProperty("${ro.value}/path", + sysProp); + assertEquals("dummy_value/path", result); + + // Property in the middle of the string + result = OverlayConfigParser.expandProperty("path/${ro.value}/file", + sysProp); + assertEquals("path/dummy_value/file", result); + + // Property at the of the string + result = OverlayConfigParser.expandProperty("path/${ro.value}", + sysProp); + assertEquals("path/dummy_value", result); + + // Property is the entire string + result = OverlayConfigParser.expandProperty("${ro.value}", + sysProp); + assertEquals("dummy_value", result); + } + + @Test + public void testMergePropNoProp() { + final SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + + final String path = "no_props/path"; + String result = OverlayConfigParser.expandProperty(path, sysProp); + assertEquals(path, result); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java index baab3b218746..4846ed27af22 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java @@ -22,6 +22,7 @@ import android.os.BatteryConsumer; import android.os.Parcel; import android.os.PersistableBundle; import android.platform.test.ravenwood.RavenwoodRule; +import android.util.IndentingPrintWriter; import android.util.SparseArray; import android.util.Xml; @@ -31,6 +32,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.google.common.truth.StringSubject; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -38,6 +41,7 @@ import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; @RunWith(AndroidJUnit4.class) @@ -56,6 +60,9 @@ public class PowerStatsTest { extras.putBoolean("hasPowerMonitor", true); SparseArray<String> stateLabels = new SparseArray<>(); stateLabels.put(0x0F, "idle"); + extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, "device:0[3]"); + extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, "state:0"); + extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, "a:0 b:1"); mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, stateLabels, 1, 2, extras); mRegistry.register(mDescriptor); @@ -191,4 +198,68 @@ public class PowerStatsTest { newParcel.setDataPosition(0); return newParcel; } + + @Test + public void formatForBatteryHistory() { + PowerStats stats = new PowerStats(mDescriptor); + stats.durationMs = 1234; + stats.stats[0] = 10; + stats.stats[1] = 20; + stats.stats[2] = 30; + stats.stateStats.put(0x0F, new long[]{16}); + stats.stateStats.put(0xF0, new long[]{17}); + stats.uidStats.put(42, new long[]{40, 50}); + stats.uidStats.put(99, new long[]{60, 70}); + + assertThat(stats.formatForBatteryHistory(" #")) + .isEqualTo("duration=1234 cpu=" + + "device: [10, 20, 30]" + + " (idle) state: 16" + + " (cpu-f0) state: 17" + + " #42: a: 40 b: 50" + + " #99: a: 60 b: 70"); + } + + @Test + public void dump() { + PowerStats stats = new PowerStats(mDescriptor); + stats.durationMs = 1234; + stats.stats[0] = 10; + stats.stats[1] = 20; + stats.stats[2] = 30; + stats.stateStats.put(0x0F, new long[]{16}); + stats.stateStats.put(0xF0, new long[]{17}); + stats.uidStats.put(42, new long[]{40, 50}); + stats.uidStats.put(99, new long[]{60, 70}); + + StringWriter sw = new StringWriter(); + IndentingPrintWriter pw = new IndentingPrintWriter(sw); + stats.dump(pw); + pw.flush(); + String dump = sw.toString(); + + assertThat(dump).contains("duration=1234"); + assertThat(dump).contains("device: [10, 20, 30]"); + assertThat(dump).contains("(idle) state: 16"); + assertThat(dump).contains("(cpu-f0) state: 17"); + assertThat(dump).contains("UID 42: a: 40 b: 50"); + assertThat(dump).contains("UID 99: a: 60 b: 70"); + } + + @Test + public void formatter() { + assertThatFormatted(new long[]{12, 34, 56}, "a:0 b:1[2]") + .isEqualTo("a: 12 b: [34, 56]"); + assertThatFormatted(new long[]{12, 0, 0}, "a:0? b:1[2]?") + .isEqualTo("a: 12"); + assertThatFormatted(new long[]{0, 34, 56}, "a:0? b:1[2]?") + .isEqualTo("b: [34, 56]"); + assertThatFormatted(new long[]{3141592, 2000000, 1414213}, "pi:0p sqrt:1[2]p") + .isEqualTo("pi: 3.14 sqrt: [2.00, 1.41]"); + } + + private static StringSubject assertThatFormatted(long[] stats, String format) { + return assertThat(new PowerStats.PowerStatsFormatter(format) + .format(stats)); + } } diff --git a/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java new file mode 100644 index 000000000000..d1ef61b2e365 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.ravenwood; + +import static junit.framework.TestCase.assertEquals; + +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RavenwoodEnvironmentTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test + public void testIsRunningOnRavenwood() { + assertEquals(RavenwoodRule.isUnderRavenwood(), + RavenwoodEnvironment.getInstance().isRunningOnRavenwood()); + } +} diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb Binary files differindex 000f6ef46c2c..b41a607e91c7 100644 --- a/data/etc/core.protolog.pb +++ b/data/etc/core.protolog.pb diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 01deb4957cd3..b93cd468118a 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -583,6 +583,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "7211222997110112110": { + "message": "Refreshing activity for freeform camera compatibility treatment, activityRecord=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_STATES", + "at": "com\/android\/server\/wm\/ActivityRefresher.java" + }, "1665699123574159131": { "message": "Starting activity when config will change = %b", "level": "VERBOSE", @@ -1771,12 +1777,6 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java" }, - "-7756685416834187936": { - "message": "Refreshing activity for camera compatibility treatment, activityRecord=%s", - "level": "VERBOSE", - "group": "WM_DEBUG_STATES", - "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java" - }, "-5176775281239247368": { "message": "Reverting orientation after camera compat force rotation", "level": "VERBOSE", diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml index ac1b06495832..a4ee82544b37 100644 --- a/data/fonts/font_fallback_cjkvf.xml +++ b/data/fonts/font_fallback_cjkvf.xml @@ -768,7 +768,7 @@ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> </family> <family lang="zh-Hans"> - <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin" + <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular" supportedAxes="wght"> NotoSansCJK-Regular.ttc <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400 @@ -780,7 +780,7 @@ </font> </family> <family lang="zh-Hant,zh-Bopo"> - <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin" + <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular" supportedAxes="wght"> NotoSansCJK-Regular.ttc <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400 @@ -792,7 +792,7 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin" + <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular" supportedAxes="wght"> NotoSansCJK-Regular.ttc <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400 @@ -810,7 +810,7 @@ </font> </family> <family lang="ko"> - <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin" + <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular" supportedAxes="wght"> NotoSansCJK-Regular.ttc <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400 diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml index 9545ae718574..8cbc3000c250 100644 --- a/data/fonts/fonts_cjkvf.xml +++ b/data/fonts/fonts_cjkvf.xml @@ -1409,39 +1409,39 @@ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> </family> <family lang="zh-Hans"> - <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="100"/> </font> - <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="200"/> </font> - <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="300"/> </font> - <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="700"/> </font> - <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="800"/> </font> - <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="900"/> </font> @@ -1450,39 +1450,39 @@ </font> </family> <family lang="zh-Hant,zh-Bopo"> - <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="100"/> </font> - <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="200"/> </font> - <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="300"/> </font> - <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="700"/> </font> - <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="800"/> </font> - <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="900"/> </font> @@ -1491,39 +1491,39 @@ </font> </family> <family lang="ja"> - <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="100"/> </font> - <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="200"/> </font> - <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="300"/> </font> - <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="700"/> </font> - <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="800"/> </font> - <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="900"/> </font> @@ -1542,39 +1542,39 @@ </font> </family> <family lang="ko"> - <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="100"/> </font> - <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="200"/> </font> - <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="300"/> </font> - <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="700"/> </font> - <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="800"/> </font> - <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="900"/> </font> diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl index e8b4104a33bb..f8d3bffbe00b 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -438,9 +438,15 @@ key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP FALLBACK_USAGE_MAPPING key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN FALLBACK_USAGE_MAPPING key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING +key usage 0x0c00D9 EMOJI_PICKER FALLBACK_USAGE_MAPPING key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING +key usage 0x0c019F SETTINGS FALLBACK_USAGE_MAPPING key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING +key usage 0x0c0227 REFRESH FALLBACK_USAGE_MAPPING +key usage 0x0c029D LANGUAGE_SWITCH FALLBACK_USAGE_MAPPING +key usage 0x0c029F RECENT_APPS FALLBACK_USAGE_MAPPING +key usage 0x0c02A2 ALL_APPS FALLBACK_USAGE_MAPPING key usage 0x0d0044 STYLUS_BUTTON_PRIMARY FALLBACK_USAGE_MAPPING key usage 0x0d005a STYLUS_BUTTON_SECONDARY FALLBACK_USAGE_MAPPING diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index d915b746e0cc..1c2014183bb7 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -143,7 +143,7 @@ public class BitmapFactory { * the decoder will try to pick the best matching config based on the * system's screen depth, and characteristics of the original image such * as if it has per-pixel alpha (requiring a config that also does). - * + * * Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by * default. */ @@ -183,7 +183,7 @@ public class BitmapFactory { /** * If true (which is the default), the resulting bitmap will have its - * color channels pre-multipled by the alpha channel. + * color channels pre-multiplied by the alpha channel. * * <p>This should NOT be set to false for images to be directly drawn by * the view system or through a {@link Canvas}. The view system and @@ -221,9 +221,9 @@ public class BitmapFactory { * if {@link #inScaled} is set (which it is by default} and this * density does not match {@link #inTargetDensity}, then the bitmap * will be scaled to the target density before being returned. - * + * * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, + * {@link BitmapFactory#decodeResource(Resources, int)}, * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, * and {@link BitmapFactory#decodeResourceStream} * will fill in the density associated with the resource. The other @@ -242,29 +242,29 @@ public class BitmapFactory { * This is used in conjunction with {@link #inDensity} and * {@link #inScaled} to determine if and how to scale the bitmap before * returning it. - * + * * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, + * {@link BitmapFactory#decodeResource(Resources, int)}, * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, * and {@link BitmapFactory#decodeResourceStream} * will fill in the density associated the Resources object's * DisplayMetrics. The other * functions will leave it as-is and no scaling for density will be * performed. - * + * * @see #inDensity * @see #inScreenDensity * @see #inScaled * @see android.util.DisplayMetrics#densityDpi */ public int inTargetDensity; - + /** * The pixel density of the actual screen that is being used. This is * purely for applications running in density compatibility code, where * {@link #inTargetDensity} is actually the density the application * sees rather than the real screen density. - * + * * <p>By setting this, you * allow the loading code to avoid scaling a bitmap that is currently * in the screen density up/down to the compatibility density. Instead, @@ -274,18 +274,18 @@ public class BitmapFactory { * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight * Bitmap.getScaledHeight} to account for any different between the * bitmap's density and the target's density. - * + * * <p>This is never set automatically for the caller by * {@link BitmapFactory} itself. It must be explicitly set, since the * caller must deal with the resulting bitmap in a density-aware way. - * + * * @see #inDensity * @see #inTargetDensity * @see #inScaled * @see android.util.DisplayMetrics#densityDpi */ public int inScreenDensity; - + /** * When this flag is set, if {@link #inDensity} and * {@link #inTargetDensity} are not 0, the @@ -345,7 +345,7 @@ public class BitmapFactory { * ignored. * * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this - * field works in conjuction with inPurgeable. If inPurgeable is false, + * field works in conjunction with inPurgeable. If inPurgeable is false, * then this field is ignored. If inPurgeable is true, then this field * determines whether the bitmap can share a reference to the input * data (inputstream, array, etc.) or if it must make a deep copy. @@ -583,11 +583,11 @@ public class BitmapFactory { opts.inDensity = density; } } - + if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } - + return decodeStream(is, pad, opts); } @@ -611,8 +611,8 @@ public class BitmapFactory { public static Bitmap decodeResource(Resources res, int id, Options opts) { validate(opts); Bitmap bm = null; - InputStream is = null; - + InputStream is = null; + try { final TypedValue value = new TypedValue(); is = res.openRawResource(id, value); diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index e03a1daf7202..0b3e5456d81c 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -55,7 +55,7 @@ import java.lang.annotation.RetentionPolicy; * Canvas and Drawables</a> developer guide.</p></div> */ public class Canvas extends BaseCanvas { - private static int sCompatiblityVersion = 0; + private static int sCompatibilityVersion = 0; private static boolean sCompatibilityRestore = false; private static boolean sCompatibilitySetBitmap = false; @@ -74,7 +74,7 @@ public class Canvas extends BaseCanvas { // Maximum bitmap size as defined in Skia's native code // (see SkCanvas.cpp, SkDraw.cpp) - private static final int MAXMIMUM_BITMAP_SIZE = 32766; + private static final int MAXIMUM_BITMAP_SIZE = 32766; // Use a Holder to allow static initialization of Canvas in the boot image. private static class NoImagePreloadHolder { @@ -331,7 +331,7 @@ public class Canvas extends BaseCanvas { * @see #getMaximumBitmapHeight() */ public int getMaximumBitmapWidth() { - return MAXMIMUM_BITMAP_SIZE; + return MAXIMUM_BITMAP_SIZE; } /** @@ -342,7 +342,7 @@ public class Canvas extends BaseCanvas { * @see #getMaximumBitmapWidth() */ public int getMaximumBitmapHeight() { - return MAXMIMUM_BITMAP_SIZE; + return MAXIMUM_BITMAP_SIZE; } // the SAVE_FLAG constants must match their native equivalents @@ -423,7 +423,7 @@ public class Canvas extends BaseCanvas { public static final int ALL_SAVE_FLAG = 0x1F; private static void checkValidSaveFlags(int saveFlags) { - if (sCompatiblityVersion >= Build.VERSION_CODES.P + if (sCompatibilityVersion >= Build.VERSION_CODES.P && saveFlags != ALL_SAVE_FLAG) { throw new IllegalArgumentException( "Invalid Layer Save Flag - only ALL_SAVE_FLAGS is allowed"); @@ -845,7 +845,7 @@ public class Canvas extends BaseCanvas { } private static void checkValidClipOp(@NonNull Region.Op op) { - if (sCompatiblityVersion >= Build.VERSION_CODES.P + if (sCompatibilityVersion >= Build.VERSION_CODES.P && op != Region.Op.INTERSECT && op != Region.Op.DIFFERENCE) { throw new IllegalArgumentException( "Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed"); @@ -1435,7 +1435,7 @@ public class Canvas extends BaseCanvas { } /*package*/ static void setCompatibilityVersion(int apiLevel) { - sCompatiblityVersion = apiLevel; + sCompatibilityVersion = apiLevel; sCompatibilityRestore = apiLevel < Build.VERSION_CODES.M; sCompatibilitySetBitmap = apiLevel < Build.VERSION_CODES.O; nSetCompatibilityVersion(apiLevel); diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java index 0f2f8797b896..c1edafc16274 100644 --- a/graphics/java/android/graphics/Color.java +++ b/graphics/java/android/graphics/Color.java @@ -289,6 +289,9 @@ import java.util.function.DoubleUnaryOperator; */ @AnyThread @SuppressAutoDoc +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodClassLoadHook( + android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK) public class Color { @ColorInt public static final int BLACK = 0xFF000000; @ColorInt public static final int DKGRAY = 0xFF444444; diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index a2319a53a659..4bc3ecebb067 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -135,6 +135,9 @@ import java.util.function.DoubleUnaryOperator; @AnyThread @SuppressWarnings("StaticInitializerReferencesSubClass") @SuppressAutoDoc +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodClassLoadHook( + android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK) public abstract class ColorSpace { /** * Standard CIE 1931 2° illuminant A, encoded in xyY. @@ -2490,9 +2493,16 @@ public abstract class ColorSpace { return mNativePtr; } - private static native long nativeGetNativeFinalizer(); - private static native long nativeCreate(float a, float b, float c, float d, - float e, float f, float g, float[] xyz); + /** + * These methods can't be put in the Rgb class directly, because ColorSpace's + * static initializer instantiates Rgb, whose constructor needs them, which is a variation + * of b/337329128. + */ + static class Native { + static native long nativeGetNativeFinalizer(); + static native long nativeCreate(float a, float b, float c, float d, + float e, float f, float g, float[] xyz); + } private static DoubleUnaryOperator generateOETF(TransferParameters function) { if (function.isHLGish()) { @@ -2959,7 +2969,7 @@ public abstract class ColorSpace { // This mimics the old code that was in native. float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform); - mNativePtr = nativeCreate((float) mTransferParameters.a, + mNativePtr = Native.nativeCreate((float) mTransferParameters.a, (float) mTransferParameters.b, (float) mTransferParameters.c, (float) mTransferParameters.d, @@ -2975,7 +2985,7 @@ public abstract class ColorSpace { private static class NoImagePreloadHolder { public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0); + ColorSpace.Rgb.class.getClassLoader(), Native.nativeGetNativeFinalizer(), 0); } /** diff --git a/graphics/java/android/graphics/ComposePathEffect.java b/graphics/java/android/graphics/ComposePathEffect.java index 3fc9eb5aea15..7d59ecea948e 100644 --- a/graphics/java/android/graphics/ComposePathEffect.java +++ b/graphics/java/android/graphics/ComposePathEffect.java @@ -20,13 +20,13 @@ public class ComposePathEffect extends PathEffect { /** * Construct a PathEffect whose effect is to apply first the inner effect - * and the the outer pathEffect (e.g. outer(inner(path))). + * and the outer pathEffect (e.g. outer(inner(path))). */ public ComposePathEffect(PathEffect outerpe, PathEffect innerpe) { native_instance = nativeCreate(outerpe.native_instance, innerpe.native_instance); } - + private static native long nativeCreate(long nativeOuterpe, long nativeInnerpe); } diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index c86b744dbafa..88f0e8ef8a94 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -219,7 +219,7 @@ public class FontFamily { @CriticalNative private static native long nGetFamilyReleaseFunc(); - // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font. + // By passing -1 to weight argument, the weight value is resolved by OS/2 table in the font. // By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font. private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic); diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 17c2dd94cd36..13c4a94cb9b6 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -127,7 +127,7 @@ public class FontListParser { parser.setInput(is, null); parser.nextTag(); return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap, - lastModifiedDate, configVersion, false /* filter out the non-exising files */); + lastModifiedDate, configVersion, false /* filter out the non-existing files */); } } @@ -254,7 +254,7 @@ public class FontListParser { * @param parser An XML parser. * @param fontDir a font directory name. * @param updatableFontMap a updated font file map. - * @param allowNonExistingFile true to allow font file that doesn't exists + * @param allowNonExistingFile true to allow font file that doesn't exist. * @return a FontFamily instance. null if no font files are available in this FontFamily. */ public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir, diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java index b3615ff60bce..8f1282897780 100644 --- a/graphics/java/android/graphics/FrameInfo.java +++ b/graphics/java/android/graphics/FrameInfo.java @@ -24,7 +24,7 @@ import java.lang.annotation.RetentionPolicy; /** * Class that contains all the timing information for the current frame. This * is used in conjunction with the hardware renderer to provide - * continous-monitoring jank events + * continuous-monitoring jank events * * All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime() * diff --git a/graphics/java/android/graphics/Interpolator.java b/graphics/java/android/graphics/Interpolator.java index 994fb2dc31bc..28f296d3621d 100644 --- a/graphics/java/android/graphics/Interpolator.java +++ b/graphics/java/android/graphics/Interpolator.java @@ -28,13 +28,13 @@ public class Interpolator { mFrameCount = 2; native_instance = nativeConstructor(valueCount, 2); } - + public Interpolator(int valueCount, int frameCount) { mValueCount = valueCount; mFrameCount = frameCount; native_instance = nativeConstructor(valueCount, frameCount); } - + /** * Reset the Interpolator to have the specified number of values and an * implicit keyFrame count of 2 (just a start and end). After this call the @@ -43,7 +43,7 @@ public class Interpolator { public void reset(int valueCount) { reset(valueCount, 2); } - + /** * Reset the Interpolator to have the specified number of values and * keyFrames. After this call the values for each keyFrame must be assigned @@ -54,20 +54,20 @@ public class Interpolator { mFrameCount = frameCount; nativeReset(native_instance, valueCount, frameCount); } - + public final int getKeyFrameCount() { return mFrameCount; } - + public final int getValueCount() { return mValueCount; } - + /** * Assign the keyFrame (specified by index) a time value and an array of key - * values (with an implicity blend array of [0, 0, 1, 1] giving linear + * values (with an implicitly blend array of [0, 0, 1, 1] giving linear * transition to the next set of key values). - * + * * @param index The index of the key frame to assign * @param msec The time (in mililiseconds) for this key frame. Based on the * SystemClock.uptimeMillis() clock @@ -80,7 +80,7 @@ public class Interpolator { /** * Assign the keyFrame (specified by index) a time value and an array of key * values and blend array. - * + * * @param index The index of the key frame to assign * @param msec The time (in mililiseconds) for this key frame. Based on the * SystemClock.uptimeMillis() clock @@ -99,7 +99,7 @@ public class Interpolator { } nativeSetKeyFrame(native_instance, index, msec, values, blend); } - + /** * Set a repeat count (which may be fractional) for the interpolator, and * whether the interpolator should mirror its repeats. The default settings @@ -110,7 +110,7 @@ public class Interpolator { nativeSetRepeatMirror(native_instance, repeatCount, mirror); } } - + public enum Result { NORMAL, FREEZE_START, @@ -130,7 +130,7 @@ public class Interpolator { * return whether the specified time was within the range of key times * (NORMAL), was before the first key time (FREEZE_START) or after the last * key time (FREEZE_END). In any event, computed values are always returned. - * + * * @param msec The time (in milliseconds) used to sample into the * Interpolator. Based on the SystemClock.uptimeMillis() clock * @param values Where to write the computed values (may be NULL). @@ -146,13 +146,13 @@ public class Interpolator { default: return Result.FREEZE_END; } } - + @Override protected void finalize() throws Throwable { nativeDestructor(native_instance); native_instance = 0; // Other finalizers can still call us. } - + private int mValueCount; private int mFrameCount; private long native_instance; diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java index 0aa6f1282c1a..fe73a1a70b9c 100644 --- a/graphics/java/android/graphics/LightingColorFilter.java +++ b/graphics/java/android/graphics/LightingColorFilter.java @@ -16,8 +16,8 @@ // This file was generated from the C++ include file: SkColorFilter.h // Any changes made to this file will be discarded by the build. -// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, -// or one of the auxilary file specifications in device/tools/gluemaker. +// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, +// or one of the auxiliary file specifications in device/tools/gluemaker. package android.graphics; diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java index 56d912b1eada..087937144b97 100644 --- a/graphics/java/android/graphics/LinearGradient.java +++ b/graphics/java/android/graphics/LinearGradient.java @@ -63,7 +63,7 @@ public class LinearGradient extends Shader { * @param colors The sRGB colors to be distributed along the gradient line * @param positions May be null. The relative positions [0..1] of * each corresponding color in the colors array. If this is null, - * the the colors are distributed evenly along the gradient line. + * the colors are distributed evenly along the gradient line. * @param tile The Shader tiling mode */ public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int[] colors, @@ -82,7 +82,7 @@ public class LinearGradient extends Shader { * @param colors The colors to be distributed along the gradient line * @param positions May be null. The relative positions [0..1] of * each corresponding color in the colors array. If this is null, - * the the colors are distributed evenly along the gradient line. + * the colors are distributed evenly along the gradient line. * @param tile The Shader tiling mode * * @throws IllegalArgumentException if there are less than two colors, the colors do diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java index fbb690c0b012..748e9dd98e10 100644 --- a/graphics/java/android/graphics/Matrix.java +++ b/graphics/java/android/graphics/Matrix.java @@ -232,7 +232,7 @@ public class Matrix { private static class NoImagePreloadHolder { public static final NativeAllocationRegistry sRegistry = NativeAllocationRegistry.createMalloced( - Matrix.class.getClassLoader(), nGetNativeFinalizerWrapper()); + Matrix.class.getClassLoader(), ExtraNatives.nGetNativeFinalizer()); } private final long native_instance; @@ -241,7 +241,7 @@ public class Matrix { * Create an identity matrix */ public Matrix() { - native_instance = nCreateWrapper(0); + native_instance = ExtraNatives.nCreate(0); NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance); } @@ -251,7 +251,7 @@ public class Matrix { * @param src The matrix to copy into this matrix */ public Matrix(Matrix src) { - native_instance = nCreateWrapper(src != null ? src.native_instance : 0); + native_instance = ExtraNatives.nCreate(src != null ? src.native_instance : 0); NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance); } @@ -562,7 +562,7 @@ public class Matrix { /** * Set the matrix to the scale and translate values that map the source rectangle to the - * destination rectangle, returning true if the the result can be represented. + * destination rectangle, returning true if the result can be represented. * * @param src the source rectangle to map from. * @param dst the destination rectangle to map to. @@ -849,40 +849,6 @@ public class Matrix { return native_instance; } - /** - * Wrapper method we use to switch to ExtraNatives.nCreate(src) only on Ravenwood. - * - * @see ExtraNatives - */ - @android.ravenwood.annotation.RavenwoodReplace - private static long nCreateWrapper(long src) { - return nCreate(src); - } - - private static long nCreateWrapper$ravenwood(long src) { - return ExtraNatives.nCreate(src); - } - - /** - * Wrapper method we use to switch to ExtraNatives.nGetNativeFinalizer(src) only on Ravenwood. - * - * @see ExtraNatives - */ - @android.ravenwood.annotation.RavenwoodReplace - private static long nGetNativeFinalizerWrapper() { - return nGetNativeFinalizer(); - } - - private static long nGetNativeFinalizerWrapper$ravenwood() { - return ExtraNatives.nGetNativeFinalizer(); - } - - // ------------------ Regular JNI ------------------------ - - private static native long nCreate(long nSrc_or_zero); - private static native long nGetNativeFinalizer(); - - // ------------------ Fast JNI ------------------------ @FastNative @@ -982,14 +948,6 @@ public class Matrix { * There are two methods that are called by the static initializers (either directly or * indirectly) in this class, namely nCreate() and nGetNativeFinalizer(). On Ravenwood * these methods can't be on the Matrix class itself, so we use a nested class to host them. - * - * We still keep the original nCreate() method and call it on non-ravenwood environment, - * in order to avoid problems in downstream (such as Android Studio). - * - * @see #nCreateWrapper(long) - * @see #nGetNativeFinalizerWrapper() - * - * TODO(b/337110712) Clean it up somehow. (remove the original nCreate() and unify the code?) */ private static class ExtraNatives { static native long nCreate(long nSrc_or_zero); diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index a4bce9eb5e88..6be8332e784b 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -271,7 +271,7 @@ public class Mesh { * not have a uniform with that name or if the uniform is declared with a type other than int * or int[1] then an IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param value value corresponding to the int uniform with the given name. */ public void setIntUniform(@NonNull String uniformName, int value) { @@ -283,7 +283,7 @@ public class Mesh { * not have a uniform with that name or if the uniform is declared with a type other than ivec2 * or int[2] then an IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param value1 first value corresponding to the int uniform with the given name. * @param value2 second value corresponding to the int uniform with the given name. */ @@ -296,7 +296,7 @@ public class Mesh { * not have a uniform with that name or if the uniform is declared with a type other than ivec3 * or int[3] then an IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param value1 first value corresponding to the int uniform with the given name. * @param value2 second value corresponding to the int uniform with the given name. * @param value3 third value corresponding to the int uniform with the given name. @@ -310,7 +310,7 @@ public class Mesh { * not have a uniform with that name or if the uniform is declared with a type other than ivec4 * or int[4] then an IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param value1 first value corresponding to the int uniform with the given name. * @param value2 second value corresponding to the int uniform with the given name. * @param value3 third value corresponding to the int uniform with the given name. @@ -327,7 +327,7 @@ public class Mesh { * int (for N=1), ivecN, or int[N], where N is the length of the values param, then an * IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param values int values corresponding to the vec4 int uniform with the given name. */ public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) { diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index af2095713ec2..382269f74366 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -22,12 +22,12 @@ import android.compat.annotation.UnsupportedAppUsage; * The NinePatch class permits drawing a bitmap in nine or more sections. * Essentially, it allows the creation of custom graphics that will scale the * way that you define, when content added within the image exceeds the normal - * bounds of the graphic. For a thorough explanation of a NinePatch image, - * read the discussion in the + * bounds of the graphic. For a thorough explanation of a NinePatch image, + * read the discussion in the * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">2D * Graphics</a> document. * <p> - * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a> + * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a> * tool offers an extremely handy way to create your NinePatch images, * using a WYSIWYG graphics editor. * </p> @@ -104,7 +104,7 @@ public class NinePatch { this(bitmap, chunk, null); } - /** + /** * Create a drawable projection from a bitmap to nine patches. * * @param bitmap The bitmap describing the patches. @@ -122,7 +122,7 @@ public class NinePatch { protected void finalize() throws Throwable { try { if (mNativeChunk != 0) { - // only attempt to destroy correctly initilized chunks + // only attempt to destroy correctly initialized chunks nativeFinalize(mNativeChunk); mNativeChunk = 0; } @@ -169,8 +169,8 @@ public class NinePatch { public Bitmap getBitmap() { return mBitmap; } - - /** + + /** * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}. * * @param canvas A container for the current matrix and clip used to draw the NinePatch. @@ -180,7 +180,7 @@ public class NinePatch { canvas.drawPatch(this, location, mPaint); } - /** + /** * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}. * * @param canvas A container for the current matrix and clip used to draw the NinePatch. @@ -190,7 +190,7 @@ public class NinePatch { canvas.drawPatch(this, location, mPaint); } - /** + /** * Draws the NinePatch. This method will ignore the paint returned * by {@link #getPaint()} and use the specified paint instead. * diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java index 5500c5217de4..2c6cfa5c2e3d 100644 --- a/graphics/java/android/graphics/PathMeasure.java +++ b/graphics/java/android/graphics/PathMeasure.java @@ -25,14 +25,14 @@ public class PathMeasure { * setPath. * * Note that once a path is associated with the measure object, it is - * undefined if the path is subsequently modified and the the measure object + * undefined if the path is subsequently modified and the measure object * is used. If the path is modified, you must call setPath with the path. */ public PathMeasure() { mPath = null; native_instance = native_create(0, false); } - + /** * Create a PathMeasure object associated with the specified path object * (already created and specified). The measure object can now return the @@ -40,7 +40,7 @@ public class PathMeasure { * path. * * Note that once a path is associated with the measure object, it is - * undefined if the path is subsequently modified and the the measure object + * undefined if the path is subsequently modified and the measure object * is used. If the path is modified, you must call setPath with the path. * * @param path The path that will be measured by this object @@ -121,7 +121,7 @@ public class PathMeasure { * such as <code>dst.rLineTo(0, 0)</code>.</p> */ public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) { - // Skia used to enforce this as part of it's API, but has since relaxed that restriction + // Skia used to enforce this as part of its API, but has since relaxed that restriction // so to maintain consistency in our API we enforce the preconditions here. float length = getLength(); if (startD < 0) { diff --git a/graphics/java/android/graphics/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java index 29d82fa1d98b..575095426563 100644 --- a/graphics/java/android/graphics/Rasterizer.java +++ b/graphics/java/android/graphics/Rasterizer.java @@ -16,8 +16,8 @@ // This file was generated from the C++ include file: SkRasterizer.h // Any changes made to this file will be discarded by the build. -// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, -// or one of the auxilary file specifications in device/tools/gluemaker. +// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, +// or one of the auxiliary file specifications in device/tools/gluemaker. package android.graphics; diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index 411a10b85bd2..5211e3a75d09 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -165,7 +165,7 @@ public final class Rect implements Parcelable { public String toShortString() { return toShortString(new StringBuilder(32)); } - + /** * Return a string representation of the rectangle in a compact form. * @hide @@ -184,7 +184,7 @@ public final class Rect implements Parcelable { * * <p>You can later recover the Rect from this string through * {@link #unflattenFromString(String)}. - * + * * @return Returns a new String of the form "left top right bottom" */ @NonNull @@ -314,7 +314,7 @@ public final class Rect implements Parcelable { public final int height() { return bottom - top; } - + /** * @return the horizontal center of the rectangle. If the computed value * is fractional, this method returns the largest integer that is @@ -323,7 +323,7 @@ public final class Rect implements Parcelable { public final int centerX() { return (left + right) >> 1; } - + /** * @return the vertical center of the rectangle. If the computed value * is fractional, this method returns the largest integer that is @@ -332,14 +332,14 @@ public final class Rect implements Parcelable { public final int centerY() { return (top + bottom) >> 1; } - + /** * @return the exact horizontal center of the rectangle as a float. */ public final float exactCenterX() { return (left + right) * 0.5f; } - + /** * @return the exact vertical center of the rectangle as a float. */ @@ -493,7 +493,7 @@ public final class Rect implements Parcelable { * @param top The top of the rectangle being tested for containment * @param right The right side of the rectangle being tested for containment * @param bottom The bottom of the rectangle being tested for containment - * @return true iff the the 4 specified sides of a rectangle are inside or + * @return true iff the 4 specified sides of a rectangle are inside or * equal to this rectangle */ public boolean contains(int left, int top, int right, int bottom) { @@ -548,7 +548,7 @@ public final class Rect implements Parcelable { } return false; } - + /** * If the specified rectangle intersects this rectangle, return true and set * this rectangle to that intersection, otherwise return false and do not @@ -670,7 +670,7 @@ public final class Rect implements Parcelable { public void union(@NonNull Rect r) { union(r.left, r.top, r.right, r.bottom); } - + /** * Update this Rect to enclose itself and the [x,y] coordinate. There is no * check to see that this rectangle is non-empty. diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java index ff50a0c55e6c..5b9b7641111b 100644 --- a/graphics/java/android/graphics/RectF.java +++ b/graphics/java/android/graphics/RectF.java @@ -38,7 +38,7 @@ public class RectF implements Parcelable { public float top; public float right; public float bottom; - + /** * Create a new empty RectF. All coordinates are initialized to 0. */ @@ -78,7 +78,7 @@ public class RectF implements Parcelable { bottom = r.bottom; } } - + public RectF(@Nullable Rect r) { if (r == null) { left = top = right = bottom = 0.0f; @@ -121,7 +121,7 @@ public class RectF implements Parcelable { public String toShortString() { return toShortString(new StringBuilder(32)); } - + /** * Return a string representation of the rectangle in a compact form. * @hide @@ -134,7 +134,7 @@ public class RectF implements Parcelable { sb.append(','); sb.append(bottom); sb.append(']'); return sb.toString(); } - + /** * Print short representation to given writer. * @hide @@ -183,14 +183,14 @@ public class RectF implements Parcelable { public final float centerY() { return (top + bottom) * 0.5f; } - + /** * Set the rectangle to (0,0,0,0) */ public void setEmpty() { left = right = top = bottom = 0; } - + /** * Set the rectangle's coordinates to the specified values. Note: no range * checking is performed, so it is up to the caller to ensure that @@ -220,7 +220,7 @@ public class RectF implements Parcelable { this.right = src.right; this.bottom = src.bottom; } - + /** * Copy the coordinates from src into this rectangle. * @@ -261,7 +261,7 @@ public class RectF implements Parcelable { left = newLeft; top = newTop; } - + /** * Inset the rectangle by (dx,dy). If dx is positive, then the sides are * moved inwards, making the rectangle narrower. If dx is negative, then the @@ -293,7 +293,7 @@ public class RectF implements Parcelable { return left < right && top < bottom // check for empty first && x >= left && x < right && y >= top && y < bottom; } - + /** * Returns true iff the 4 specified sides of a rectangle are inside or equal * to this rectangle. i.e. is this rectangle a superset of the specified @@ -303,7 +303,7 @@ public class RectF implements Parcelable { * @param top The top of the rectangle being tested for containment * @param right The right side of the rectangle being tested for containment * @param bottom The bottom of the rectangle being tested for containment - * @return true iff the the 4 specified sides of a rectangle are inside or + * @return true iff the 4 specified sides of a rectangle are inside or * equal to this rectangle */ public boolean contains(float left, float top, float right, float bottom) { @@ -313,7 +313,7 @@ public class RectF implements Parcelable { && this.left <= left && this.top <= top && this.right >= right && this.bottom >= bottom; } - + /** * Returns true iff the specified rectangle r is inside or equal to this * rectangle. An empty rectangle never contains another rectangle. @@ -329,7 +329,7 @@ public class RectF implements Parcelable { && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom; } - + /** * If the rectangle specified by left,top,right,bottom intersects this * rectangle, return true and set this rectangle to that intersection, @@ -367,7 +367,7 @@ public class RectF implements Parcelable { } return false; } - + /** * If the specified rectangle intersects this rectangle, return true and set * this rectangle to that intersection, otherwise return false and do not @@ -382,7 +382,7 @@ public class RectF implements Parcelable { public boolean intersect(@NonNull RectF r) { return intersect(r.left, r.top, r.right, r.bottom); } - + /** * If rectangles a and b intersect, return true and set this rectangle to * that intersection, otherwise return false and do not change this @@ -406,7 +406,7 @@ public class RectF implements Parcelable { } return false; } - + /** * Returns true if this rectangle intersects the specified rectangle. * In no event is this rectangle modified. No check is performed to see @@ -426,7 +426,7 @@ public class RectF implements Parcelable { return this.left < right && left < this.right && this.top < bottom && top < this.bottom; } - + /** * Returns true iff the two specified rectangles intersect. In no event are * either of the rectangles modified. To record the intersection, @@ -441,7 +441,7 @@ public class RectF implements Parcelable { return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom; } - + /** * Set the dst integer Rect by rounding this rectangle's coordinates * to their nearest integer values. @@ -489,7 +489,7 @@ public class RectF implements Parcelable { } } } - + /** * Update this Rect to enclose itself and the specified rectangle. If the * specified rectangle is empty, nothing is done. If this rectangle is empty @@ -500,7 +500,7 @@ public class RectF implements Parcelable { public void union(@NonNull RectF r) { union(r.left, r.top, r.right, r.bottom); } - + /** * Update this Rect to enclose itself and the [x,y] coordinate. There is no * check to see that this rectangle is non-empty. @@ -520,7 +520,7 @@ public class RectF implements Parcelable { bottom = y; } } - + /** * Swap top/bottom or left/right if there are flipped (i.e. left > right * and/or top > bottom). This can be called if @@ -548,7 +548,7 @@ public class RectF implements Parcelable { public int describeContents() { return 0; } - + /** * Write this rectangle to the specified parcel. To restore a rectangle from * a parcel, use readFromParcel() @@ -561,7 +561,7 @@ public class RectF implements Parcelable { out.writeFloat(right); out.writeFloat(bottom); } - + public static final @android.annotation.NonNull Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() { /** * Return a new rectangle from the data in the specified parcel. @@ -572,7 +572,7 @@ public class RectF implements Parcelable { r.readFromParcel(in); return r; } - + /** * Return an array of rectangles of the specified size. */ @@ -581,7 +581,7 @@ public class RectF implements Parcelable { return new RectF[size]; } }; - + /** * Set the rectangle's coordinates from the data stored in the specified * parcel. To write a rectangle to a parcel, call writeToParcel(). diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 27325694073c..0650b7817729 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -765,12 +765,12 @@ public final class RenderNode { * Default value is false. See * {@link #setProjectBackwards(boolean)} for a description of what this entails. * - * @param shouldRecieve True if this RenderNode is a projection receiver, false otherwise. + * @param shouldReceive True if this RenderNode is a projection receiver, false otherwise. * Default is false. * @return True if the value changed, false if the new value was the same as the previous value. */ - public boolean setProjectionReceiver(boolean shouldRecieve) { - return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve); + public boolean setProjectionReceiver(boolean shouldReceive) { + return nSetProjectionReceiver(mNativeRenderNode, shouldReceive); } /** @@ -1799,7 +1799,7 @@ public final class RenderNode { private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject); @CriticalNative - private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve); + private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive); @CriticalNative private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top, diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 50b167ef9f46..3256f31bdc93 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -318,7 +318,7 @@ public class SurfaceTexture { } /** - * Releases the the texture content. This is needed in single buffered mode to allow the image + * Releases the texture content. This is needed in single buffered mode to allow the image * content producer to take ownership of the image buffer. * <p> * For more information see {@link #SurfaceTexture(int, boolean)}. @@ -431,7 +431,7 @@ public class SurfaceTexture { * error. * <p> * Note that while calling this method causes all the buffers to be freed - * from the perspective of the the SurfaceTexture, if there are additional + * from the perspective of the SurfaceTexture, if there are additional * references on the buffers (e.g. if a buffer is referenced by a client or * by OpenGL ES as a texture) then those buffer will remain allocated. * <p> diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 4c4e8fa9c088..fd788167a0d8 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -600,7 +600,7 @@ public class Typeface { * {@link #setWeight} and {@link #setItalic}. * * If {@link #setWeight} is not called, the fallback family keeps the default weight. - * Similary, if {@link #setItalic} is not called, the fallback family keeps the default + * Similarly, if {@link #setItalic} is not called, the fallback family keeps the default * italic information. For example, calling {@code builder.setFallback("sans-serif-light")} * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in * terms of fallback. The default weight and italic information are overridden by calling @@ -794,7 +794,7 @@ public class Typeface { /** * Returns the maximum capacity of custom fallback families. * - * This includes the the first font family passed to the constructor. + * This includes the first font family passed to the constructor. * It is guaranteed that the value will be greater than or equal to 64. * * @return the maximum number of font families for the custom fallback @@ -816,7 +816,7 @@ public class Typeface { /** * Sets a system fallback by name. * - * You can specify generic font familiy names or OEM specific family names. If the system + * You can specify generic font family names or OEM specific family names. If the system * don't have a specified fallback, the default fallback is used instead. * For more information about generic font families, see <a * href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a> diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java index 81769e2e21bf..6bb22a12280e 100644 --- a/graphics/java/android/graphics/Xfermode.java +++ b/graphics/java/android/graphics/Xfermode.java @@ -16,8 +16,8 @@ // This file was generated from the C++ include file: SkXfermode.h // Any changes made to this file will be discarded by the build. -// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, -// or one of the auxilary file specifications in device/tools/gluemaker. +// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, +// or one of the auxiliary file specifications in device/tools/gluemaker. package android.graphics; @@ -28,7 +28,7 @@ import android.os.Build; * Xfermode is the base class for objects that are called to implement custom * "transfer-modes" in the drawing pipeline. The static function Create(Modes) * can be called to return an instance of any of the predefined subclasses as - * specified in the Modes enum. When an Xfermode is assigned to an Paint, then + * specified in the Modes enum. When an Xfermode is assigned to a Paint, then * objects drawn with that paint have the xfermode applied. */ public class Xfermode { diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java index ce35b55d526f..b0c7f202f23a 100644 --- a/graphics/java/android/graphics/YuvImage.java +++ b/graphics/java/android/graphics/YuvImage.java @@ -63,7 +63,7 @@ public class YuvImage { private int mWidth; /** - * The height of the the image. + * The height of the image. */ private int mHeight; diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index 688425a77ab5..7ee7d6beec78 100644 --- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java +++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java @@ -98,7 +98,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback * extra content to reveal within the clip path when performing affine transformations on the * layers. * - * Each layers will reserve 25% of it's width and height. + * Each layers will reserve 25% of its width and height. * * As a result, the view port of the layers is smaller than their intrinsic width and height. */ diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 4972e928dd22..7f2feac2278d 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -839,7 +839,7 @@ public abstract class Drawable { } /** - * Describes the current state, as a union of primitve states, such as + * Describes the current state, as a union of primitive states, such as * {@link android.R.attr#state_focused}, * {@link android.R.attr#state_selected}, etc. * Some drawables may modify their imagery based on the selected state. diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 166a795e1661..29d033e64aea 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -936,7 +936,7 @@ public class GradientDrawable extends Drawable { } /** - * Retrn the inner radius of the ring + * Return the inner radius of the ring * * @see #setInnerRadius(int) * @attr ref android.R.styleable#GradientDrawable_innerRadius diff --git a/graphics/java/android/graphics/drawable/shapes/PathShape.java b/graphics/java/android/graphics/drawable/shapes/PathShape.java index 393fdee87bb8..299f6d5cddfa 100644 --- a/graphics/java/android/graphics/drawable/shapes/PathShape.java +++ b/graphics/java/android/graphics/drawable/shapes/PathShape.java @@ -93,7 +93,7 @@ public class PathShape extends Shape { && Float.compare(pathShape.mStdHeight, mStdHeight) == 0 && Float.compare(pathShape.mScaleX, mScaleX) == 0 && Float.compare(pathShape.mScaleY, mScaleY) == 0 - // Path does not have equals implementation but incase it gains one, use it here + // Path does not have equals implementation but in case it gains one, use it here && Objects.equals(mPath, pathShape.mPath); } diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java index 318aadda63fe..2893177aafc5 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -789,7 +789,7 @@ public final class Font { return false; } - // ByteBuffer#equals compares all bytes which is not performant for e.g HashMap. Since + // ByteBuffer#equals compares all bytes which is not performant for e.g. HashMap. Since // underlying native font object holds buffer address, check if this buffer points exactly // the same address as a shortcut of equality. For being compatible with of API30 or before, // check buffer position even if the buffer points the same address. diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java index ff38282255f2..abcafb666576 100644 --- a/graphics/java/android/graphics/fonts/FontFileUtil.java +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -34,7 +34,7 @@ import java.util.Set; */ public class FontFileUtil { - private FontFileUtil() {} // Do not instanciate + private FontFileUtil() {} // Do not instantiate /** * Unpack the weight value from packed integer. @@ -87,7 +87,7 @@ public class FontFileUtil { } if (weight != -1 && italic != -1) { - // Both weight/italic style are specifeid by variation settings. + // Both weight/italic style are specified by variation settings. // No need to look into OS/2 table. // TODO: Good to look HVAR table to check if this font supports wght/ital axes. return pack(weight, italic == 1); diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index a90961eb6d2e..f727f5b076a1 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -216,7 +216,7 @@ public final class SystemFonts { } else if (defaultFamily != null) { familyListSet.familyList.add(defaultFamily); } else { - // There is no valid for for default fallback. Ignore. + // There is no valid for default fallback. Ignore. } } } diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java index 0c6d4bd5da48..d8cf21e72441 100644 --- a/graphics/java/android/graphics/text/LineBreaker.java +++ b/graphics/java/android/graphics/text/LineBreaker.java @@ -376,8 +376,8 @@ public class LineBreaker { * @see LineBreaker#computeLineBreaks */ public static class Result { - // Following two contstant must be synced with minikin's line breaker. - // TODO(nona): Remove these constatns by introducing native methods. + // Following two constants must be synced with minikin's line breaker. + // TODO(nona): Remove these constants by introducing native methods. private static final int TAB_MASK = 0x20000000; private static final int HYPHEN_MASK = 0xFF; private static final int START_HYPHEN_MASK = 0x18; // 0b11000 diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java index f8328b13ca1f..671eb6e514c5 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -139,7 +139,7 @@ public final class PositionedGlyphs { * Returns the glyph ID used for drawing the glyph at the given index. * * @param index the glyph index - * @return An glyph ID of the font. + * @return A glyph ID of the font. */ @IntRange(from = 0) public int getGlyphId(@IntRange(from = 0) int index) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index e93b0bf1e69a..1fbaeeac8608 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -256,8 +256,10 @@ class DividerPresenter implements View.OnTouchListener { static Color getContainerBackgroundColor( @NonNull TaskFragmentContainer container, @NonNull Color defaultColor) { final Activity activity = container.getTopNonFinishingActivity(); - if (activity == null || !activity.isResumed()) { - // This can happen when the top activity in the container is from a different process. + if (activity == null) { + // This can happen when the activities in the container are from a different process. + // TODO(b/340984203) Report whether the top activity is in the same process. Use default + // color if not. return defaultColor; } @@ -515,8 +517,11 @@ class DividerPresenter implements View.OnTouchListener { private void onStartDragging() { mRenderer.mIsDragging = true; mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging); + mRenderer.updateSurface(); + + // Veil visibility change should be applied together with the surface boost transaction in + // the wct. final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mRenderer.updateSurface(t); mRenderer.showVeils(t); // Callbacks must be executed on the executor to release mLock and prevent deadlocks. @@ -532,18 +537,18 @@ class DividerPresenter implements View.OnTouchListener { @GuardedBy("mLock") private void onDrag() { - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mRenderer.updateSurface(t); - t.apply(); + mRenderer.updateSurface(); } @GuardedBy("mLock") private void onFinishDragging() { mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition); mRenderer.setDividerPosition(mDividerPosition); + mRenderer.updateSurface(); + // Veil visibility change should be applied together with the surface boost transaction in + // the wct. final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mRenderer.updateSurface(t); mRenderer.hideVeils(t); // Callbacks must be executed on the executor to release mLock and prevent deadlocks. @@ -994,6 +999,22 @@ class DividerPresenter implements View.OnTouchListener { * Updates the positions and crops of the divider surface and veil surfaces. This method * should be called when {@link #mProperties} is changed or while dragging to update the * position of the divider surface and the veil surfaces. + * + * This method applies the changes in a stand-alone surface transaction immediately. + */ + private void updateSurface() { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + updateSurface(t); + t.apply(); + } + + /** + * Updates the positions and crops of the divider surface and veil surfaces. This method + * should be called when {@link #mProperties} is changed or while dragging to update the + * position of the divider surface and the veil surfaces. + * + * This method applies the changes in the provided surface transaction and can be synced + * with other changes. */ private void updateSurface(@NonNull SurfaceControl.Transaction t) { final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index a23a47416fb0..f9a6caf42e6e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -21,6 +21,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED; import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; @@ -358,6 +359,13 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.addTaskFragmentOperation(fragmentToken, operation); } + void setTaskFragmentPinned(@NonNull WindowContainerTransaction wct, + @NonNull IBinder fragmentToken, boolean pinned) { + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_PINNED).setBooleanValue(pinned).build(); + wct.addTaskFragmentOperation(fragmentToken, operation); + } + void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, boolean dimOnTask) { final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 5b0e6b9c49a1..13c2d1f73461 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -350,8 +350,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Resets the isolated navigation and updates the container. final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); final WindowContainerTransaction wct = transactionRecord.getTransaction(); - mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin, - false /* isolated */); + mPresenter.setTaskFragmentPinned(wct, containerToUnpin, false /* pinned */); updateContainer(wct, containerToUnpin); transactionRecord.apply(false /* shouldApplyIndependently */); updateCallbackIfNecessary(); @@ -1078,8 +1077,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } - // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer. - if (container != null && container.isIsolatedNavigationEnabled()) { + if (container != null && container.shouldSkipActivityResolving()) { return true; } @@ -1535,8 +1533,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity( launchingActivity); if (taskFragmentContainer != null - && taskFragmentContainer.isIsolatedNavigationEnabled()) { - // Skip resolving if started from an isolated navigated TaskFragmentContainer. + && taskFragmentContainer.shouldSkipActivityResolving()) { return null; } if (isAssociatedWithOverlay(launchingActivity)) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 0e4fb3075c65..27048136afd8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -401,18 +401,26 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return; } - setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */); + setTaskFragmentPinned(wct, secondaryContainer, !isStacked /* pinned */); if (isStacked && !splitPinRule.isSticky()) { secondaryContainer.getTaskContainer().removeSplitPinContainer(); } } /** - * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer} + * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}. + * <p> + * If a container enables isolated navigation, activities can't be launched to this container + * unless explicitly requested to be launched to. + * + * @see TaskFragmentContainer#isOverlayWithActivityAssociation() */ void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, boolean isolatedNavigationEnabled) { + if (!Flags.activityEmbeddingOverlayPresentationFlag() && container.isOverlay()) { + return; + } if (container.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) { return; } @@ -422,6 +430,28 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } /** + * Sets whether to pin this {@link TaskFragmentContainer}. + * <p> + * If a container is pinned, it won't be chosen as the launch target unless it's the launching + * container. + * + * @see TaskFragmentContainer#isAlwaysOnTopOverlay() + * @see TaskContainer#getSplitPinContainer() + */ + void setTaskFragmentPinned(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer container, + boolean pinned) { + if (!Flags.activityEmbeddingOverlayPresentationFlag() && container.isOverlay()) { + return; + } + if (container.isPinned() == pinned) { + return; + } + container.setPinned(pinned); + setTaskFragmentPinned(wct, container.getTaskFragmentToken(), pinned); + } + + /** * Resizes the task fragment if it was already registered. Skips the operation if the container * creation has not been reported from the server yet. */ @@ -586,6 +616,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { super.setCompanionTaskFragment(wct, primary, secondary); } + /** + * Applies the {@code attributes} to a standalone {@code container}. + * + * @param minDimensions the minimum dimension of the container. + */ void applyActivityStackAttributes( @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, @@ -594,16 +629,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions, container); final boolean isFillParent = relativeBounds.isEmpty(); - // Note that we only set isolated navigation for overlay container without activity - // association. Activity will be launched to an expanded container on top of the overlay - // if the overlay is associated with an activity. Thus, an overlay with activity association - // will never be isolated navigated. - final boolean isIsolatedNavigated = container.isAlwaysOnTopOverlay() && !isFillParent; final boolean dimOnTask = !isFillParent - && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK - && Flags.fullscreenDimFlag(); + && Flags.fullscreenDimFlag() + && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK; final IBinder fragmentToken = container.getTaskFragmentToken(); + if (container.isAlwaysOnTopOverlay()) { + setTaskFragmentPinned(wct, container, !isFillParent); + } else if (container.isOverlayWithActivityAssociation()) { + setTaskFragmentIsolatedNavigation(wct, container, !isFillParent); + } + // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds // and WCT#setWindowingMode to take fragmentToken. resizeTaskFragmentIfRegistered(wct, container, relativeBounds); @@ -612,7 +648,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); // Always use default animation for standalone ActivityStack. updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); - setTaskFragmentIsolatedNavigation(wct, container, isIsolatedNavigated); setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index c952dfea4de5..482554351b97 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -184,6 +184,11 @@ class TaskFragmentContainer { private boolean mIsIsolatedNavigationEnabled; /** + * Whether this TaskFragment is pinned. + */ + private boolean mIsPinned; + + /** * Whether to apply dimming on the parent Task that was requested last. */ private boolean mLastDimOnTask; @@ -893,6 +898,34 @@ class TaskFragmentContainer { mIsIsolatedNavigationEnabled = isolatedNavigationEnabled; } + /** + * Returns whether this container is pinned. + * + * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED + */ + boolean isPinned() { + return mIsPinned; + } + + /** + * Sets whether to pin this container or not. + * + * @see #isPinned() + */ + void setPinned(boolean pinned) { + mIsPinned = pinned; + } + + /** + * Indicates to skip activity resolving if the activity is from this container. + * + * @see #isIsolatedNavigationEnabled() + * @see #isPinned() + */ + boolean shouldSkipActivityResolving() { + return isIsolatedNavigationEnabled() || isPinned(); + } + /** Sets whether to apply dim on the parent Task. */ void setLastDimOnTask(boolean lastDimOnTask) { mLastDimOnTask = lastDimOnTask; diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index ad913c98482c..b0a45e285896 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -631,15 +631,8 @@ public class DividerPresenterTest { assertEquals(defaultColor, DividerPresenter.getContainerBackgroundColor(container, defaultColor)); - // When the top non-finishing activity is not resumed, the default color should be returned. + // When the top non-finishing activity is non-null, its background color should be returned. when(container.getTopNonFinishingActivity()).thenReturn(activity); - when(activity.isResumed()).thenReturn(false); - assertEquals(defaultColor, - DividerPresenter.getContainerBackgroundColor(container, defaultColor)); - - // When the top non-finishing activity is resumed, its background color should be returned. - when(container.getTopNonFinishingActivity()).thenReturn(activity); - when(activity.isResumed()).thenReturn(true); assertEquals(activityBackgroundColor, DividerPresenter.getContainerBackgroundColor(container, defaultColor)); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 049a9e2c2aca..9ebcb759115d 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -188,6 +188,32 @@ public class OverlayPresentationTest { } @Test + public void testSetIsolatedNavigation_overlayFeatureDisabled_earlyReturn() { + mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG); + + final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test"); + + mSplitPresenter.setTaskFragmentIsolatedNavigation(mTransaction, container, + !container.isIsolatedNavigationEnabled()); + + verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(), + any(IBinder.class), anyBoolean()); + } + + @Test + public void testSetPinned_overlayFeatureDisabled_earlyReturn() { + mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG); + + final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test"); + + mSplitPresenter.setTaskFragmentPinned(mTransaction, container, + !container.isPinned()); + + verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), any(IBinder.class), + anyBoolean()); + } + + @Test public void testGetAllNonFinishingOverlayContainers() { assertThat(mSplitController.getAllNonFinishingOverlayContainers()).isEmpty(); @@ -608,8 +634,11 @@ public class OverlayPresentationTest { WINDOWING_MODE_UNDEFINED); verify(mSplitPresenter).updateAnimationParams(mTransaction, token, TaskFragmentAnimationParams.DEFAULT); - verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false); verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false); + verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), + any(TaskFragmentContainer.class), anyBoolean()); + verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(), + any(TaskFragmentContainer.class), anyBoolean()); } @Test @@ -630,9 +659,9 @@ public class OverlayPresentationTest { WINDOWING_MODE_MULTI_WINDOW); verify(mSplitPresenter).updateAnimationParams(mTransaction, token, TaskFragmentAnimationParams.DEFAULT); - // Set isolated navigation to false if the overlay container is associated with - // the launching activity. - verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false); + verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true); + verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), + any(TaskFragmentContainer.class), anyBoolean()); verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true); } @@ -655,10 +684,9 @@ public class OverlayPresentationTest { container, WINDOWING_MODE_MULTI_WINDOW); verify(mSplitPresenter).updateAnimationParams(mTransaction, token, TaskFragmentAnimationParams.DEFAULT); - // Set isolated navigation to false if the overlay container is associated with - // the launching activity. - verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, - container, true); + verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(), + any(TaskFragmentContainer.class), anyBoolean()); + verify(mSplitPresenter).setTaskFragmentPinned(mTransaction, container, true); verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true); } @@ -678,6 +706,8 @@ public class OverlayPresentationTest { verify(mSplitPresenter).updateAnimationParams(mTransaction, token, TaskFragmentAnimationParams.DEFAULT); verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false); + verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), + any(TaskFragmentContainer.class), anyBoolean()); verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index c677484f64f1..3fbce9ec31a5 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED; import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY; import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY; @@ -285,6 +286,28 @@ public class SplitPresenterTest { } @Test + public void testSetTaskFragmentPinned() { + final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + + // Verify the default. + assertFalse(container.isPinned()); + + mPresenter.setTaskFragmentPinned(mTransaction, container, true); + + final TaskFragmentOperation expectedOperation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_PINNED).setBooleanValue(true).build(); + verify(mTransaction).addTaskFragmentOperation(container.getTaskFragmentToken(), + expectedOperation); + assertTrue(container.isPinned()); + + // No request to set the same animation params. + clearInvocations(mTransaction); + mPresenter.setTaskFragmentPinned(mTransaction, container, true); + + verify(mTransaction, never()).addTaskFragmentOperation(any(), any()); + } + + @Test public void testGetMinDimensionsForIntent() { final Intent intent = new Intent(ApplicationProvider.getApplicationContext(), MinimumDimensionActivity.class); diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index fe68123513ca..8977d5cad780 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -71,3 +71,10 @@ flag { description: "Hides the bubble overflow if there aren't any overflowed bubbles" bug: "334175587" } + +flag { + name: "enable_retrievable_bubbles" + namespace: "multitasking" + description: "Allow opening bubbles overflow UI without bubbles being visible" + bug: "340337839" +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java index 5af4c3b0a716..bdd89c0e1ac9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.desktopmode; +package com.android.wm.shell.shared; import android.annotation.NonNull; import android.content.Context; @@ -127,7 +127,7 @@ public class DesktopModeStatus { /** * Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time. */ - static int getMaxTaskLimit() { + public static int getMaxTaskLimit() { return MAX_TASK_LIMIT; } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index dcd4062cb819..785e30d879d2 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -69,8 +69,12 @@ public class TransitionUtil { /** Returns {@code true} if the transition is opening or closing mode. */ public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) { - return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE - || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK; + return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK; + } + + /** Returns {@code true} if the transition is opening mode. */ + public static boolean isOpeningMode(@TransitionInfo.TransitionMode int mode) { + return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT; } /** Returns {@code true} if the transition has a display change. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 9e6c5fbf9f5e..38c344322a30 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -87,6 +87,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; @@ -1170,7 +1171,9 @@ public class BubbleController implements ConfigurationChangeListener, * @param bubbleKey key of the bubble being dragged */ public void startBubbleDrag(String bubbleKey) { - onBubbleDrag(bubbleKey, true /* isBeingDragged */); + if (mBubbleData.getSelectedBubble() != null) { + mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ false); + } if (mBubbleStateListener != null) { boolean overflow = BubbleOverflow.KEY.equals(bubbleKey); Rect rect = new Rect(); @@ -1183,23 +1186,29 @@ public class BubbleController implements ConfigurationChangeListener, } /** - * A bubble is no longer being dragged in Launcher. As was released in given location. + * A bubble is no longer being dragged in Launcher. And was released in given location. * Will be called only when bubble bar is expanded. * - * @param bubbleKey key of the bubble being dragged * @param location location where bubble was released */ - public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) { + public void stopBubbleDrag(BubbleBarLocation location) { mBubblePositioner.setBubbleBarLocation(location); - onBubbleDrag(bubbleKey, false /* isBeingDragged */); + if (mBubbleData.getSelectedBubble() != null) { + mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true); + } } - private void onBubbleDrag(String bubbleKey, boolean isBeingDragged) { - // TODO(b/330585402): collapse stack if any bubble is dragged - if (mBubbleData.getSelectedBubble() != null - && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) { - // Should collapse/expand only if equals to selected bubble. - mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged); + /** + * A bubble was dragged and is released in dismiss target in Launcher. + * + * @param bubbleKey key of the bubble being dragged to dismiss target + */ + public void dragBubbleToDismiss(String bubbleKey) { + String selectedBubbleKey = mBubbleData.getSelectedBubbleKey(); + removeBubble(bubbleKey, Bubbles.DISMISS_USER_GESTURE); + if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) { + // We did not remove the selected bubble. Expand it again + mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true); } } @@ -1858,7 +1867,11 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void bubbleOverflowChanged(boolean hasBubbles) { - // TODO (b/334175587): tell stack view to hide / show the overflow + if (Flags.enableOptionalBubbleOverflow()) { + if (mStackView != null) { + mStackView.showOverflow(hasBubbles); + } + } } }; @@ -2358,12 +2371,6 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void removeBubble(String key) { - mMainExecutor.execute( - () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE)); - } - - @Override public void removeAllBubbles() { mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE)); } @@ -2379,8 +2386,13 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) { - mMainExecutor.execute(() -> mController.stopBubbleDrag(bubbleKey, location)); + public void stopBubbleDrag(BubbleBarLocation location) { + mMainExecutor.execute(() -> mController.stopBubbleDrag(location)); + } + + @Override + public void dragBubbleToDismiss(String key) { + mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key)); } @Override @@ -2397,7 +2409,10 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void setBubbleBarBounds(Rect bubbleBarBounds) { - mMainExecutor.execute(() -> mBubblePositioner.setBubbleBarBounds(bubbleBarBounds)); + mMainExecutor.execute(() -> { + mBubblePositioner.setBubbleBarBounds(bubbleBarBounds); + if (mLayerView != null) mLayerView.updateExpandedView(); + }); } } @@ -2709,6 +2724,15 @@ public class BubbleController implements ConfigurationChangeListener, () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged( sensitiveNotificationProtectionActive)); } + + @Override + public boolean canShowBubbleNotification() { + // in bubble bar mode, when the IME is visible we can't animate new bubbles. + if (BubbleController.this.isShowingAsBubbleBar()) { + return !BubbleController.this.mBubblePositioner.getIsImeVisible(); + } + return true; + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index ea30af5c3d5a..26483c8428c6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -327,6 +327,14 @@ public class BubbleData { return mSelectedBubble; } + /** + * Returns the key of the selected bubble, or null if no bubble is selected. + */ + @Nullable + public String getSelectedBubbleKey() { + return mSelectedBubble != null ? mSelectedBubble.getKey() : null; + } + public BubbleOverflow getOverflow() { return mOverflow; } @@ -1228,9 +1236,7 @@ public class BubbleData { public void dump(PrintWriter pw) { pw.println("BubbleData state:"); pw.print(" selected: "); - pw.println(mSelectedBubble != null - ? mSelectedBubble.getKey() - : "null"); + pw.println(getSelectedBubbleKey()); pw.print(" expanded: "); pw.println(mExpanded); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java index 633b01bde4ca..18e04d14c71b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java @@ -44,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ContrastColorUtil; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; import java.util.ArrayList; @@ -195,7 +196,9 @@ public class BubbleOverflowContainerView extends LinearLayout { } void updateEmptyStateVisibility() { - mEmptyState.setVisibility(mOverflowBubbles.isEmpty() ? View.VISIBLE : View.GONE); + boolean showEmptyState = mOverflowBubbles.isEmpty() + && !Flags.enableOptionalBubbleOverflow(); + mEmptyState.setVisibility(showEmptyState ? View.VISIBLE : View.GONE); mRecyclerView.setVisibility(mOverflowBubbles.isEmpty() ? View.GONE : View.VISIBLE); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index c4bbe32e3205..a35a004cdace 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 @@ -324,6 +324,11 @@ public class BubblePositioner { return 0; } + /** Returns whether the IME is visible. */ + public boolean getIsImeVisible() { + return mImeVisible; + } + /** Sets whether the IME is visible. **/ public void setImeVisible(boolean visible, int height) { mImeVisible = visible; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index be88b3497000..9fabd4247670 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -80,6 +80,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.FrameworkStatsLog; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener; @@ -863,6 +864,7 @@ public class BubbleStackView extends FrameLayout } }; + private boolean mShowingOverflow; private BubbleOverflow mBubbleOverflow; private StackEducationView mStackEduView; private StackEducationView.Manager mStackEducationViewManager; @@ -992,18 +994,12 @@ public class BubbleStackView extends FrameLayout mBubbleOverflow = mBubbleData.getOverflow(); - resetOverflowView(); - mBubbleContainer.addView(mBubbleOverflow.getIconView(), - mBubbleContainer.getChildCount() /* index */, - new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), - mPositioner.getBubbleSize())); - updateOverflow(); - mBubbleOverflow.getIconView().setOnClickListener((View v) -> { - mBubbleData.setShowingOverflow(true); - mBubbleData.setSelectedBubble(mBubbleOverflow); - mBubbleData.setExpanded(true); - }); - + if (Flags.enableOptionalBubbleOverflow()) { + showOverflow(mBubbleData.hasOverflowBubbles()); + } else { + mShowingOverflow = true; // if the flags not on this is always true + setUpOverflow(); + } mScrim = new View(getContext()); mScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); mScrim.setBackgroundDrawable(new ColorDrawable( @@ -1220,6 +1216,19 @@ public class BubbleStackView extends FrameLayout } }; + private void setUpOverflow() { + resetOverflowView(); + mBubbleContainer.addView(mBubbleOverflow.getIconView(), + mBubbleContainer.getChildCount() /* index */, + new FrameLayout.LayoutParams(mBubbleSize, mBubbleSize)); + updateOverflow(); + mBubbleOverflow.getIconView().setOnClickListener((View v) -> { + mBubbleData.setShowingOverflow(true); + mBubbleData.setSelectedBubble(mBubbleOverflow); + mBubbleData.setExpanded(true); + }); + } + private void setUpDismissView() { if (mDismissView != null) { removeView(mDismissView); @@ -1458,24 +1467,56 @@ public class BubbleStackView extends FrameLayout b.getExpandedView().updateFontSize(); } } - if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) { + if (mShowingOverflow && mBubbleOverflow != null + && mBubbleOverflow.getExpandedView() != null) { mBubbleOverflow.getExpandedView().updateFontSize(); } } void updateLocale() { - if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) { + if (mShowingOverflow && mBubbleOverflow != null + && mBubbleOverflow.getExpandedView() != null) { mBubbleOverflow.getExpandedView().updateLocale(); } } private void updateOverflow() { mBubbleOverflow.update(); - mBubbleContainer.reorderView(mBubbleOverflow.getIconView(), - mBubbleContainer.getChildCount() - 1 /* index */); + if (mShowingOverflow) { + mBubbleContainer.reorderView(mBubbleOverflow.getIconView(), + mBubbleContainer.getChildCount() - 1 /* index */); + } updateOverflowVisibility(); } + private void updateOverflowVisibility() { + mBubbleOverflow.setVisible(mShowingOverflow + && (mIsExpanded || mBubbleData.isShowingOverflow()) + ? VISIBLE + : GONE); + } + + private void updateOverflowDotVisibility(boolean expanding) { + if (mShowingOverflow && mBubbleOverflow.showDot()) { + mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> { + mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE); + }); + } + } + + /** Sets whether the overflow should be visible or not. */ + public void showOverflow(boolean showOverflow) { + if (!Flags.enableOptionalBubbleOverflow()) return; + if (mShowingOverflow != showOverflow) { + mShowingOverflow = showOverflow; + if (showOverflow) { + setUpOverflow(); + } else if (mBubbleOverflow != null) { + resetOverflowView(); + } + } + } + /** * Handle theme changes. */ @@ -1535,7 +1576,10 @@ public class BubbleStackView extends FrameLayout b.getExpandedView().updateDimensions(); } } - mBubbleOverflow.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize)); + if (mShowingOverflow) { + mBubbleOverflow.getIconView().setLayoutParams( + new LayoutParams(mBubbleSize, mBubbleSize)); + } mExpandedAnimationController.updateResources(); mStackAnimationController.updateResources(); mDismissView.updateResources(); @@ -1699,7 +1743,7 @@ public class BubbleStackView extends FrameLayout bubble.getIconView().setContentDescription(getResources().getString( R.string.bubble_content_description_single, titleStr, appName)); } else { - final int moreCount = mBubbleContainer.getChildCount() - 1; + final int moreCount = getBubbleCount(); bubble.getIconView().setContentDescription(getResources().getString( R.string.bubble_content_description_stack, titleStr, appName, moreCount)); @@ -1752,7 +1796,8 @@ public class BubbleStackView extends FrameLayout View bubbleOverflowIconView = mBubbleOverflow != null ? mBubbleOverflow.getIconView() : null; - if (bubbleOverflowIconView != null && !mBubbleData.getBubbles().isEmpty()) { + if (mShowingOverflow && bubbleOverflowIconView != null + && !mBubbleData.getBubbles().isEmpty()) { Bubble lastBubble = mBubbleData.getBubbles().get(mBubbleData.getBubbles().size() - 1); View lastBubbleIconView = lastBubble.getIconView(); @@ -1928,20 +1973,6 @@ public class BubbleStackView extends FrameLayout } } - private void updateOverflowVisibility() { - mBubbleOverflow.setVisible((mIsExpanded || mBubbleData.isShowingOverflow()) - ? VISIBLE - : GONE); - } - - private void updateOverflowDotVisibility(boolean expanding) { - if (mBubbleOverflow.showDot()) { - mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> { - mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE); - }); - } - } - // via BubbleData.Listener void updateBubble(Bubble bubble) { animateInFlyoutForBubble(bubble); @@ -3428,8 +3459,9 @@ public class BubbleStackView extends FrameLayout * @return the number of bubbles in the stack view. */ public int getBubbleCount() { - // Subtract 1 for the overflow button that is always in the bubble container. - return mBubbleContainer.getChildCount() - 1; + final int childCount = mBubbleContainer.getChildCount(); + // Subtract 1 for the overflow button if it's showing. + return mShowingOverflow ? childCount - 1 : childCount; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 322088b17e63..1d053f9aab35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -297,6 +297,15 @@ public interface Bubbles { boolean sensitiveNotificationProtectionActive); /** + * Determines whether Bubbles can show notifications. + * + * <p>Normally bubble notifications are shown by Bubbles, but in some cases the bubble + * notification is suppressed and should be shown by the Notifications pipeline as regular + * notifications. + */ + boolean canShowBubbleNotification(); + + /** * A listener to be notified of bubble state changes, used by launcher to render bubbles in * its process. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 66f77fa6f76d..1eff149f2e91 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -33,7 +33,7 @@ interface IBubbles { oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3; - oneway void removeBubble(in String key) = 4; + oneway void dragBubbleToDismiss(in String key) = 4; oneway void removeAllBubbles() = 5; @@ -47,5 +47,5 @@ interface IBubbles { oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10; - oneway void stopBubbleDrag(in String key, in BubbleBarLocation location) = 11; + oneway void stopBubbleDrag(in BubbleBarLocation location) = 11; }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index a351cef223b5..123cc7e9d488 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -356,7 +356,7 @@ public class BubbleBarLayerView extends FrameLayout } /** Updates the expanded view size and position. */ - private void updateExpandedView() { + public void updateExpandedView() { if (mExpandedView == null || mExpandedBubble == null) return; boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); mPositioner.getBubbleBarExpandedViewBounds(mPositioner.isBubbleBarOnLeft(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index dba0a985411d..579a7943829e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -152,7 +152,8 @@ object PipUtils { "org.chromium.arc", 0) val isTv = AppGlobals.getPackageManager().hasSystemFeature( PackageManager.FEATURE_LEANBACK, 0) - isPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false) || + isPip2ExperimentEnabled = SystemProperties.getBoolean( + "persist.wm_shell.pip2", false) || (Flags.enablePip2Implementation() && !isArc && !isTv) } return isPip2ExperimentEnabled as Boolean diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 607a3b5423d1..2234041b8c9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -347,7 +347,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { if (mMoving) { final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos; mLastDraggingPosition = position; - mSplitLayout.updateDividerBounds(position); + mSplitLayout.updateDividerBounds(position, true /* shouldUseParallaxEffect */); } break; case MotionEvent.ACTION_UP: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 30eb8b5d2f05..de016d3ae400 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -31,7 +31,6 @@ import android.animation.ValueAnimator; import android.app.ActivityManager; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -57,7 +56,13 @@ import com.android.wm.shell.common.SurfaceUtils; import java.util.function.Consumer; /** - * Handles split decor like showing resizing hint for a specific split. + * Handles additional layers over a running task in a split pair, for example showing a veil with an + * app icon when the task is being resized (usually to hide weird layouts while the app is being + * stretched). One SplitDecorManager is initialized on each window. + * <br> + * Currently, we show a veil when: + * a) Task is resizing down from a fullscreen window. + * b) Task is being stretched past its original bounds. */ public class SplitDecorManager extends WindowlessWindowManager { private static final String TAG = SplitDecorManager.class.getSimpleName(); @@ -78,7 +83,11 @@ public class SplitDecorManager extends WindowlessWindowManager { private boolean mShown; private boolean mIsResizing; - private final Rect mOldBounds = new Rect(); + /** The original bounds of the main task, captured at the beginning of a resize transition. */ + private final Rect mOldMainBounds = new Rect(); + /** The original bounds of the side task, captured at the beginning of a resize transition. */ + private final Rect mOldSideBounds = new Rect(); + /** The current bounds of the main task, mid-resize. */ private final Rect mResizingBounds = new Rect(); private final Rect mTempRect = new Rect(); private ValueAnimator mFadeAnimator; @@ -184,29 +193,38 @@ public class SplitDecorManager extends WindowlessWindowManager { mResizingIconView = null; mIsResizing = false; mShown = false; - mOldBounds.setEmpty(); + mOldMainBounds.setEmpty(); + mOldSideBounds.setEmpty(); mResizingBounds.setEmpty(); } /** Showing resizing hint. */ public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, - boolean immediately) { + boolean immediately, float[] veilColor) { if (mResizingIconView == null) { return; } if (!mIsResizing) { mIsResizing = true; - mOldBounds.set(newBounds); + mOldMainBounds.set(newBounds); + mOldSideBounds.set(sideBounds); } mResizingBounds.set(newBounds); mOffsetX = offsetX; mOffsetY = offsetY; - final boolean show = - newBounds.width() > mOldBounds.width() || newBounds.height() > mOldBounds.height(); - final boolean update = show != mShown; + // Show a veil when: + // a) Task is resizing down from a fullscreen window. + // b) Task is being stretched past its original bounds. + final boolean isResizingDownFromFullscreen = + mOldSideBounds.width() <= 1 || mOldSideBounds.height() <= 1; + final boolean isStretchingPastOriginalBounds = + newBounds.width() > mOldMainBounds.width() + || newBounds.height() > mOldMainBounds.height(); + final boolean showVeil = isResizingDownFromFullscreen || isStretchingPastOriginalBounds; + final boolean update = showVeil != mShown; if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) { // If we need to animate and animator still running, cancel it before we ensure both // background and icon surfaces are non null for next animation. @@ -216,18 +234,18 @@ public class SplitDecorManager extends WindowlessWindowManager { if (mBackgroundLeash == null) { mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash, RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession); - t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask)) + t.setColor(mBackgroundLeash, veilColor) .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1); } if (mGapBackgroundLeash == null && !immediately) { final boolean isLandscape = newBounds.height() == sideBounds.height(); - final int left = isLandscape ? mOldBounds.width() : 0; - final int top = isLandscape ? 0 : mOldBounds.height(); + final int left = isLandscape ? mOldMainBounds.width() : 0; + final int top = isLandscape ? 0 : mOldMainBounds.height(); mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash, GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession); // Fill up another side bounds area. - t.setColor(mGapBackgroundLeash, getResizingBackgroundColor(resizingTask)) + t.setColor(mGapBackgroundLeash, veilColor) .setLayer(mGapBackgroundLeash, Integer.MAX_VALUE - 2) .setPosition(mGapBackgroundLeash, left, top) .setWindowCrop(mGapBackgroundLeash, sideBounds.width(), sideBounds.height()); @@ -251,12 +269,12 @@ public class SplitDecorManager extends WindowlessWindowManager { if (update) { if (immediately) { - t.setVisibility(mBackgroundLeash, show); - t.setVisibility(mIconLeash, show); + t.setVisibility(mBackgroundLeash, showVeil); + t.setVisibility(mIconLeash, showVeil); } else { - startFadeAnimation(show, false, null); + startFadeAnimation(showVeil, false, null); } - mShown = show; + mShown = showVeil; } } @@ -309,7 +327,8 @@ public class SplitDecorManager extends WindowlessWindowManager { mIsResizing = false; mOffsetX = 0; mOffsetY = 0; - mOldBounds.setEmpty(); + mOldMainBounds.setEmpty(); + mOldSideBounds.setEmpty(); mResizingBounds.setEmpty(); if (mFadeAnimator != null && mFadeAnimator.isRunning()) { if (!mShown) { @@ -346,14 +365,14 @@ public class SplitDecorManager extends WindowlessWindowManager { /** Screenshot host leash and attach on it if meet some conditions */ public void screenshotIfNeeded(SurfaceControl.Transaction t) { - if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) { + if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) { if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { mScreenshotAnimator.cancel(); } else if (mScreenshot != null) { t.remove(mScreenshot); } - mTempRect.set(mOldBounds); + mTempRect.set(mOldMainBounds); mTempRect.offsetTo(0, 0); mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect, Integer.MAX_VALUE - 1); @@ -364,7 +383,7 @@ public class SplitDecorManager extends WindowlessWindowManager { public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) { if (screenshot == null || !screenshot.isValid()) return; - if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) { + if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) { if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { mScreenshotAnimator.cancel(); } else if (mScreenshot != null) { @@ -465,9 +484,4 @@ public class SplitDecorManager extends WindowlessWindowManager { mIcon = null; } } - - private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { - final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); - return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents(); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 2ea32f44a78b..8331654839c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -496,10 +496,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange * Updates bounds with the passing position. Usually used to update recording bounds while * performing animation or dragging divider bar to resize the splits. */ - void updateDividerBounds(int position) { + void updateDividerBounds(int position, boolean shouldUseParallaxEffect) { updateBounds(position); mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x, - mSurfaceEffectPolicy.mParallaxOffset.y); + mSurfaceEffectPolicy.mParallaxOffset.y, shouldUseParallaxEffect); } void setDividerPosition(int position, boolean applyLayoutChange) { @@ -647,7 +647,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange .setDuration(duration); mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); mDividerFlingAnimator.addUpdateListener( - animation -> updateDividerBounds((int) animation.getAnimatedValue())); + animation -> updateDividerBounds( + (int) animation.getAnimatedValue(), false /* shouldUseParallaxEffect */) + ); mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -897,7 +899,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl, * SurfaceControl, SurfaceControl, boolean) */ - void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY); + void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY, + boolean shouldUseParallaxEffect); /** * Calls when finish resizing the split bounds. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java index f9259e79472e..e8226051b672 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java @@ -16,8 +16,6 @@ package com.android.wm.shell.common.split; -import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED; - import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -26,25 +24,18 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import android.app.ActivityManager; import android.app.PendingIntent; -import android.content.ComponentName; import android.content.Intent; -import android.content.pm.LauncherApps; -import android.content.pm.ShortcutInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.Rect; -import android.os.UserHandle; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.util.ArrayUtils; import com.android.wm.shell.Flags; import com.android.wm.shell.ShellTaskOrganizer; -import java.util.Arrays; -import java.util.List; - /** Helper utility class for split screen components to use. */ public class SplitScreenUtils { /** Reverse the split position. */ @@ -137,4 +128,10 @@ public class SplitScreenUtils { return isLandscape; } } + + /** Returns the specified background color that matches a RunningTaskInfo. */ + public static Color getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { + final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); + return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index e729c7dd802b..991fbafed296 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -72,7 +72,6 @@ import com.android.wm.shell.compatui.CompatUIConfiguration; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.compatui.CompatUIShellCommandHandler; import com.android.wm.shell.desktopmode.DesktopMode; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; @@ -88,6 +87,7 @@ import com.android.wm.shell.performance.PerfHintController; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index a1910c5eb3a3..fb0a1ab3062e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -57,7 +57,6 @@ import com.android.wm.shell.dagger.back.ShellBackAnimationModule; import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; @@ -77,6 +76,7 @@ import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 414a9d1151ac..01364d1de279 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -133,6 +133,7 @@ public abstract class Pip2Module { PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, @NonNull PipTransitionState pipTransitionState, + @NonNull PipScheduler pipScheduler, @NonNull SizeSpecSource sizeSpecSource, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -140,7 +141,7 @@ public abstract class Pip2Module { @ShellMainThread ShellExecutor mainExecutor, Optional<PipPerfHintController> pipPerfHintControllerOptional) { return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, - pipBoundsState, pipTransitionState, sizeSpecSource, pipMotionHelper, + pipBoundsState, pipTransitionState, pipScheduler, sizeSpecSource, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index 9038aaad9178..0b7a3e838e88 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -39,6 +39,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterRe import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 6bbc8fec2894..7e0234ef8546 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -446,11 +446,6 @@ class DesktopModeTaskRepository { * Called when the desktop changes the number of visible freeform tasks. */ fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {} - - /** - * Called when the desktop stashed status changes. - */ - fun onStashedChanged(displayId: Int, stashed: Boolean) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt new file mode 100644 index 000000000000..aa11a7d8a663 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.util.Log +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.wm.shell.dagger.WMSingleton +import javax.inject.Inject + +/** + * Log Aster UIEvents for desktop windowing mode. + */ +@WMSingleton +class DesktopModeUiEventLogger @Inject constructor( + private val mUiEventLogger: UiEventLogger, + private val mInstanceIdSequence: InstanceIdSequence +) { + /** + * Logs an event for a CUI, on a particular package. + * + * @param uid The user id associated with the package the user is interacting with + * @param packageName The name of the package the user is interacting with + * @param event The event type to generate + */ + fun log(uid: Int, packageName: String, event: DesktopUiEventEnum) { + if (packageName.isEmpty() || uid < 0) { + Log.d(TAG, "Skip logging since package name is empty or bad uid") + return + } + mUiEventLogger.log(event, uid, packageName) + } + + /** + * Retrieves a new instance id for a new interaction. + */ + fun getNewInstanceId(): InstanceId = mInstanceIdSequence.newInstanceId() + + /** + * Logs an event as part of a particular CUI, on a particular package. + * + * @param instanceId The id identifying an interaction, potentially taking place across multiple + * surfaces. There should be a new id generated for each distinct CUI. + * @param uid The user id associated with the package the user is interacting with + * @param packageName The name of the package the user is interacting with + * @param event The event type to generate + */ + fun logWithInstanceId( + instanceId: InstanceId, + uid: Int, + packageName: String, + event: DesktopUiEventEnum + ) { + if (packageName.isEmpty() || uid < 0) { + Log.d(TAG, "Skip logging since package name is empty or bad uid") + return + } + mUiEventLogger.logWithInstanceId(event, uid, packageName, instanceId) + } + + companion object { + /** + * Enums for logging desktop windowing mode UiEvents. + */ + enum class DesktopUiEventEnum(private val mId: Int) : UiEventLogger.UiEventEnum { + + @UiEvent(doc = "Resize the window in desktop windowing mode by dragging the edge") + DESKTOP_WINDOW_EDGE_DRAG_RESIZE(1721), + + @UiEvent(doc = "Resize the window in desktop windowing mode by dragging the corner") + DESKTOP_WINDOW_CORNER_DRAG_RESIZE(1722), + + @UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode") + DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723), + + @UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode") + DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724); + + override fun getId(): Int = mId + } + + private const val TAG = "DesktopModeUiEventLogger" + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index b2bdbfefb9aa..2dc4573b4921 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -72,6 +72,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.splitscreen.SplitScreenController @@ -227,7 +228,7 @@ class DesktopTasksController( bringDesktopAppsToFront(displayId, wct) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - // TODO(b/255649902): ensure remote transition is supplied once state is introduced + // TODO(b/309014605): ensure remote transition is supplied once state is introduced val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT val handler = remoteTransition?.let { OneShotRemoteHandler(transitions.mainExecutor, remoteTransition) @@ -1374,16 +1375,6 @@ class DesktopTasksController( l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount) } } - - override fun onStashedChanged(displayId: Int, stashed: Boolean) { - KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "IDesktopModeImpl: onStashedChanged display=%d stashed=%b", - displayId, - stashed - ) - remoteListener.call { l -> l.onStashedChanged(displayId, stashed) } - } } init { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 3404d376fe92..0f88384ec2ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -25,6 +25,7 @@ import android.window.WindowContainerTransaction import androidx.annotation.VisibleForTesting import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionObserver import com.android.wm.shell.util.KtProtoLog diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 451e09c3cd9c..dae75f90e3ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -23,6 +23,7 @@ import android.view.WindowManager import android.window.TransitionInfo import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.KtProtoLog diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl index 8ed87f23bf40..8ebdfdcf4731 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl @@ -25,6 +25,6 @@ interface IDesktopTaskListener { /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */ oneway void onTasksVisibilityChanged(int displayId, int visibleTasksCount); - /** Desktop task stashed status has changed. */ + /** @deprecated this is no longer supported. */ oneway void onStashedChanged(int displayId, boolean stashed); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index 59d696918448..4bb10dfdf8c6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -22,11 +22,11 @@ import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; @@ -41,7 +41,6 @@ import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; @@ -278,7 +277,7 @@ public class DragLayout extends LinearLayout final int activityType = taskInfo1.getActivityType(); if (activityType == ACTIVITY_TYPE_STANDARD) { Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo); - int bgColor1 = getResizingBackgroundColor(taskInfo1); + int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb(); mDropZoneView1.setAppInfo(bgColor1, icon1); mDropZoneView2.setAppInfo(bgColor1, icon1); updateDropZoneSizes(null, null); // passing null splits the views evenly @@ -298,10 +297,10 @@ public class DragLayout extends LinearLayout mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); if (topOrLeftTask != null && bottomOrRightTask != null) { Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo); - int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask); + int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask).toArgb(); Drawable bottomOrRightIcon = mIconProvider.getIcon( bottomOrRightTask.topActivityInfo); - int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask); + int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask).toArgb(); mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon); mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon); } @@ -556,11 +555,6 @@ public class DragLayout extends LinearLayout } } - private static int getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { - final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); - return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); - } - /** * Dumps information about this drag layout. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index a414a55eb633..e0e2e706d649 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -27,9 +27,9 @@ import android.view.SurfaceControl; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 9eaf7e4e2e21..c79eef7efb61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING; @@ -83,6 +84,7 @@ public class KeyguardTransitionHandler * @see KeyguardTransitions */ private IRemoteTransition mExitTransition = null; + private IRemoteTransition mAppearTransition = null; private IRemoteTransition mOccludeTransition = null; private IRemoteTransition mOccludeByDreamTransition = null; private IRemoteTransition mUnoccludeTransition = null; @@ -170,26 +172,28 @@ public class KeyguardTransitionHandler // Choose a transition applicable for the changes and keyguard state. if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) { - return startAnimation(mExitTransition, - "going-away", + return startAnimation(mExitTransition, "going-away", transition, info, startTransaction, finishTransaction, finishCallback); } + if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) { + return startAnimation(mAppearTransition, "appearing", + transition, info, startTransaction, finishTransaction, finishCallback); + } + + // Occlude/unocclude animations are only played if the keyguard is locked. if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) { if (hasOpeningDream(info)) { - return startAnimation(mOccludeByDreamTransition, - "occlude-by-dream", + return startAnimation(mOccludeByDreamTransition, "occlude-by-dream", transition, info, startTransaction, finishTransaction, finishCallback); } else { - return startAnimation(mOccludeTransition, - "occlude", + return startAnimation(mOccludeTransition, "occlude", transition, info, startTransaction, finishTransaction, finishCallback); } } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { - return startAnimation(mUnoccludeTransition, - "unocclude", + return startAnimation(mUnoccludeTransition, "unocclude", transition, info, startTransaction, finishTransaction, finishCallback); } } @@ -359,11 +363,13 @@ public class KeyguardTransitionHandler @Override public void register( IRemoteTransition exitTransition, + IRemoteTransition appearTransition, IRemoteTransition occludeTransition, IRemoteTransition occludeByDreamTransition, IRemoteTransition unoccludeTransition) { mMainExecutor.execute(() -> { mExitTransition = exitTransition; + mAppearTransition = appearTransition; mOccludeTransition = occludeTransition; mOccludeByDreamTransition = occludeByDreamTransition; mUnoccludeTransition = unoccludeTransition; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java index 4215b2cc5f29..b7245b91f36c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java @@ -35,6 +35,7 @@ public interface KeyguardTransitions { */ default void register( @NonNull IRemoteTransition unlockTransition, + @NonNull IRemoteTransition appearTransition, @NonNull IRemoteTransition occludeTransition, @NonNull IRemoteTransition occludeByDreamTransition, @NonNull IRemoteTransition unoccludeTransition) {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index e885262658f4..e1657f99639d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -854,7 +854,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipUiEventLoggerLogger.log(uiEventEnum); ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "onTaskAppeared: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState); + "onTaskAppeared: %s, state=%s, taskId=%s", mTaskInfo.topActivity, + mPipTransitionState, mTaskInfo.taskId); if (mPipTransitionState.getInSwipePipToHomeTransition()) { if (!mWaitForFixedRotation) { onEndOfSwipePipToHomeTransition(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index fdde3ee01264..2082756feda7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -824,12 +824,10 @@ public class PipTransition extends PipTransitionController { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo) { startTransaction.apply(); - if (info.getChanges().isEmpty()) { + final TransitionInfo.Change pipChange = findCurrentPipTaskChange(info); + if (pipChange == null) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "removePipImmediately is called with empty changes"); - } else { - finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), - mPipDisplayLayoutState.getDisplayBounds()); + "removePipImmediately is called without pip change"); } mPipOrganizer.onExitPipFinished(taskInfo); finishCallback.onTransitionFinished(null); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index a097a0ffa47d..be10151ca5aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -58,7 +58,8 @@ import java.util.function.Consumer; * A helper to animate and manipulate the PiP. */ public class PipMotionHelper implements PipAppOpsListener.Callback, - FloatingContentCoordinator.FloatingContent { + FloatingContentCoordinator.FloatingContent, + PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = "PipMotionHelper"; private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change"; private static final boolean DEBUG = false; @@ -181,7 +182,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } }; mPipTransitionState = pipTransitionState; - mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged); + mPipTransitionState.addPipTransitionStateChangedListener(this); } void init() { @@ -687,7 +688,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, // setAnimatingToBounds(toBounds); } - private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @Override + public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) { switch (newState) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java index 04cf350ddd3e..b55a41d8808f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -24,6 +24,7 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.hardware.input.InputManager; +import android.os.Bundle; import android.os.Looper; import android.view.BatchedInputEventReceiver; import android.view.Choreographer; @@ -32,6 +33,7 @@ import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputMonitor; import android.view.MotionEvent; +import android.view.SurfaceControl; import android.view.ViewConfiguration; import androidx.annotation.VisibleForTesting; @@ -51,16 +53,20 @@ import java.util.function.Consumer; * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to * trigger dynamic resize. */ -public class PipResizeGestureHandler { +public class PipResizeGestureHandler implements + PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = "PipResizeGestureHandler"; private static final int PINCH_RESIZE_SNAP_DURATION = 250; private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f; + private static final String RESIZE_BOUNDS_CHANGE = "resize_bounds_change"; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final PipBoundsState mPipBoundsState; private final PipTouchState mPipTouchState; + private final PipScheduler mPipScheduler; + private final PipTransitionState mPipTransitionState; private final PhonePipMenuController mPhonePipMenuController; private final PipUiEventLogger mPipUiEventLogger; private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; @@ -88,6 +94,7 @@ public class PipResizeGestureHandler { private boolean mIsSysUiStateValid; private boolean mThresholdCrossed; private boolean mOngoingPinchToResize = false; + private boolean mWaitingForBoundsChangeTransition = false; private float mAngle = 0; int mFirstIndex = -1; int mSecondIndex = -1; @@ -104,11 +111,17 @@ public class PipResizeGestureHandler { private int mCtrlType; private int mOhmOffset; - public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm, - PipBoundsState pipBoundsState, PipTouchState pipTouchState, + public PipResizeGestureHandler(Context context, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipBoundsState pipBoundsState, + PipTouchState pipTouchState, + PipScheduler pipScheduler, + PipTransitionState pipTransitionState, Runnable updateMovementBoundsRunnable, - PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, - ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) { + PipUiEventLogger pipUiEventLogger, + PhonePipMenuController menuActivityController, + ShellExecutor mainExecutor, + @Nullable PipPerfHintController pipPerfHintController) { mContext = context; mDisplayId = context.getDisplayId(); mMainExecutor = mainExecutor; @@ -116,6 +129,11 @@ public class PipResizeGestureHandler { mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; mPipTouchState = pipTouchState; + mPipScheduler = pipScheduler; + + mPipTransitionState = pipTransitionState; + mPipTransitionState.addPipTransitionStateChangedListener(this); + mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; @@ -125,6 +143,7 @@ public class PipResizeGestureHandler { mUserResizeBounds.set(rect); // mMotionHelper.synchronizePinnedStackBounds(); mUpdateMovementBoundsRunnable.run(); + mPipBoundsState.setBounds(rect); resetState(); }; } @@ -202,7 +221,7 @@ public class PipResizeGestureHandler { @VisibleForTesting void onInputEvent(InputEvent ev) { if (!mEnablePinchResize) { - // No need to handle anything if neither form of resizing is enabled. + // No need to handle anything if resizing isn't enabled. return; } @@ -227,7 +246,7 @@ public class PipResizeGestureHandler { } } - if (mEnablePinchResize && mOngoingPinchToResize) { + if (mOngoingPinchToResize) { onPinchResize(mv); } } @@ -249,13 +268,11 @@ public class PipResizeGestureHandler { } boolean willStartResizeGesture(MotionEvent ev) { - if (isInValidSysUiState()) { - if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { - if (mEnablePinchResize && ev.getPointerCount() == 2) { - onPinchResize(ev); - mOngoingPinchToResize = mAllowGesture; - return mAllowGesture; - } + if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { + if (mEnablePinchResize && ev.getPointerCount() == 2) { + onPinchResize(ev); + mOngoingPinchToResize = mAllowGesture; + return mAllowGesture; } } return false; @@ -284,7 +301,6 @@ public class PipResizeGestureHandler { mSecondIndex = -1; mAllowGesture = false; finishResize(); - cleanUpHighPerfSessionMaybe(); } if (ev.getPointerCount() != 2) { @@ -347,10 +363,7 @@ public class PipResizeGestureHandler { mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize, mDownBounds, mLastResizeBounds); - /* - mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, - mAngle, null); - */ + mPipScheduler.scheduleUserResizePip(mLastResizeBounds, mAngle); mPipBoundsState.setHasUserResizedPip(true); } } @@ -399,57 +412,43 @@ public class PipResizeGestureHandler { } private void finishResize() { - if (!mLastResizeBounds.isEmpty()) { - // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped - // position correctly. Drag-resize does not need to move, so just finalize resize. - if (mOngoingPinchToResize) { - final Rect startBounds = new Rect(mLastResizeBounds); - // If user resize is pretty close to max size, just auto resize to max. - if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x - || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { - resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); - } + if (mLastResizeBounds.isEmpty()) { + resetState(); + } + if (!mOngoingPinchToResize) { + return; + } + final Rect startBounds = new Rect(mLastResizeBounds); - // If user resize is smaller than min size, auto resize to min - if (mLastResizeBounds.width() < mMinSize.x - || mLastResizeBounds.height() < mMinSize.y) { - resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y); - } + // If user resize is pretty close to max size, just auto resize to max. + if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x + || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { + resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); + } - // get the current movement bounds - final Rect movementBounds = mPipBoundsAlgorithm - .getMovementBounds(mLastResizeBounds); - - // snap mLastResizeBounds to the correct edge based on movement bounds - snapToMovementBoundsEdge(mLastResizeBounds, movementBounds); - - final float snapFraction = mPipBoundsAlgorithm.getSnapFraction( - mLastResizeBounds, movementBounds); - mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); - - // disable any touch events beyond resizing too - mPipTouchState.setAllowInputEvents(false); - - /* - mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, - PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> { - // enable touch events - mPipTouchState.setAllowInputEvents(true); - }); - */ - } else { - /* - mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, - TRANSITION_DIRECTION_USER_RESIZE, - mUpdateResizeBoundsCallback); - */ - } - final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f; - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE); - } else { - resetState(); + // If user resize is smaller than min size, auto resize to min + if (mLastResizeBounds.width() < mMinSize.x + || mLastResizeBounds.height() < mMinSize.y) { + resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y); } + + // get the current movement bounds + final Rect movementBounds = mPipBoundsAlgorithm + .getMovementBounds(mLastResizeBounds); + + // snap mLastResizeBounds to the correct edge based on movement bounds + snapToMovementBoundsEdge(mLastResizeBounds, movementBounds); + + final float snapFraction = mPipBoundsAlgorithm.getSnapFraction( + mLastResizeBounds, movementBounds); + mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); + + // Update the transition state to schedule a resize transition. + Bundle extra = new Bundle(); + extra.putBoolean(RESIZE_BOUNDS_CHANGE, true); + mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra); + + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE); } private void resetState() { @@ -509,6 +508,40 @@ public class PipResizeGestureHandler { rect.set(l, t, r, b); } + @Override + public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) { + switch (newState) { + case PipTransitionState.SCHEDULED_BOUNDS_CHANGE: + if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break; + mWaitingForBoundsChangeTransition = true; + mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds); + break; + case PipTransitionState.CHANGING_PIP_BOUNDS: + if (!mWaitingForBoundsChangeTransition) break; + + // If bounds change transition was scheduled from this class, handle leash updates. + mWaitingForBoundsChangeTransition = false; + + SurfaceControl.Transaction startTx = extra.getParcelable( + PipTransition.PIP_START_TX, SurfaceControl.Transaction.class); + Rect destinationBounds = extra.getParcelable( + PipTransition.PIP_DESTINATION_BOUNDS, Rect.class); + startTx.setPosition(mPipTransitionState.mPinnedTaskLeash, + destinationBounds.left, destinationBounds.top); + startTx.apply(); + + // All motion operations have actually finished, so make bounds cache updates. + cleanUpHighPerfSessionMaybe(); + + // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core. + mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS); + + mUpdateResizeBoundsCallback.accept(destinationBounds); + break; + } + } + /** * Dumps the {@link PipResizeGestureHandler} state. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index c5b0de31f104..49475077211f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -24,6 +24,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Matrix; import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerTransaction; @@ -165,6 +166,16 @@ public class PipScheduler { * {@link WindowContainerTransaction}. */ public void scheduleUserResizePip(Rect toBounds) { + scheduleUserResizePip(toBounds, 0f /* degrees */); + } + + /** + * Directly perform a scaled matrix transformation on the leash. This will not perform any + * {@link WindowContainerTransaction}. + * + * @param degrees the angle to rotate the bounds to. + */ + public void scheduleUserResizePip(Rect toBounds, float degrees) { if (toBounds.isEmpty()) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG); @@ -172,7 +183,16 @@ public class PipScheduler { } SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash; final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); - tx.setPosition(leash, toBounds.left, toBounds.top); + + Matrix transformTensor = new Matrix(); + final float[] mMatrixTmp = new float[9]; + final float scale = (float) toBounds.width() / mPipBoundsState.getBounds().width(); + + transformTensor.setScale(scale, scale); + transformTensor.postTranslate(toBounds.left, toBounds.top); + transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY()); + + tx.setMatrix(leash, transformTensor, mMatrixTmp); tx.apply(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 9c6e3ea494fa..319d1999a272 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -73,7 +73,7 @@ import java.util.Optional; * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding * the PIP. */ -public class PipTouchHandler { +public class PipTouchHandler implements PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = "PipTouchHandler"; private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; @@ -84,6 +84,7 @@ public class PipTouchHandler { private final PipBoundsAlgorithm mPipBoundsAlgorithm; @NonNull private final PipBoundsState mPipBoundsState; @NonNull private final PipTransitionState mPipTransitionState; + @NonNull private final PipScheduler mPipScheduler; @NonNull private final SizeSpecSource mSizeSpecSource; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; @@ -173,6 +174,7 @@ public class PipTouchHandler { PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, @NonNull PipTransitionState pipTransitionState, + @NonNull PipScheduler pipScheduler, @NonNull SizeSpecSource sizeSpecSource, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -188,6 +190,7 @@ public class PipTouchHandler { mPipTransitionState = pipTransitionState; mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged); + mPipScheduler = pipScheduler; mSizeSpecSource = sizeSpecSource; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; @@ -213,10 +216,10 @@ public class PipTouchHandler { }, menuController::hideMenu, mainExecutor); - mPipResizeGestureHandler = - new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, - mTouchState, this::updateMovementBounds, pipUiEventLogger, - menuController, mainExecutor, mPipPerfHintController); + mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm, + pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, + this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor, + mPipPerfHintController); mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize); if (PipUtils.isPip2ExperimentEnabled()) { @@ -1075,7 +1078,8 @@ public class PipTouchHandler { mPipResizeGestureHandler.setOhmOffset(offset); } - private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @Override + public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) { switch (newState) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index a8611d966a29..c53e7fe00598 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -50,9 +50,9 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellCommandHandler; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index b10176df5826..4299088a51f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -43,10 +43,12 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString; +import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; +import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; @@ -67,6 +69,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN; import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; +import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; @@ -2386,14 +2389,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY) { + public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY, + boolean shouldUseParallaxEffect) { final SurfaceControl.Transaction t = mTransactionPool.acquire(); t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); - updateSurfaceBounds(layout, t, true /* applyResizingOffset */); + updateSurfaceBounds(layout, t, shouldUseParallaxEffect); getMainStageBounds(mTempRect1); getSideStageBounds(mTempRect2); - mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately); - mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately); + // TODO (b/307490004): "commonColor" below is a temporary fix to ensure the colors on both + // sides match. When b/307490004 is fixed, this code can be reverted. + float[] commonColor = getResizingBackgroundColor(mSideStage.mRootTaskInfo).getComponents(); + mMainStage.onResizing( + mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately, commonColor); + mSideStage.onResizing( + mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately, commonColor); t.apply(); mTransactionPool.release(t); } @@ -2836,7 +2845,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setFreezeDividerWindow(false); final StageChangeRecord record = new StageChangeRecord(); final int transitType = info.getType(); - boolean hasEnteringPip = false; + TransitionInfo.Change pipChange = null; for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); if (change.getMode() == TRANSIT_CHANGE @@ -2847,7 +2856,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } if (mMixedHandler.isEnteringPip(change, transitType)) { - hasEnteringPip = true; + pipChange = change; } final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); @@ -2899,9 +2908,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - if (hasEnteringPip) { + if (pipChange != null) { + TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange, + mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId, + getSplitItemStage(pipChange.getLastParent())); + if (pipReplacingChange != null) { + // Set an enter transition for when startAnimation gets called again + mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null, + TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false); + } + mMixedHandler.animatePendingEnterPipFromSplit(transition, info, - startTransaction, finishTransaction, finishCallback); + startTransaction, finishTransaction, finishCallback, + pipReplacingChange != null); notifySplitAnimationFinished(); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 1e305c5dbbcf..0f3d6cade95a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -177,9 +177,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d", + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d taskParent=%d rootTask=%d " + + "taskActivity=%s", taskInfo.taskId, taskInfo.parentTaskId, - mRootTaskInfo != null ? mRootTaskInfo.taskId : -1); + mRootTaskInfo != null ? mRootTaskInfo.taskId : -1, + taskInfo.baseActivity); if (mRootTaskInfo == null) { mRootLeash = leash; mRootTaskInfo = taskInfo; @@ -213,6 +215,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s", + taskInfo.taskId, taskInfo.baseActivity); mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); if (mRootTaskInfo.taskId == taskInfo.taskId) { // Inflates split decor view only when the root task is visible. @@ -310,10 +314,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, - int offsetY, boolean immediately) { + int offsetY, boolean immediately, float[] veilColor) { if (mSplitDecorManager != null && mRootTaskInfo != null) { mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX, - offsetY, immediately); + offsetY, immediately, veilColor); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java index e419462012e3..e07e1b460168 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -45,6 +45,7 @@ import android.window.SplashScreenView; import com.android.internal.R; +import java.io.Closeable; import java.util.function.LongConsumer; /** @@ -100,7 +101,7 @@ public class SplashscreenIconDrawableFactory { * Drawable pre-drawing the scaled icon in a separate thread to increase the speed of the * final drawing. */ - private static class ImmobileIconDrawable extends Drawable { + private static class ImmobileIconDrawable extends Drawable implements Closeable { private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG); private final Matrix mMatrix = new Matrix(); @@ -154,6 +155,16 @@ public class SplashscreenIconDrawableFactory { public int getOpacity() { return 1; } + + @Override + public void close() { + synchronized (mPaint) { + if (mIconBitmap != null) { + mIconBitmap.recycle(); + mIconBitmap = null; + } + } + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 968b27b62490..bcacecbd8981 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -77,6 +77,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler, private ActivityEmbeddingController mActivityEmbeddingController; abstract static class MixedTransition { + /** Entering Pip from split, breaks split. */ static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; /** Both the display and split-state (enter/exit) is changing */ @@ -103,6 +104,9 @@ public class DefaultMixedHandler implements MixedTransitionHandler, /** Enter pip from one of the Activity Embedding windows. */ static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 9; + /** Entering Pip from split, but replace the Pip stage instead of breaking split. */ + static final int TYPE_ENTER_PIP_REPLACE_FROM_SPLIT = 10; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -484,9 +488,11 @@ public class DefaultMixedHandler implements MixedTransitionHandler, // TODO(b/287704263): Remove when split/mixed are reversed. public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - Transitions.TransitionFinishCallback finishCallback) { - final MixedTransition mixed = createDefaultMixedTransition( - MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition); + Transitions.TransitionFinishCallback finishCallback, boolean replacingPip) { + int type = replacingPip + ? MixedTransition.TYPE_ENTER_PIP_REPLACE_FROM_SPLIT + : MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT; + final MixedTransition mixed = createDefaultMixedTransition(type, transition); mActiveTransitions.add(mixed); Transitions.TransitionFinishCallback callback = wct -> { mActiveTransitions.remove(mixed); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index b028bd65b438..0ada74937df4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -76,7 +76,12 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { info, startTransaction, finishTransaction, finishCallback); case TYPE_ENTER_PIP_FROM_SPLIT -> animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, - finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler, + /*replacingPip*/ false); + case TYPE_ENTER_PIP_REPLACE_FROM_SPLIT -> + animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler, + /*replacingPip*/ true); case TYPE_KEYGUARD -> animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback, mKeyguardHandler, mPipHandler); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java index ffc0b76b131d..e8b01b5880fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -23,11 +23,15 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; +import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy; import android.annotation.NonNull; +import android.annotation.Nullable; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -45,7 +49,8 @@ public class MixedTransitionHelper { @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull Transitions player, @NonNull MixedTransitionHandler mixedHandler, - @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) { + @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler, + boolean replacingPip) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + "entering PIP while Split-Screen is foreground."); TransitionInfo.Change pipChange = null; @@ -99,7 +104,7 @@ public class MixedTransitionHelper { // we need a separate one to send over to launcher. SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; - if (splitHandler.isSplitScreenVisible()) { + if (splitHandler.isSplitScreenVisible() && !replacingPip) { // The non-going home case, we could be pip-ing one of the split stages and keep // showing the other for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -115,11 +120,12 @@ public class MixedTransitionHelper { break; } } + + // Let split update internal state for dismiss. + splitHandler.prepareDismissAnimation(topStageToKeep, + EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, + finishTransaction); } - // Let split update internal state for dismiss. - splitHandler.prepareDismissAnimation(topStageToKeep, - EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, - finishTransaction); // We are trying to accommodate launcher's close animation which can't handle the // divider-bar, so if split-handler is closing the divider-bar, just hide it and @@ -152,6 +158,44 @@ public class MixedTransitionHelper { return true; } + /** + * Check to see if we're only closing split to enter pip or if we're replacing pip with + * another task. If we are replacing, this will return the change for the task we are replacing + * pip with + * + * @param info Any number of changes + * @param pipChange TransitionInfo.Change indicating the task that is being pipped + * @param splitMainStageRootId MainStage's rootTaskInfo's id + * @param splitSideStageRootId SideStage's rootTaskInfo's id + * @param lastPipSplitStage The last stage that {@param pipChange} was in + * @return The change from {@param info} that is replacing the {@param pipChange}, {@code null} + * otherwise + */ + @Nullable + public static TransitionInfo.Change getPipReplacingChange(TransitionInfo info, + TransitionInfo.Change pipChange, int splitMainStageRootId, int splitSideStageRootId, + @SplitScreen.StageType int lastPipSplitStage) { + int lastPipParentTask = -1; + if (lastPipSplitStage == STAGE_TYPE_MAIN) { + lastPipParentTask = splitMainStageRootId; + } else if (lastPipSplitStage == STAGE_TYPE_SIDE) { + lastPipParentTask = splitSideStageRootId; + } + + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange || !isOpeningMode(change.getMode())) { + // Ignore the change/task that's going into Pip or not opening + continue; + } + + if (change.getTaskInfo().parentTaskId == lastPipParentTask) { + return change; + } + } + return null; + } + private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) { return change.getTaskInfo() != null && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index d6e64cfaf4d5..9fc6702562bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -142,7 +142,8 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { && mSplitHandler.getSplitItemPosition(change.getLastParent()) != SPLIT_POSITION_UNDEFINED) { return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, - finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler, + /*replacingPip*/ false); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 4d3c76322fa8..6224543516fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -31,7 +31,6 @@ import static android.view.WindowManager.fixScale; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; -import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; @@ -567,15 +566,15 @@ public class Transitions implements RemoteCallable<Transitions>, final int mode = change.getMode(); // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { - if (isOpening - // This is for when an activity launches while a different transition is - // collecting. - || change.hasFlags(FLAG_MOVED_TO_TOP)) { + if (isOpening) { // put on top return zSplitLine + numChanges - i; - } else { + } else if (isClosing) { // put on bottom return zSplitLine - i; + } else { + // maintain relative ordering (put all changes in the animating layer) + return zSplitLine + numChanges - i; } } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { if (isOpening) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java index 1897560deed7..6adbe4f7ce92 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java @@ -49,8 +49,12 @@ public class PerfettoTransitionTracer implements TransitionTracer { public PerfettoTransitionTracer() { Producer.init(InitArguments.DEFAULTS); - mDataSource.register( - new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .build(); + mDataSource.register(params); } /** @@ -214,8 +218,6 @@ public class PerfettoTransitionTracer implements TransitionTracer { } os.end(mappingsToken); - - ctx.flush(); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index dfdb58a50892..9afb057ffbe5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -84,12 +84,12 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 4c347adcb486..4d4dc3c72420 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -66,8 +66,8 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder; import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index de6c03549f0e..541825437c86 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -52,7 +52,7 @@ import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.desktopmode.DesktopModeStatus; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement; import java.util.ArrayList; diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml index 4dd14f4011d0..f69a90cc793f 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml index 5c86a386fc6c..b76d06565700 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml index aa70c093b847..041978c371ff 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml index c7c804f2361a..a66dfb4566f9 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml index 214bdfaa0743..85715db3d952 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 8de60b7acc91..cfe8e07aa6e5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -26,6 +26,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_STA import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -115,9 +116,9 @@ public class SplitLayoutTests extends ShellTestCase { @Test public void testUpdateDivideBounds() { - mSplitLayout.updateDividerBounds(anyInt()); + mSplitLayout.updateDividerBounds(anyInt(), anyBoolean()); verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(), - anyInt()); + anyInt(), anyBoolean()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index 60a7dcda5351..2a2483df0792 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -41,6 +41,7 @@ import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt new file mode 100644 index 000000000000..285e5b6a04a5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.Companion.DesktopUiEventEnum.DESKTOP_WINDOW_EDGE_DRAG_RESIZE +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Test class for [DesktopModeUiEventLogger] + * + * Usage: atest WMShellUnitTests:DesktopModeUiEventLoggerTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopModeUiEventLoggerTest : ShellTestCase() { + private lateinit var uiEventLoggerFake: UiEventLoggerFake + private lateinit var logger: DesktopModeUiEventLogger + private val instanceIdSequence = InstanceIdSequence(10) + + + @Before + fun setUp() { + uiEventLoggerFake = UiEventLoggerFake() + logger = DesktopModeUiEventLogger(uiEventLoggerFake, instanceIdSequence) + } + + @Test + fun log_invalidUid_eventNotLogged() { + logger.log(-1, PACKAGE_NAME, DESKTOP_WINDOW_EDGE_DRAG_RESIZE) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0) + } + + @Test + fun log_emptyPackageName_eventNotLogged() { + logger.log(UID, "", DESKTOP_WINDOW_EDGE_DRAG_RESIZE) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0) + } + + @Test + fun log_eventLogged() { + val event = + DESKTOP_WINDOW_EDGE_DRAG_RESIZE + logger.log(UID, PACKAGE_NAME, event) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id) + assertThat(uiEventLoggerFake[0].instanceId).isNull() + assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID) + assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME) + } + + @Test + fun getNewInstanceId() { + val first = logger.getNewInstanceId() + assertThat(first).isNotEqualTo(logger.getNewInstanceId()) + } + + @Test + fun logWithInstanceId_invalidUid_eventNotLogged() { + logger.logWithInstanceId(INSTANCE_ID, -1, PACKAGE_NAME, DESKTOP_WINDOW_EDGE_DRAG_RESIZE) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0) + } + + @Test + fun logWithInstanceId_emptyPackageName_eventNotLogged() { + logger.logWithInstanceId(INSTANCE_ID, UID, "", DESKTOP_WINDOW_EDGE_DRAG_RESIZE) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0) + } + + @Test + fun logWithInstanceId_eventLogged() { + val event = + DESKTOP_WINDOW_EDGE_DRAG_RESIZE + logger.logWithInstanceId(INSTANCE_ID, UID, PACKAGE_NAME, event) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id) + assertThat(uiEventLoggerFake[0].instanceId).isEqualTo(INSTANCE_ID) + assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID) + assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME) + } + + + companion object { + private val INSTANCE_ID = InstanceId.fakeInstanceId(0) + private const val UID = 10 + private const val PACKAGE_NAME = "com.foo" + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 7e55628b5641..f67da5573b7d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -79,6 +79,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplit import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 539d5b86453f..3c488cac6edd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -32,6 +32,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.StubTransaction diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java index 665077be3af7..cd68c6996578 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java @@ -35,8 +35,8 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.windowdecor.WindowDecorViewModel; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 240324ba4420..884cb6ec9f74 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -67,8 +67,8 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 7d19f3cbf659..aa2cee79fcfc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -60,8 +60,8 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue -import com.android.wm.shell.desktopmode.DesktopModeStatus import com.android.wm.shell.desktopmode.DesktopTasksController +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.sysui.KeyguardChangeListener import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 4eb44d747486..8b8cd119effd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -75,7 +75,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.desktopmode.DesktopModeStatus; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.tests.R; import org.junit.Before; diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 753a69960b4c..7c1c5b4e7e5f 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -336,6 +336,7 @@ cc_defaults { "jni/android_graphics_animation_NativeInterpolatorFactory.cpp", "jni/android_graphics_animation_RenderNodeAnimator.cpp", "jni/android_graphics_Canvas.cpp", + "jni/android_graphics_Color.cpp", "jni/android_graphics_ColorSpace.cpp", "jni/android_graphics_drawable_AnimatedVectorDrawable.cpp", "jni/android_graphics_drawable_VectorDrawable.cpp", diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp index fd9915a54bb5..70a9ef04d6f3 100644 --- a/libs/hwui/apex/LayoutlibLoader.cpp +++ b/libs/hwui/apex/LayoutlibLoader.cpp @@ -46,6 +46,7 @@ namespace android { extern int register_android_graphics_Canvas(JNIEnv* env); extern int register_android_graphics_CanvasProperty(JNIEnv* env); +extern int register_android_graphics_Color(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); extern int register_android_graphics_ColorSpace(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); @@ -87,6 +88,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)}, {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)}, {"android.graphics.CanvasProperty", REG_JNI(register_android_graphics_CanvasProperty)}, + {"android.graphics.Color", REG_JNI(register_android_graphics_Color)}, {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)}, {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)}, {"android.graphics.CreateJavaOutputStreamAdaptor", diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index fb0cdb034575..6ace3967ecf3 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -49,6 +49,7 @@ namespace android { extern int register_android_graphics_Canvas(JNIEnv* env); extern int register_android_graphics_CanvasProperty(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); +extern int register_android_graphics_Color(JNIEnv* env); extern int register_android_graphics_ColorSpace(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); extern int register_android_graphics_FontFamily(JNIEnv* env); @@ -98,6 +99,7 @@ extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_Canvas), + REG_JNI(register_android_graphics_Color), // This needs to be before register_android_graphics_Graphics, or the latter // will not be able to find the jmethodID for ColorSpace.get(). REG_JNI(register_android_graphics_ColorSpace), diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index a952be020855..2a057e7a4cdc 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -36,25 +36,6 @@ static const uint32_t sGradientShaderFlags = SkGradientShader::kInterpolateColor return 0; \ } -static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue, jfloatArray hsvArray) -{ - SkScalar hsv[3]; - SkRGBToHSV(red, green, blue, hsv); - - AutoJavaFloatArray autoHSV(env, hsvArray, 3); - float* values = autoHSV.ptr(); - for (int i = 0; i < 3; i++) { - values[i] = SkScalarToFloat(hsv[i]); - } -} - -static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray) -{ - AutoJavaFloatArray autoHSV(env, hsvArray, 3); - SkScalar* hsv = autoHSV.ptr(); - return static_cast<jint>(SkHSVToColor(alpha, hsv)); -} - /////////////////////////////////////////////////////////////////////////////////////////////// static void Shader_safeUnref(SkShader* shader) { @@ -409,11 +390,6 @@ static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder /////////////////////////////////////////////////////////////////////////////////////////////// -static const JNINativeMethod gColorMethods[] = { - { "nativeRGBToHSV", "(III[F)V", (void*)Color_RGBToHSV }, - { "nativeHSVToColor", "(I[F)I", (void*)Color_HSVToColor } -}; - static const JNINativeMethod gShaderMethods[] = { { "nativeGetFinalizer", "()J", (void*)Shader_getNativeFinalizer }, }; @@ -456,8 +432,6 @@ static const JNINativeMethod gRuntimeShaderMethods[] = { int register_android_graphics_Shader(JNIEnv* env) { - android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods, - NELEM(gColorMethods)); android::RegisterMethodsOrDie(env, "android/graphics/Shader", gShaderMethods, NELEM(gShaderMethods)); android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods, diff --git a/libs/hwui/jni/android_graphics_Color.cpp b/libs/hwui/jni/android_graphics_Color.cpp new file mode 100644 index 000000000000..c22b8b926373 --- /dev/null +++ b/libs/hwui/jni/android_graphics_Color.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" + +#include "SkColor.h" + +using namespace android; + +static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue, + jfloatArray hsvArray) +{ + SkScalar hsv[3]; + SkRGBToHSV(red, green, blue, hsv); + + AutoJavaFloatArray autoHSV(env, hsvArray, 3); + float* values = autoHSV.ptr(); + for (int i = 0; i < 3; i++) { + values[i] = SkScalarToFloat(hsv[i]); + } +} + +static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray) +{ + AutoJavaFloatArray autoHSV(env, hsvArray, 3); + SkScalar* hsv = autoHSV.ptr(); + return static_cast<jint>(SkHSVToColor(alpha, hsv)); +} + +static const JNINativeMethod gColorMethods[] = { + { "nativeRGBToHSV", "(III[F)V", (void*)Color_RGBToHSV }, + { "nativeHSVToColor", "(I[F)I", (void*)Color_HSVToColor } +}; + +namespace android { + +int register_android_graphics_Color(JNIEnv* env) { + return android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods, + NELEM(gColorMethods)); +} + +}; // namespace android diff --git a/libs/hwui/jni/android_graphics_ColorSpace.cpp b/libs/hwui/jni/android_graphics_ColorSpace.cpp index 63d3f83febd6..d06206be90d7 100644 --- a/libs/hwui/jni/android_graphics_ColorSpace.cpp +++ b/libs/hwui/jni/android_graphics_ColorSpace.cpp @@ -148,7 +148,7 @@ static const JNINativeMethod gColorSpaceRgbMethods[] = { namespace android { int register_android_graphics_ColorSpace(JNIEnv* env) { - return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb", + return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb$Native", gColorSpaceRgbMethods, NELEM(gColorSpaceRgbMethods)); } diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp index c0d791a88908..eedc069ed01b 100644 --- a/libs/hwui/jni/android_graphics_Matrix.cpp +++ b/libs/hwui/jni/android_graphics_Matrix.cpp @@ -326,9 +326,6 @@ public: }; static const JNINativeMethod methods[] = { - {"nGetNativeFinalizer", "()J", (void*) SkMatrixGlue::getNativeFinalizer}, - {"nCreate","(J)J", (void*) SkMatrixGlue::create}, - // ------- @FastNative below here --------------- {"nMapPoints","(J[FI[FIIZ)V", (void*) SkMatrixGlue::mapPoints}, {"nMapRect","(JLandroid/graphics/RectF;Landroid/graphics/RectF;)Z", @@ -388,9 +385,6 @@ static jmethodID sCtor; int register_android_graphics_Matrix(JNIEnv* env) { // Methods only used on Ravenwood (for now). See the javadoc on Matrix$ExtraNativesx // for why we need it. - // - // We don't need it on non-ravenwood, but we don't (yet) have a way to detect ravenwood - // environment, so we just always run it. RegisterMethodsOrDie(env, "android/graphics/Matrix$ExtraNatives", extra_methods, NELEM(extra_methods)); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index b43ff63f3fcc..a4887567b075 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -252,18 +252,14 @@ public final class MediaController { return 0; } - /** - * Get the current playback info for this session. - * - * @return The current playback info or null. - */ - public @Nullable PlaybackInfo getPlaybackInfo() { + /** Returns the current playback info for this session. */ + @NonNull + public PlaybackInfo getPlaybackInfo() { try { return mSessionBinder.getVolumeAttributes(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getAudioInfo.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java index f674b06ad33d..c3c74a6fd265 100644 --- a/nfc/java/android/nfc/cardemulation/HostApduService.java +++ b/nfc/java/android/nfc/cardemulation/HostApduService.java @@ -20,6 +20,7 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; @@ -404,6 +405,7 @@ public abstract class HostApduService extends Service { * * @param frame A description of the polling frame. */ + @SuppressLint("OnNameExpected") @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public void processPollingFrames(@NonNull List<PollingFrame> frame) { } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index d4a81109e53c..7bc25ed81089 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -174,11 +174,8 @@ class CredentialSelectorViewModel( onUserCancel() } else { Log.d(Constants.LOG_TAG, "The provider activity was cancelled," + - " re-displaying our UI.") - uiState = uiState.copy( - selectedEntry = null, - providerActivityState = ProviderActivityState.NOT_APPLICABLE, - ) + " re-displaying our UI.") + resetUiStateForReLaunch() } } else { if (entry != null) { @@ -202,6 +199,15 @@ class CredentialSelectorViewModel( } } + // Resets UI states for any situation that re-launches the UI + private fun resetUiStateForReLaunch() { + onBiometricPromptStateChange(BiometricPromptState.INACTIVE) + uiState = uiState.copy( + selectedEntry = null, + providerActivityState = ProviderActivityState.NOT_APPLICABLE, + ) + } + fun onLastLockedAuthEntryNotFoundError() { Log.d(Constants.LOG_TAG, "Unable to find the last unlocked entry") onInternalError() @@ -502,4 +508,4 @@ class CredentialSelectorViewModel( fun logUiEvent(uiEventEnum: UiEventEnum) { this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName) } -}
\ No newline at end of file +} diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING index 4fa882265e53..ad3b44f1bcce 100644 --- a/packages/PrintSpooler/TEST_MAPPING +++ b/packages/PrintSpooler/TEST_MAPPING @@ -8,5 +8,10 @@ } ] } + ], + "postsubmit": [ + { + "name": "PrintSpoolerOutOfProcessTests" + } ] } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index d25d5dcaac87..ff09084e24cd 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -785,6 +785,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } else { onPrinterUnavailable(printerInfo); } + if (mPrinterRegistry != null) { + mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId()); + } mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerInfo); diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md index 30cb9932f104..a762ad3fe199 100644 --- a/packages/SettingsLib/DataStore/README.md +++ b/packages/SettingsLib/DataStore/README.md @@ -1,55 +1,93 @@ # Datastore library -This library aims to manage datastore in a consistent way. +This library provides consistent API for data management (including backup, +restore, and metrics logging) on Android platform. + +Notably, it is designed to be flexible and could be utilized for a wide range of +data store besides the settings preferences. ## Overview -A datastore is required to extend the `BackupRestoreStorage` class and implement -either `Observable` or `KeyedObservable` interface, which enforces: - -- Backup and restore: Datastore should support - [data backup](https://developer.android.com/guide/topics/data/backup) to - preserve user experiences on a new device. -- Observer pattern: The - [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to - monitor data change in the datastore and - - trigger - [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\)) - automatically. - - track data change event to log metrics. - - update internal state and take action. +In the high-level design, a persistent datastore aims to support two key +characteristics: + +- **observable**: triggers backup and metrics logging whenever data is + changed. +- **transferable**: offers users with a seamless experience by backing up and + restoring data on to new devices. + +More specifically, Android framework supports +[data backup](https://developer.android.com/guide/topics/data/backup) to +preserve user experiences on a new device. And the +[observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to +monitor data change. ### Backup and restore -The Android backup framework provides +Currently, the Android backup framework provides [BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper) and [BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper) -to back up a datastore. However, there are several caveats when implement -`BackupHelper`: +to facilitate data backup. However, there are several caveats to consider when +implementing `BackupHelper`: -- performBackup: The data is updated incrementally but it is not well +- *performBackup*: The data is updated incrementally but it is not well documented. The `ParcelFileDescriptor` state parameters are normally ignored and data is updated even there is no change. -- restoreEntity: The implementation must take care not to seek or close the - underlying data source, nor read more than size() bytes from the stream when - restore (see +- *restoreEntity*: The implementation must take care not to seek or close the + underlying data source, nor read more than `size()` bytes from the stream + when restore (see [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)). - It is possible a `BackupHelper` prevents other `BackupHelper`s from - restoring data. -- writeNewStateDescription: Existing implementations rarely notice that this - callback is invoked after all entities are restored, and check if necessary - data are all restored in `restoreEntity` (e.g. + It is possible that a `BackupHelper` interferes with the restore process of + other `BackupHelper`s. +- *writeNewStateDescription*: Existing implementations rarely notice that this + callback is invoked after *all* entities are restored. Instead, they check + if necessary data are all restored in the `restoreEntity` (e.g. [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)), which is not robust sometimes. -This library provides more clear API and offers some improvements: +The datastore library will mitigate these problems by providing alternative +APIs. For instance, library users make use of `InputStream` / `OutputStream` to +back up and restore data directly. + +### Observer pattern + +In the current implementation, the Android backup framework requires a manual +call to +[BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\)). +However, it's often observed that this API call is forgotten when using +`SharedPreferences`. Additionally, there's a common need to log metrics when +data changed. To address these limitations, datastore API employed the observer +pattern. + +### API design and advantages -- The implementation only needs to focus on the `BackupRestoreEntity` - interface. The `InputStream` of restore will ensure bounded data are read, - and close the stream will be no-op. -- The library computes checksum of the backup data automatically, so that - unchanged data will not be sent to Android backup system. +Datastore must extend the `BackupRestoreStorage` class (subclass of +[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)). +The data in a datastore is group by entity, which is represented by +`BackupRestoreEntity`. Basically, a datastore implementation only needs to focus +on the `BackupRestoreEntity`. + +If the datastore is key-value based (e.g. `SharedPreferences`), implements the +`KeyedObservable` interface to offer fine-grained observer. Otherwise, +implements `Observable`. There are builtin thread-safe implementations of the +two interfaces (`KeyedDataObservable` / `DataObservable`). If it is Kotlin, use +delegation to simplify the code. + +Keep in mind that the implementation should call `KeyedObservable.notifyChange` +/ `Observable.notifyChange` whenever internal data is changed, so that the +registered observer will be notified properly. + +For `SharedPreferences` use case, leverage the `SharedPreferencesStorage` +directly. To back up other file based storage, extend the +`BackupRestoreFileStorage` class. + +Here are some highlights of the library: + +- The restore `InputStream` will ensure bounded data are read, and close the + stream is no-op. That being said, all entities are isolated. +- Data checksum is computed automatically, unchanged data will not be sent to + Android backup system. - Data compression is supported: - ZIP best compression is enabled by default, no extra effort needs to be taken. @@ -67,98 +105,159 @@ This library provides more clear API and offers some improvements: successfully restored in those older versions. This is achieved by extending the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will treat each file as an entity and do the backup / restore. -- Manual `BackupManager.dataChanged` call is unnecessary now, the library will - do the invocation (see next section). +- Manual `BackupManager.dataChanged` call is unnecessary now, the framework + will invoke the API automatically. -### Observer pattern +## Usages -Manual `BackupManager.dataChanged` call is required by current backup framework. -In practice, it is found that `SharedPreferences` usages foget to invoke the -API. Besides, there are common use cases to log metrics when data is changed. -Consequently, observer pattern is employed to resolve the issues. +This section provides [examples](example/ExampleStorage.kt) of datastore. -If the datastore is key-value based (e.g. `SharedPreferences`), implements the -`KeyedObservable` interface to offer fine-grained observer. Otherwise, -implements `Observable`. The library provides thread-safe implementations -(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be -helpful. +Here is a datastore with a string data: -Keep in mind that the implementation should call `KeyedObservable.notifyChange` -/ `Observable.notifyChange` whenever internal data is changed, so that the -registered observer will be notified properly. +```kotlin +class ExampleStorage : ObservableBackupRestoreStorage() { + @Volatile // field is manipulated by multiple threads, synchronization might be needed + var data: String? = null + private set -## Usage and example + @AnyThread + fun setData(data: String?) { + this.data = data + // call notifyChange to trigger backup and metrics logging whenever data is changed + if (data != null) { + notifyChange(ChangeReason.UPDATE) + } else { + notifyChange(ChangeReason.DELETE) + } + } + + override val name: String + get() = "ExampleStorage" + + override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = + listOf(StringEntity("data")) + + override fun enableRestore(): Boolean { + return true // check condition like flag, environment, etc. + } + + override fun enableBackup(backupContext: BackupContext): Boolean { + return true // check condition like flag, environment, etc. + } + + @BinderThread + private inner class StringEntity(override val key: String) : BackupRestoreEntity { + override fun backup(backupContext: BackupContext, outputStream: OutputStream) = + if (data != null) { + outputStream.write(data!!.toByteArray(UTF_8)) + EntityBackupResult.UPDATE + } else { + EntityBackupResult.DELETE // delete existing backup data + } + + override fun restore(restoreContext: RestoreContext, inputStream: InputStream) { + // DO NOT call setData API here, which will trigger notifyChange unexpectedly. + // Under the hood, the datastore library will call notifyChange(ChangeReason.RESTORE) + // later to notify observers. + data = String(inputStream.readBytes(), UTF_8) + // Handle restored data in onRestoreFinished() callback + } + } -For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To -back up other file based storage, extend the `BackupRestoreFileStorage` class. + override fun onRestoreFinished() { + // TODO: Update state with the restored data. Use this callback instead of "restore()" in + // case the restore action involves several entities. + // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you + } +} +``` -Here is an example of customized datastore, which has a string to back up: +And this is a datastore with key value data: ```kotlin -class MyDataStore : ObservableBackupRestoreStorage() { - // Another option is make it a StringEntity type and maintain a String field inside StringEntity - @Volatile // backup/restore happens on Binder thread - var data: String? = null - private set - - fun setData(data: String?) { - this.data = data - notifyChange(ChangeReason.UPDATE) +class ExampleKeyValueStorage : + BackupRestoreStorage(), KeyedObservable<String> by KeyedDataObservable() { + // thread safe data structure + private val map = ConcurrentHashMap<String, String>() + + override val name: String + get() = "ExampleKeyValueStorage" + + fun updateData(key: String, value: String?) { + if (value != null) { + map[key] = value + notifyChange(ChangeReason.UPDATE) + } else { + map.remove(key) + notifyChange(ChangeReason.DELETE) } + } - override val name: String - get() = "MyData" - - override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = - listOf(StringEntity("data")) - - private inner class StringEntity(override val key: String) : BackupRestoreEntity { - override fun backup( - backupContext: BackupContext, - outputStream: OutputStream, - ) = - if (data != null) { - outputStream.write(data!!.toByteArray(UTF_8)) - EntityBackupResult.UPDATE - } else { - EntityBackupResult.DELETE - } - - override fun restore(restoreContext: RestoreContext, inputStream: InputStream) { - data = String(inputStream.readAllBytes(), UTF_8) - // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you + override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = + listOf(createMapBackupRestoreEntity()) + + private fun createMapBackupRestoreEntity() = + object : BackupRestoreEntity { + override val key: String + get() = "map" + + override fun backup( + backupContext: BackupContext, + outputStream: OutputStream, + ): EntityBackupResult { + // Use TreeMap to achieve predictable and stable order, so that data will not be + // updated to Android backup backend if there is only order change. + val copy = TreeMap(map) + if (copy.isEmpty()) return EntityBackupResult.DELETE + val dataOutputStream = DataOutputStream(outputStream) + dataOutputStream.writeInt(copy.size) + for ((key, value) in copy) { + dataOutputStream.writeUTF(key) + dataOutputStream.writeUTF(value) } - } + return EntityBackupResult.UPDATE + } - override fun onRestoreFinished() { - // TODO: Update state with the restored data. Use this callback instead "restore()" in case - // the restore action involves several entities. - // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you + override fun restore(restoreContext: RestoreContext, inputStream: InputStream) { + val dataInputString = DataInputStream(inputStream) + repeat(dataInputString.readInt()) { + val key = dataInputString.readUTF() + val value = dataInputString.readUTF() + map[key] = value + } + } } } ``` -In the application class: +All the datastore should be added in the application class: ```kotlin -class MyApplication : Application() { +class ExampleApplication : Application() { override fun onCreate() { - super.onCreate(); - BackupRestoreStorageManager.getInstance(this).add(MyDataStore()); + super.onCreate() + BackupRestoreStorageManager.getInstance(this) + .add(ExampleStorage(), ExampleKeyValueStorage()) } } ``` -In the custom `BackupAgentHelper` class: +Additionally, inject datastore to the custom `BackupAgentHelper` class: ```kotlin -class MyBackupAgentHelper : BackupAgentHelper() { +class ExampleBackupAgent : BackupAgentHelper() { override fun onCreate() { - BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this); + super.onCreate() + BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this) } override fun onRestoreFinished() { - BackupRestoreStorageManager.getInstance(this).onRestoreFinished(); + BackupRestoreStorageManager.getInstance(this).onRestoreFinished() } } ``` + +## Development + +Please preserve the code coverage ratio during development. The current line +coverage is **100% (444/444)** and branch coverage is **93.6% (176/188)**. diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt index 817ee4c56b19..6720e5c6d714 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt @@ -23,7 +23,11 @@ import java.io.IOException import java.io.InputStream import java.io.OutputStream -/** Entity for back up and restore. */ +/** + * Entity for back up and restore. + * + * Note that backup/restore callback is invoked on the binder thread. + */ interface BackupRestoreEntity { /** * Key of the entity. @@ -45,9 +49,12 @@ interface BackupRestoreEntity { /** * Backs up the entity. * + * Back up data in predictable order (e.g. use `TreeMap` instead of `HashMap`), otherwise data + * will be backed up needlessly. + * * @param backupContext context for backup * @param outputStream output stream to back up data - * @return false if backup file is deleted, otherwise true + * @return backup result */ @BinderThread @Throws(IOException::class) diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt index 935f9ccf6ed9..284c97b5ad6c 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt @@ -22,6 +22,7 @@ import android.app.backup.BackupDataOutput import android.app.backup.BackupHelper import android.os.ParcelFileDescriptor import android.util.Log +import androidx.annotation.BinderThread import androidx.annotation.VisibleForTesting import androidx.collection.MutableScatterMap import com.google.common.io.ByteStreams @@ -38,16 +39,22 @@ import java.util.zip.CRC32 import java.util.zip.CheckedInputStream import java.util.zip.CheckedOutputStream import java.util.zip.Checksum +import javax.annotation.concurrent.ThreadSafe internal const val LOG_TAG = "BackupRestoreStorage" /** - * Storage with backup and restore support. Subclass must implement either [Observable] or - * [KeyedObservable] interface. + * Storage with backup and restore support. + * + * Subclass MUST + * - implement either [Observable] or [KeyedObservable] interface. + * - be thread safe, backup/restore happens on Binder thread, while general data read/write + * operations occur on other threads. * * The storage is identified by a unique string [name] and data set is split into entities * ([BackupRestoreEntity]). */ +@ThreadSafe abstract class BackupRestoreStorage : BackupHelper { /** * A unique string used to disambiguate the various storages within backup agent. @@ -68,7 +75,7 @@ abstract class BackupRestoreStorage : BackupHelper { @VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null /** Entities to back up and restore. */ - abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> + @BinderThread abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> /** Default codec used to encode/decode the entity data. */ open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION @@ -134,7 +141,11 @@ abstract class BackupRestoreStorage : BackupHelper { Log.i(LOG_TAG, "[$name] Backup end") } - /** Returns if backup is enabled. */ + /** + * Returns if backup is enabled. + * + * If disabled, [performBackup] will be no-op, all entities backup are skipped. + */ open fun enableBackup(backupContext: BackupContext): Boolean = true open fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream): OutputStream { @@ -172,7 +183,11 @@ abstract class BackupRestoreStorage : BackupHelper { private fun ensureEntities(): List<BackupRestoreEntity> = entities ?: createBackupRestoreEntities().also { entities = it } - /** Returns if restore is enabled. */ + /** + * Returns if restore is enabled. + * + * If disabled, [restoreEntity] will be no-op, all entities restore are skipped. + */ open fun enableRestore(): Boolean = true open fun wrapRestoreInputStream( @@ -188,12 +203,13 @@ abstract class BackupRestoreStorage : BackupHelper { } final override fun writeNewStateDescription(newState: ParcelFileDescriptor) { + if (!enableRestore()) return entities = null // clear to reduce memory footprint newState.writeAndClearEntityStates() onRestoreFinished() } - /** Callbacks when restore finished. */ + /** Callbacks when entity data are all restored. */ open fun onRestoreFinished() {} @VisibleForTesting diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt index 99998ffc13ec..26534baaa47d 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt @@ -248,6 +248,15 @@ class BackupRestoreStorageTest { } @Test + fun writeNewStateDescription_restoreDisabled() { + val storage = spy(TestStorage().apply { enabled = false }) + temporaryFolder.newFile().toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use { + storage.writeNewStateDescription(it) + } + verify(storage, never()).onRestoreFinished() + } + + @Test fun backupAndRestore() { val storage = spy(TestStorage(entity1, entity2)) val backupAgentHelper = BackupAgentHelper() diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml index 4ced9f2469ab..cece9665b729 100644 --- a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml +++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml @@ -16,8 +16,8 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/> - <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/> - <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/> - <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected --> + <item android:state_pressed="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/> + <item android:state_selected="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/> + <item android:state_activated="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/> + <item android:color="@color/settingslib_materialColorSurfaceBright"/> <!-- not selected --> </selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml index 285ab7301162..eba9c2ceba70 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml @@ -19,12 +19,12 @@ <item android:left="?android:attr/listPreferredItemPaddingStart" android:right="?android:attr/listPreferredItemPaddingEnd" - android:top="1dp"> + android:top="2dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_preference_bg_color" /> <corners - android:radius="?android:attr/dialogCornerRadius" /> + android:radius="@dimen/settingslib_preference_corner_radius" /> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml index e417307edc3d..5c60f37a7244 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml @@ -19,15 +19,15 @@ <item android:left="?android:attr/listPreferredItemPaddingStart" android:right="?android:attr/listPreferredItemPaddingEnd" - android:top="1dp"> + android:top="2dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_preference_bg_color" /> <corners - android:topLeftRadius="0dp" - android:bottomLeftRadius="?android:attr/dialogCornerRadius" - android:topRightRadius="0dp" - android:bottomRightRadius="?android:attr/dialogCornerRadius" /> + android:topLeftRadius="4dp" + android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius" + android:topRightRadius="4dp" + android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" /> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml new file mode 100644 index 000000000000..de64efd23a0d --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="?android:attr/listPreferredItemPaddingStart" + android:right="?android:attr/listPreferredItemPaddingEnd" + android:top="2dp"> + <shape android:shape="rectangle"> + <solid + android:color="@color/settingslib_materialColorSurfaceContainerHigh" /> + <corners + android:topLeftRadius="4dp" + android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius" + android:topRightRadius="4dp" + android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml index e9646575663d..dd70f4f7a146 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml @@ -19,12 +19,12 @@ <item android:left="?android:attr/listPreferredItemPaddingStart" android:right="?android:attr/listPreferredItemPaddingEnd" - android:top="1dp"> + android:top="2dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_preference_bg_color" /> <corners - android:radius="1dp" /> + android:radius="4dp" /> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml new file mode 100644 index 000000000000..fffc6c8c4bef --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="?android:attr/listPreferredItemPaddingStart" + android:right="?android:attr/listPreferredItemPaddingEnd" + android:top="2dp"> + <shape android:shape="rectangle"> + <solid + android:color="@color/settingslib_materialColorSurfaceContainerHigh" /> + <corners + android:radius="4dp" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml new file mode 100644 index 000000000000..f83e3b177200 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="?android:attr/listPreferredItemPaddingStart" + android:right="?android:attr/listPreferredItemPaddingEnd" + android:top="2dp"> + <shape android:shape="rectangle"> + <solid + android:color="@color/settingslib_materialColorSurfaceContainerHigh" /> + <corners + android:radius="@dimen/settingslib_preference_corner_radius" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml index a9d69c264a2c..ab79d18a1ab3 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml @@ -19,15 +19,15 @@ <item android:left="?android:attr/listPreferredItemPaddingStart" android:right="?android:attr/listPreferredItemPaddingEnd" - android:top="1dp"> + android:top="2dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_preference_bg_color" /> <corners - android:topLeftRadius="?android:attr/dialogCornerRadius" - android:bottomLeftRadius="0dp" - android:topRightRadius="?android:attr/dialogCornerRadius" - android:bottomRightRadius="0dp" /> + android:topLeftRadius="@dimen/settingslib_preference_corner_radius" + android:bottomLeftRadius="4dp" + android:topRightRadius="@dimen/settingslib_preference_corner_radius" + android:bottomRightRadius="4dp" /> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml new file mode 100644 index 000000000000..112ec735aa27 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="?android:attr/listPreferredItemPaddingStart" + android:right="?android:attr/listPreferredItemPaddingEnd" + android:top="2dp"> + <shape android:shape="rectangle"> + <solid + android:color="@color/settingslib_materialColorSurfaceContainerHigh" /> + <corners + android:topLeftRadius="@dimen/settingslib_preference_corner_radius" + android:bottomLeftRadius="4dp" + android:topRightRadius="@dimen/settingslib_preference_corner_radius" + android:bottomRightRadius="4dp" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml index 221e8f51db89..94ff02d898db 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml @@ -37,8 +37,11 @@ <!-- Material next track off color--> <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color> + <!-- Dialog text button color. --> + <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color> + <!-- Dialog background color. --> - <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color> + <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</color> <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml index dc2d3dc696cb..8b9501608000 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml @@ -37,8 +37,11 @@ <!-- Material next track off color--> <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color> + <!-- Dialog text button color. --> + <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color> + <!-- Dialog background color. --> - <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color> + <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</color> <!-- Material next track outline color--> <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml new file mode 100644 index 000000000000..d783956ee240 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <dimen name="settingslib_preference_corner_radius">20dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index e7823df7ce0e..45667f55882d 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -41,7 +41,7 @@ subprojects { defaultConfig { minSdk = 21 - targetSdk = 34 + targetSdk = 35 } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt index da1ee77bcbfb..e867a8f0a8d1 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt @@ -21,6 +21,7 @@ import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.annotation.VisibleForTesting import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -30,7 +31,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier -import androidx.core.view.WindowCompat import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraphBuilder @@ -82,7 +82,7 @@ open class BrowseActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.Theme_SpaLib) super.onCreate(savedInstanceState) - WindowCompat.setDecorFitsSystemWindows(window, false) + enableEdgeToEdge() spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK) setContent { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index 36cd136602f3..9a344c3d7f14 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -35,6 +35,7 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalContentColor @@ -42,11 +43,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableFloatStateOf @@ -79,7 +80,12 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.roundToInt -@OptIn(ExperimentalMaterial3Api::class) +private val windowInsets: WindowInsets + @Composable + @NonRestartableComposable + get() = WindowInsets.safeDrawing + .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) + @Composable internal fun CustomizedTopAppBar( title: @Composable () -> Unit, @@ -91,7 +97,7 @@ internal fun CustomizedTopAppBar( titleTextStyle = MaterialTheme.typography.titleMedium, navigationIcon = navigationIcon, actions = actions, - windowInsets = TopAppBarDefaults.windowInsets, + windowInsets = windowInsets, colors = topAppBarColors(), ) } @@ -118,7 +124,7 @@ internal fun CustomizedLargeTopAppBar( navigationIcon = navigationIcon, actions = actions, colors = topAppBarColors(), - windowInsets = TopAppBarDefaults.windowInsets, + windowInsets = windowInsets, pinnedHeight = ContainerHeight, scrollBehavior = scrollBehavior, ) @@ -336,7 +342,7 @@ private fun TwoRowsTopAppBar( Modifier.draggable( orientation = Orientation.Vertical, state = rememberDraggableState { delta -> - scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta + scrollBehavior.state.heightOffset += delta }, onDragStopped = { velocity -> settleAppBar( @@ -411,6 +417,7 @@ private fun TwoRowsTopAppBar( * (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and * the actions are optional. * + * @param modifier a [Modifier] * @param heightPx the total height this layout is capped to * @param navigationIconContentColor the content color that will be applied via a * [LocalContentColor] when composing the navigation icon diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt index a49b358ca782..4a7937a3c2ac 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt @@ -22,9 +22,11 @@ import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.ExperimentalMaterial3Api @@ -92,6 +94,7 @@ fun SearchScaffold( ) }, containerColor = MaterialTheme.colorScheme.settingsBackground, + contentWindowInsets = WindowInsets.safeDrawing, ) { paddingValues -> Box( Modifier diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt index af7a14647570..4cf741e517be 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt @@ -23,7 +23,9 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -57,6 +59,7 @@ fun SettingsScaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { SettingsTopAppBar(title, scrollBehavior, actions) }, containerColor = MaterialTheme.colorScheme.settingsBackground, + contentWindowInsets = WindowInsets.safeDrawing, ) { paddingValues -> Box(Modifier.padding(paddingValues.horizontalValues())) { content(paddingValues.verticalValues()) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt index fc409302a2eb..4726dadc3688 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt @@ -21,7 +21,9 @@ import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -55,7 +57,10 @@ fun SuwScaffold( content: @Composable () -> Unit, ) { ActivityTitle(title) - Scaffold(containerColor = MaterialTheme.colorScheme.settingsBackground) { innerPadding -> + Scaffold( + containerColor = MaterialTheme.colorScheme.settingsBackground, + contentWindowInsets = WindowInsets.safeDrawing, + ) { innerPadding -> BoxWithConstraints( Modifier .padding(innerPadding) diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 4ea746007f76..363045ec1d83 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -214,6 +214,10 @@ <string name="bluetooth_battery_level_untethered_left">Left: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery</string> <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for the right part of the untethered headset. [CHAR LIMIT=NONE] --> <string name="bluetooth_battery_level_untethered_right">Right: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery</string> + <!-- Connected devices settings. Message when Bluetooth is connected, showing remote device battery level for the left part of the untethered headset. [CHAR LIMIT=NONE] --> + <string name="tv_bluetooth_battery_level_untethered_left">Left <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string> + <!-- Connected devices settings. Message when Bluetooth is connected, showing remote device battery level for the right part of the untethered headset. [CHAR LIMIT=NONE] --> + <string name="tv_bluetooth_battery_level_untethered_right">Right <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string> <!-- Connected devices settings. Message when Bluetooth is connected and active but no battery information, showing remote device status. [CHAR LIMIT=NONE] --> <string name="bluetooth_active_no_battery_level">Active</string> <!-- Connected devices settings. Message shown when bluetooth device is disconnected but is a known, previously connected device [CHAR LIMIT=NONE] --> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java index 57bde56b4c04..c3651423b487 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java @@ -128,7 +128,7 @@ public class RecentAppOpsAccess { final long now = mClock.millis(); final UserManager um = mContext.getSystemService(UserManager.class); final List<UserHandle> profiles = um.getUserProfiles(); - ArrayMap<UserHandle, Boolean> shouldIncludeAppsByUsers = new ArrayMap<>(); + ArrayMap<UserHandle, Boolean> shouldHideAppsByUsers = new ArrayMap<>(); for (int i = 0; i < appOpsCount; ++i) { AppOpsManager.PackageOps ops = appOps.get(i); @@ -136,13 +136,13 @@ public class RecentAppOpsAccess { int uid = ops.getUid(); UserHandle user = UserHandle.getUserHandleForUid(uid); - if (!shouldIncludeAppsByUsers.containsKey(user)) { - shouldIncludeAppsByUsers.put(user, shouldHideUser(um, user)); + if (!shouldHideAppsByUsers.containsKey(user)) { + shouldHideAppsByUsers.put(user, shouldHideUser(um, user)); } // Don't show apps belonging to background users except for profiles that shouldn't // be shown in quiet mode. - if (!profiles.contains(user) || !shouldIncludeAppsByUsers.get(user)) { + if (!profiles.contains(user) || shouldHideAppsByUsers.get(user)) { continue; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index c2a83b1e772f..0fec61c5affe 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1532,7 +1532,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> // the left. if (leftBattery >= 0) { String left = res.getString( - R.string.bluetooth_battery_level_untethered_left, + R.string.tv_bluetooth_battery_level_untethered_left, Utils.formatPercentage(leftBattery)); addBatterySpan(spannableBuilder, left, isBatteryLow(leftBattery, BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD), @@ -1543,7 +1543,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> spannableBuilder.append(" "); } String right = res.getString( - R.string.bluetooth_battery_level_untethered_right, + R.string.tv_bluetooth_battery_level_untethered_right, Utils.formatPercentage(rightBattery)); addBatterySpan(spannableBuilder, right, isBatteryLow(rightBattery, BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD), diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt new file mode 100644 index 000000000000..d69c87b318e2 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.satellite + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.OutcomeReceiver +import android.telephony.satellite.SatelliteManager +import android.util.Log +import android.view.WindowManager +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.android.settingslib.wifi.WifiUtils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.Default +import kotlinx.coroutines.Job +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeoutException +import kotlin.coroutines.resume + +/** A util for Satellite dialog */ +object SatelliteDialogUtils { + + /** + * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and + * Wifi during the satellite mode is on. + */ + @JvmStatic + fun mayStartSatelliteWarningDialog( + context: Context, + lifecycleOwner: LifecycleOwner, + type: Int, + allowClick: (isAllowed: Boolean) -> Unit + ): Job { + return mayStartSatelliteWarningDialog( + context, lifecycleOwner.lifecycleScope, type, allowClick) + } + + /** + * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and + * Wifi during the satellite mode is on. + */ + @JvmStatic + fun mayStartSatelliteWarningDialog( + context: Context, + coroutineScope: CoroutineScope, + type: Int, + allowClick: (isAllowed: Boolean) -> Unit + ): Job = + coroutineScope.launch { + var isSatelliteModeOn = false + try { + isSatelliteModeOn = requestIsEnabled(context) + } catch (e: InterruptedException) { + Log.w(TAG, "Error to get satellite status : $e") + } catch (e: ExecutionException) { + Log.w(TAG, "Error to get satellite status : $e") + } catch (e: TimeoutException) { + Log.w(TAG, "Error to get satellite status : $e") + } + + if (isSatelliteModeOn) { + startSatelliteWarningDialog(context, type) + } + withContext(Dispatchers.Main) { + allowClick(!isSatelliteModeOn) + } + } + + private fun startSatelliteWarningDialog(context: Context, type: Int) { + context.startActivity(Intent(Intent.ACTION_MAIN).apply { + component = ComponentName( + "com.android.settings", + "com.android.settings.network.SatelliteWarningDialogActivity" + ) + putExtra(WifiUtils.DIALOG_WINDOW_TYPE, + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) + putExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, type) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) + }) + } + + /** + * Checks if the satellite modem is enabled. + * + * @param executor The executor to run the asynchronous operation on + * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled, + * `false` otherwise. + */ + private suspend fun requestIsEnabled( + context: Context, + ): Boolean = withContext(Default) { + val satelliteManager: SatelliteManager? = + context.getSystemService(SatelliteManager::class.java) + if (satelliteManager == null) { + Log.w(TAG, "SatelliteManager is null") + return@withContext false + } + + suspendCancellableCoroutine {continuation -> + satelliteManager?.requestIsEnabled(Default.asExecutor(), + object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> { + override fun onResult(result: Boolean) { + Log.i(TAG, "Satellite modem enabled status: $result") + continuation.resume(result) + } + + override fun onError(error: SatelliteManager.SatelliteException) { + super.onError(error) + Log.w(TAG, "Can't get satellite modem enabled status", error) + continuation.resume(false) + } + }) + } + } + + const val TAG = "SatelliteDialogUtils" + + const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String = + "extra_type_of_satellite_warning_dialog" + const val TYPE_IS_UNKNOWN = -1 + const val TYPE_IS_WIFI = 0 + const val TYPE_IS_BLUETOOTH = 1 + const val TYPE_IS_AIRPLANE_MODE = 2 +}
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt index 2a44511599f1..a939ed14b7c1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt @@ -17,6 +17,7 @@ package com.android.settingslib.statusbar.notification.data.repository import android.app.NotificationManager +import android.provider.Settings import com.android.settingslib.statusbar.notification.data.model.ZenMode import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -28,10 +29,14 @@ class FakeNotificationsSoundPolicyRepository : NotificationsSoundPolicyRepositor override val notificationPolicy: StateFlow<NotificationManager.Policy?> get() = mutableNotificationPolicy.asStateFlow() - private val mutableZenMode = MutableStateFlow<ZenMode?>(null) + private val mutableZenMode = MutableStateFlow<ZenMode?>(ZenMode(Settings.Global.ZEN_MODE_OFF)) override val zenMode: StateFlow<ZenMode?> get() = mutableZenMode.asStateFlow() + init { + updateNotificationPolicy() + } + fun updateNotificationPolicy(policy: NotificationManager.Policy?) { mutableNotificationPolicy.value = policy } @@ -48,13 +53,14 @@ fun FakeNotificationsSoundPolicyRepository.updateNotificationPolicy( suppressedVisualEffects: Int = NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET, state: Int = NotificationManager.Policy.STATE_UNSET, priorityConversationSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE, -) = updateNotificationPolicy( - NotificationManager.Policy( - priorityCategories, - priorityCallSenders, - priorityMessageSenders, - suppressedVisualEffects, - state, - priorityConversationSenders, +) = + updateNotificationPolicy( + NotificationManager.Policy( + priorityCategories, + priorityCallSenders, + priorityMessageSenders, + suppressedVisualEffects, + state, + priorityConversationSenders, + ) ) -)
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt index 65a5317ed0cb..36e396fb0c4f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt @@ -72,7 +72,11 @@ interface AudioRepository { suspend fun setVolume(audioStream: AudioStream, volume: Int) - suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) + /** + * Mutes and un-mutes [audioStream]. Returns true when the state changes and false the + * otherwise. + */ + suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) @@ -164,14 +168,20 @@ class AudioRepositoryImpl( audioManager.setStreamVolume(audioStream.value, volume, 0) } - override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) = - withContext(backgroundCoroutineContext) { - audioManager.adjustStreamVolume( - audioStream.value, - if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE, - 0, - ) + override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean { + return withContext(backgroundCoroutineContext) { + if (isMuted == audioManager.isStreamMute(audioStream.value)) { + false + } else { + audioManager.adjustStreamVolume( + audioStream.value, + if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE, + 0, + ) + true + } } + } override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) { withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt index 33f917e701c2..0e5ebdae96e4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt @@ -25,6 +25,7 @@ import com.android.settingslib.volume.shared.model.RingerMode import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map /** Provides audio stream state and an ability to change it */ @@ -46,8 +47,16 @@ class AudioVolumeInteractor( val ringerMode: StateFlow<RingerMode> get() = audioRepository.ringerMode - suspend fun setVolume(audioStream: AudioStream, volume: Int) = + suspend fun setVolume(audioStream: AudioStream, volume: Int) { + val streamModel = getAudioStream(audioStream).first() + val oldVolume = streamModel.volume audioRepository.setVolume(audioStream, volume) + when { + volume == streamModel.minVolume -> setMuted(audioStream, true) + oldVolume == streamModel.minVolume && volume > streamModel.minVolume -> + setMuted(audioStream, false) + } + } suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) { if (audioStream.value == AudioManager.STREAM_RING) { @@ -55,7 +64,16 @@ class AudioVolumeInteractor( if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL audioRepository.setRingerMode(audioStream, RingerMode(mode)) } - audioRepository.setMuted(audioStream, isMuted) + val mutedChanged = audioRepository.setMuted(audioStream, isMuted) + if (mutedChanged && !isMuted) { + with(getAudioStream(audioStream).first()) { + if (volume == minVolume) { + // Slightly increase volume when user un-mutes the stream that is lowered + // down to its minimum + setVolume(audioStream, volume + 1) + } + } + } } /** Checks if the volume can be changed via the UI. */ diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index f87b5190c7a1..e125083488ed 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -41,7 +41,10 @@ android_app { //########################################################### android_robolectric_test { name: "SettingsLibRoboTests", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], static_libs: [ "Settings_robolectric_meta_service_file", "Robolectric_shadows_androidx_fragment_upstream", diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index b9bf9caddac7..0d814947527c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -780,9 +780,8 @@ public class CachedBluetoothDeviceTest { mBatteryLevel = 10; // Act & Assert: - // Get "Left: 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( - "Left: 10% battery"); + // Get "Left 10%" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Left 10%"); } @Test @@ -815,9 +814,9 @@ public class CachedBluetoothDeviceTest { mBatteryLevel = 10; // Act & Assert: - // Get "Left: 10% battery" result with Battery Level 10. + // Get "Left 10%" result with Battery Level 10. assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( - "Left: 10% battery"); + "Left 10%"); } @Test @@ -925,9 +924,9 @@ public class CachedBluetoothDeviceTest { mBatteryLevel = 10; // Act & Assert: - // Get "Left: 10% battery Right: 10% battery" result with Battery Level 10. + // Get "Left 10% Right 10%" result with Battery Level 10. assertThat(mCachedDevice.getTvConnectionSummary().toString()) - .isEqualTo("Left: 10% battery Right: 10% battery"); + .isEqualTo("Left 10% Right 10%"); } @Test @@ -1226,7 +1225,7 @@ public class CachedBluetoothDeviceTest { TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( - "Left: 15% battery Right: 25% battery"); + "Left 15% Right 25%"); } @Test @@ -1262,11 +1261,7 @@ public class CachedBluetoothDeviceTest { TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getTvConnectionSummary().toString()) - .isEqualTo( - mContext.getString(R.string.bluetooth_battery_level_untethered_left, "15%") - + " " - + mContext.getString( - R.string.bluetooth_battery_level_untethered_right, "25%")); + .isEqualTo("Left 15% Right 25%"); } @Test @@ -1283,10 +1278,8 @@ public class CachedBluetoothDeviceTest { .thenReturn(TWS_BATTERY_RIGHT.getBytes()); int lowBatteryColor = mContext.getColor(LOW_BATTERY_COLOR); - String leftBattery = - mContext.getString(R.string.bluetooth_battery_level_untethered_left, "15%"); - String rightBattery = - mContext.getString(R.string.bluetooth_battery_level_untethered_right, "25%"); + String leftBattery = "Left 15%"; + String rightBattery = "Right 25%"; // Default low battery threshold, only left battery is low CharSequence summary = mCachedDevice.getTvConnectionSummary(LOW_BATTERY_COLOR); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt new file mode 100644 index 000000000000..aeda1ed66d16 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.satellite + +import android.content.Context +import android.content.Intent +import android.os.OutcomeReceiver +import android.platform.test.annotations.RequiresFlagsEnabled +import android.telephony.satellite.SatelliteManager +import android.telephony.satellite.SatelliteManager.SatelliteException +import android.util.AndroidRuntimeException +import androidx.test.core.app.ApplicationProvider +import com.android.internal.telephony.flags.Flags +import com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.`when` +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.verify +import org.mockito.internal.verification.Times +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class SatelliteDialogUtilsTest { + @JvmField + @Rule + val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Spy + var context: Context = ApplicationProvider.getApplicationContext() + @Mock + private lateinit var satelliteManager: SatelliteManager + + private val coroutineScope = CoroutineScope(Dispatchers.Main) + + @Before + fun setUp() { + `when`(context.getSystemService(SatelliteManager::class.java)) + .thenReturn(satelliteManager) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking { + `when`( + satelliteManager.requestIsEnabled( + any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>() + ) + ) + .thenAnswer { invocation -> + val receiver = invocation + .getArgument< + OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>( + 1 + ) + receiver.onResult(true) + null + } + + try { + SatelliteDialogUtils.mayStartSatelliteWarningDialog( + context, coroutineScope, TYPE_IS_WIFI, allowClick = { + assertTrue(it) + }) + } catch (e: AndroidRuntimeException) { + // Catch exception of starting activity . + } + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking { + `when`( + satelliteManager.requestIsEnabled( + any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>() + ) + ) + .thenAnswer { invocation -> + val receiver = invocation + .getArgument< + OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>( + 1 + ) + receiver.onResult(false) + null + } + + + SatelliteDialogUtils.mayStartSatelliteWarningDialog( + context, coroutineScope, TYPE_IS_WIFI, allowClick = { + assertFalse(it) + }) + + verify(context, Times(0)).startActivity(any<Intent>()) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking { + `when`(context.getSystemService(SatelliteManager::class.java)) + .thenReturn(null) + + SatelliteDialogUtils.mayStartSatelliteWarningDialog( + context, coroutineScope, TYPE_IS_WIFI, allowClick = { + assertFalse(it) + }) + + verify(context, Times(0)).startActivity(any<Intent>()) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking { + `when`( + satelliteManager.requestIsEnabled( + any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>() + ) + ) + .thenAnswer { invocation -> + val receiver = invocation + .getArgument< + OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>( + 1 + ) + receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR)) + null + } + + + SatelliteDialogUtils.mayStartSatelliteWarningDialog( + context, coroutineScope, TYPE_IS_WIFI, allowClick = { + assertFalse(it) + }) + + verify(context, Times(0)).startActivity(any<Intent>()) + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index be3f4108fdd1..888e39593a2e 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -274,6 +274,9 @@ public class SecureSettings { Settings.Secure.SCREEN_RESOLUTION_MODE, Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, - Settings.Secure.CHARGE_OPTIMIZATION_MODE + Settings.Secure.CHARGE_OPTIMIZATION_MODE, + Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, + Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, + Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index b1feede57506..b992ddc8a397 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -18,6 +18,7 @@ package android.provider.settings.validators; import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.ANY_LONG_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; @@ -433,5 +434,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, new InclusiveIntegerRangeValidator(0, 10)); VALIDATORS.put(Secure.CHARGE_OPTIMIZATION_MODE, new InclusiveIntegerRangeValidator(0, 10)); + VALIDATORS.put(Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR); + VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR); + VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java index 677c81ad9271..255b1ad3b3d2 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java @@ -239,6 +239,18 @@ public class SettingsValidators { } }; + static final Validator ANY_LONG_VALIDATOR = value -> { + if (value == null) { + return true; + } + try { + Long.parseLong(value); + return true; + } catch (NumberFormatException e) { + return false; + } + }; + static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() { @Override public boolean validate(String value) { diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 46bf494f2b1a..374240bb7262 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -347,6 +347,7 @@ <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" /> <uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" /> <uses-permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" /> + <uses-permission android:name="android.permission.DELIVER_COMPANION_MESSAGES" /> <uses-permission android:name="android.permission.MANAGE_APPOPS" /> <uses-permission android:name="android.permission.WATCH_APPOPS" /> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index c04ec4f61c89..8b60ed035d07 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -78,11 +78,69 @@ filegroup { visibility: ["//visibility:private"], } +filegroup { + name: "SystemUI-tests-broken-robofiles-run", + srcs: [ + "tests/src/**/systemui/util/LifecycleFragmentTest.java", + "tests/src/**/systemui/util/TestableAlertDialogTest.kt", + "tests/src/**/systemui/util/kotlin/PairwiseFlowTest", + "tests/src/**/systemui/util/sensors/AsyncManagerTest.java", + "tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java", + "tests/src/**/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java", + "tests/src/**/systemui/statusbar/KeyboardShortcutListSearchTest.java", + "tests/src/**/systemui/statusbar/KeyboardShortcutsTest.java", + "tests/src/**/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt", + "tests/src/**/systemui/statusbar/notification/AssistantFeedbackControllerTest.java", + "tests/src/**/systemui/statusbar/notification/collection/NotifCollectionTest.java", + "tests/src/**/systemui/statusbar/notification/collection/NotificationEntryTest.java", + "tests/src/**/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt", + "tests/src/**/systemui/statusbar/notification/collection/ShadeListBuilderTest.java", + "tests/src/**/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java", + "tests/src/**/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java", + "tests/src/**/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt", + "tests/src/**/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt", + "tests/src/**/systemui/statusbar/NotificationLockscreenUserManagerTest.java", + "tests/src/**/systemui/statusbar/notification/logging/NotificationLoggerTest.java", + "tests/src/**/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java", + "tests/src/**/systemui/statusbar/notification/row/NotificationContentInflaterTest.java", + "tests/src/**/systemui/statusbar/notification/row/NotificationContentViewTest.kt", + "tests/src/**/systemui/statusbar/notification/row/NotificationConversationInfoTest.java", + "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerTest.java", + "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt", + "tests/src/**/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt", + "tests/src/**/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt", + "tests/src/**/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java", + "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java", + "tests/src/**/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt", + "tests/src/**/systemui/statusbar/phone/AutoTileManagerTest.java", + "tests/src/**/systemui/statusbar/phone/CentralSurfacesImplTest.java", + "tests/src/**/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java", + "tests/src/**/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt", + "tests/src/**/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt", + "tests/src/**/systemui/statusbar/phone/PhoneStatusBarView.java", + "tests/src/**/systemui/statusbar/phone/PhoneStatusBarViewTest.kt", + "tests/src/**/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt", + "tests/src/**/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt", + "tests/src/**/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt", + "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt", + "tests/src/**/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt", + "tests/src/**/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt", + "tests/src/**/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt", + "tests/src/**/systemui/statusbar/policy/CallbackControllerTest.java", + "tests/src/**/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java", + "tests/src/**/systemui/statusbar/policy/InflatedSmartRepliesTest.java", + "tests/src/**/systemui/statusbar/policy/LocationControllerImplTest.java", + "tests/src/**/systemui/statusbar/policy/RemoteInputViewTest.java", + "tests/src/**/systemui/statusbar/policy/SmartReplyViewTest.java", + "tests/src/**/systemui/statusbar/StatusBarStateControllerImplTest.kt", + ], +} + // We are running robolectric tests in the tests directory as well as // multivalent tests. If you add a test, and it doesn't run in robolectric, // it should be added to this exclusion list. go/multivalent-tests filegroup { - name: "SystemUI-tests-broken-robofiles", + name: "SystemUI-tests-broken-robofiles-compile", srcs: [ "tests/src/**/*DeviceOnlyTest.java", "tests/src/**/*DeviceOnlyTest.kt", @@ -703,7 +761,8 @@ android_robolectric_test { ":SystemUI-tests-robofiles", ], exclude_srcs: [ - ":SystemUI-tests-broken-robofiles", + ":SystemUI-tests-broken-robofiles-compile", + ":SystemUI-tests-broken-robofiles-run", ], static_libs: [ "RoboTestLibraries", diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b9e70ef14007..9c58371a387d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -826,20 +826,6 @@ </intent-filter> </activity> - <activity - android:name=".contrast.ContrastDialogActivity" - android:label="@string/quick_settings_contrast_label" - android:theme="@style/Theme.SystemUI.ContrastDialog" - android:finishOnCloseSystemDialogs="true" - android:launchMode="singleInstance" - android:excludeFromRecents="true" - android:exported="true"> - <intent-filter> - <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </activity> - <activity android:name=".ForegroundServicesDialog" android:process=":fgservices" android:excludeFromRecents="true" diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 755fe2a4476a..55edff6d9518 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -4,6 +4,13 @@ container: "system" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. flag { + name: "create_windowless_window_magnifier" + namespace: "accessibility" + description: "Uses SurfaceControlViewHost to create the magnifier for window magnification." + bug: "280992417" +} + +flag { name: "delay_show_magnification_button" namespace: "accessibility" description: "Delays the showing of magnification mode switch button." @@ -66,8 +73,11 @@ flag { } flag { - name: "create_windowless_window_magnifier" + name: "save_and_restore_magnification_settings_buttons" namespace: "accessibility" - description: "Uses SurfaceControlViewHost to create the magnifier for window magnification." - bug: "280992417" + description: "Saves the selected button status in magnification settings and restore the status when revisiting the same smallest screen DP." + bug: "325567876" + metadata { + purpose: PURPOSE_BUGFIX + } } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 204429b53674..f9e955bf0366 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -26,14 +26,6 @@ flag { } flag { - - name: "notification_heads_up_cycling" - namespace: "systemui" - description: "Heads-up notification cycling animation for the Notification Avalanche feature." - bug: "316404716" -} - -flag { name: "priority_people_section" namespace: "systemui" description: "Add a new section for priority people (aka important conversations)." @@ -204,7 +196,16 @@ flag { description: "Re-enable the codepath that removed tinting of notifications when the" " standard background color is desired. This was the behavior before we discovered" " a resources threading issue, which we worked around by tinting the notification" - " backgrounds and footer buttons." + " backgrounds." + bug: "294830092" +} + +flag { + name: "notification_footer_background_tint_optimization" + namespace: "systemui" + description: "Remove duplicative tinting of notification footer buttons. This was the behavior" + " before we discovered a resources threading issue, which we worked around by applying the" + " same color as a tint to the background drawable of footer buttons." bug: "294830092" } @@ -355,6 +356,14 @@ flag { } flag { + name: "status_bar_screen_sharing_chips" + namespace: "systemui" + description: "Show chips on the left side of the status bar when a user is screen sharing, " + "recording, or casting" + bug: "332662551" +} + +flag { name: "compose_bouncer" namespace: "systemui" description: "Use the new compose bouncer in SystemUI" @@ -405,6 +414,16 @@ flag { } flag { + name: "fix_image_wallpaper_crash_surface_already_released" + namespace: "systemui" + description: "Make sure ImageWallpaper doesn't return from OnSurfaceDestroyed until any drawing is finished" + bug: "337287154" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "activity_transition_use_largest_window" namespace: "systemui" description: "Target largest opening window during activity transitions." @@ -486,6 +505,15 @@ flag { } } +flag { + name: "screenshot_scroll_crop_view_crash_fix" + namespace: "systemui" + description: "Mitigate crash on invalid computed range in CropView" + bug: "232633995" + metadata { + purpose: PURPOSE_BUGFIX + } +} flag { name: "screenshot_private_profile_behavior_fix" @@ -862,6 +890,9 @@ flag { namespace: "systemui" description: "Enforce BaseUserRestriction for DISALLOW_CONFIG_BRIGHTNESS." bug: "329205638" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -920,3 +951,20 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "validate_keyboard_shortcut_helper_icon_uri" + namespace: "systemui" + description: "Adds a check that the caller can access the content URI of an icon in the shortcut helper." + bug: "331180422" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "glanceable_hub_gesture_handle" + namespace: "systemui" + description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub" + bug: "339667383" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 8ee8ea4b4f4c..feb1f5b17bef 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -2,16 +2,24 @@ package com.android.systemui.communal.ui.compose import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey @@ -26,6 +34,7 @@ import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions +import com.android.systemui.Flags import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.ui.compose.extensions.allowGestures @@ -88,6 +97,8 @@ fun CommunalContainer( val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank) val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false) + val showGestureIndicator by + viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false) val state: MutableSceneTransitionLayoutState = remember { MutableSceneTransitionLayoutState( initialScene = currentSceneKey, @@ -126,7 +137,19 @@ fun CommunalContainer( ) ) { // This scene shows nothing only allowing for transitions to the communal scene. - Box(modifier = Modifier.fillMaxSize()) + // TODO(b/339667383): remove this temporary swipe gesture handle + Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.End) { + if (showGestureIndicator && Flags.glanceableHubGestureHandle()) { + Box( + modifier = + Modifier.height(220.dp) + .width(4.dp) + .align(Alignment.CenterVertically) + .background(color = Color.White, RoundedCornerShape(4.dp)) + ) + Spacer(modifier = Modifier.width(12.dp)) + } + } } scene( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 2a52c60c820e..cd27d5713c2d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -48,6 +48,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight @@ -87,8 +88,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter @@ -120,9 +119,10 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.times import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup -import androidx.core.view.setPadding import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowMetricsCalculator import com.android.compose.modifiers.thenIf @@ -427,8 +427,8 @@ private fun BoxScope.CommunalHubLazyGrid( state = gridState, rows = GridCells.Fixed(CommunalContentSize.FULL.span), contentPadding = contentPadding, - horizontalArrangement = Arrangement.spacedBy(32.dp), - verticalArrangement = Arrangement.spacedBy(32.dp), + horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), + verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), ) { items( count = list.size, @@ -441,7 +441,7 @@ private fun BoxScope.CommunalHubLazyGrid( Dimensions.CardWidth.value, list[index].size.dp().value, ) - val cardModifier = Modifier.size(width = size.width.dp, height = size.height.dp) + val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp) if (viewModel.isEditMode && dragDropState != null) { val selected by remember(index) { derivedStateOf { list[index].key == selectedKey.value } } @@ -795,12 +795,10 @@ private fun CtaTileInViewModeContent( containerColor = colors.primary, contentColor = colors.onPrimary, ), - shape = RoundedCornerShape(80.dp, 40.dp, 80.dp, 40.dp) + shape = RoundedCornerShape(68.dp, 34.dp, 68.dp, 34.dp) ) { Column( - modifier = Modifier.fillMaxSize().padding(horizontal = 82.dp), - verticalArrangement = - Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically), + modifier = Modifier.fillMaxSize().padding(vertical = 38.dp, horizontal = 70.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { Icon( @@ -808,11 +806,13 @@ private fun CtaTileInViewModeContent( contentDescription = stringResource(R.string.cta_label_to_open_widget_picker), modifier = Modifier.size(Dimensions.IconSize), ) + Spacer(modifier = Modifier.size(6.dp)) Text( text = stringResource(R.string.cta_label_to_edit_widget), - style = MaterialTheme.typography.titleLarge, + style = MaterialTheme.typography.titleMedium, textAlign = TextAlign.Center, ) + Spacer(modifier = Modifier.size(20.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, @@ -828,9 +828,10 @@ private fun CtaTileInViewModeContent( ) { Text( text = stringResource(R.string.cta_tile_button_to_dismiss), + fontSize = 12.sp, ) } - Spacer(modifier = Modifier.size(Dimensions.Spacing)) + Spacer(modifier = Modifier.size(14.dp)) Button( colors = ButtonDefaults.buttonColors( @@ -842,6 +843,7 @@ private fun CtaTileInViewModeContent( ) { Text( text = stringResource(R.string.cta_tile_button_to_open_widget_editor), + fontSize = 12.sp, ) } } @@ -927,10 +929,14 @@ private fun WidgetContent( model.appWidgetHost .createViewForCommunal(context, model.appWidgetId, model.providerInfo) .apply { - updateAppWidgetSize(Bundle.EMPTY, listOf(size)) - // Remove the extra padding applied to AppWidgetHostView to allow widgets to - // occupy the entire box. - setPadding(0) + updateAppWidgetSize( + /* newOptions = */ Bundle(), + /* minWidth = */ size.width.toInt(), + /* minHeight = */ size.height.toInt(), + /* maxWidth = */ size.width.toInt(), + /* maxHeight = */ size.height.toInt(), + /* ignorePadding = */ true + ) accessibilityDelegate = viewModel.widgetAccessibilityDelegate } }, @@ -1153,7 +1159,11 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl @Composable private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues { if (!isEditMode || toolbarSize == null) { - return PaddingValues(start = 48.dp, end = 48.dp, top = Dimensions.GridTopSpacing) + return PaddingValues( + start = Dimensions.ItemSpacing, + end = Dimensions.ItemSpacing, + top = Dimensions.GridTopSpacing, + ) } val context = LocalContext.current val density = LocalDensity.current @@ -1216,18 +1226,19 @@ data class ContentPaddingInPx(val start: Float, val top: Float) { } object Dimensions { - val CardWidth = 424.dp - val CardHeightFull = 596.dp - val CardHeightHalf = 282.dp - val CardHeightThird = 177.33.dp - val CardOutlineWidth = 3.dp - val GridTopSpacing = 64.dp + val CardHeightFull = 530.dp + val GridTopSpacing = 114.dp val GridHeight = CardHeightFull + GridTopSpacing - val Spacing = 16.dp + val ItemSpacing = 50.dp + val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2 + val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3 + val CardWidth = 360.dp + val CardOutlineWidth = 3.dp + val Spacing = ItemSpacing / 2 // The sizing/padding of the toolbar in glanceable hub edit mode val ToolbarPaddingTop = 27.dp - val ToolbarPaddingHorizontal = 16.dp + val ToolbarPaddingHorizontal = ItemSpacing val ToolbarButtonPaddingHorizontal = 24.dp val ToolbarButtonPaddingVertical = 16.dp val ButtonPadding = @@ -1235,10 +1246,7 @@ object Dimensions { vertical = ToolbarButtonPaddingVertical, horizontal = ToolbarButtonPaddingHorizontal, ) - val IconSize = 48.dp - - val LockIconSize = 52.dp - val LockIconBottomPadding = 70.dp + val IconSize = 40.dp } private object Colors { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt index 6d8c47d84850..ca4ff837daa6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel @@ -60,6 +61,6 @@ constructor( } val blueprint = blueprintByBlueprintId[blueprintId] ?: return - with(blueprint) { Content(modifier) } + with(blueprint) { Content(modifier.sysuiResTag("keyguard_root_view")) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index abff93d15c55..a39fa64dd45b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.unit.IntRect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection @@ -129,7 +130,7 @@ constructor( with(lockSection) { LockIcon() } // Aligned to bottom and constrained to below the lock icon. - Column(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.fillMaxWidth().sysuiResTag("keyguard_bottom_area")) { if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { with(ambientIndicationSectionOptional.get()) { AmbientIndication(modifier = Modifier.fillMaxWidth()) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index 88b8298335aa..067315381773 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -36,7 +36,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.modifiers.thenIf -import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene @@ -80,7 +79,7 @@ constructor( } SceneTransitionLayout( - modifier = modifier.sysuiResTag("keyguard_clock_container"), + modifier = modifier, currentScene = currentScene, onChangeScene = {}, transitions = ClockTransition.defaultClockTransitions, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index cb3867f209e3..271eb9601dbd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -78,12 +78,7 @@ fun VolumeSlider( } state.a11yStateDescription?.let { stateDescription = it } - ?: run { - // provide a not animated value to the a11y because it fails to announce - // the settled value when it changes rapidly. - progressBarRangeInfo = - ProgressBarRangeInfo(state.value, state.valueRange) - } + progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) } else { disabled() contentDescription = diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py index bac3553e7498..95a25c58bc67 100755 --- a/packages/SystemUI/flag_check.py +++ b/packages/SystemUI/flag_check.py @@ -12,19 +12,20 @@ following case-sensitive regex: %s The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags. - -As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the -flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|STAGING|TEAMFOOD|TRUNKFOOD|NEXTFOOD). +As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the flag. +For legacy flags use EXEMPT with your flag name. Some examples below: -Flag: NONE -Flag: NA -Flag: LEGACY ENABLE_ONE_SEARCH DISABLED -Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT -Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD +Flag: NONE Repohook Update +Flag: TEST_ONLY +Flag: EXEMPT resource only update +Flag: EXEMPT bugfix +Flag: EXEMPT refactor +Flag: com.android.launcher3.enable_twoline_allapps +Flag: com.google.android.apps.nexuslauncher.zero_state_web_data_loader -Check the git history for more examples. It's a regex matched field. +Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats. """ def main(): @@ -63,28 +64,31 @@ def main(): return field = 'Flag' - none = '(NONE|NA|N\/A)' # NONE|NA|N/A - - typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG] + none = 'NONE' + testOnly = 'TEST_ONLY' + docsOnly = 'DOCS_ONLY' + exempt = 'EXEMPT' + justification = '<justification>' - # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH - # Aconfig Flag name format = "packageName"."flagName" + # Aconfig Flag name format = <packageName>.<flagName> # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3 - # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check. + # For now alphabets, digits, "_", "." characters are allowed in flag name. + # Checks if there is "one dot" between packageName and flagName and not adding stricter format check #common_typos_disable - flagName = '([a-zA-z0-9_.])+' + flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)' - #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|STAGING|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)] - stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|STAGING|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)' + # None and Exempt needs justification + exemptRegex = fr'{exempt}\s*[a-zA-Z]+' + noneRegex = fr'{none}\s*[a-zA-Z]+' #common_typos_enable - readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|STAGING|TRUNKFOOD|NEXTFOOD' + readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: <packageName>.<flagName>\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly flagRegex = fr'^{field}: .*$' check_flag = re.compile(flagRegex) #Flag: # Ignore case for flag name format. - flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*' + flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\s*' check_flagName = re.compile(flagNameRegex) #Flag: <flag name format> flagError = False diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java index 11a42413c4ff..27bffd0818e7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java @@ -18,10 +18,8 @@ package com.android.systemui.ambient.touch; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.view.GestureDetector; import android.view.MotionEvent; @@ -30,6 +28,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -37,7 +36,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -51,89 +49,66 @@ public class ShadeTouchHandlerTest extends SysuiTestCase { CentralSurfaces mCentralSurfaces; @Mock + ShadeViewController mShadeViewController; + + @Mock TouchHandler.TouchSession mTouchSession; ShadeTouchHandler mTouchHandler; - @Captor - ArgumentCaptor<GestureDetector.OnGestureListener> mGestureListenerCaptor; - @Captor - ArgumentCaptor<InputChannelCompat.InputEventListener> mInputListenerCaptor; - private static final int TOUCH_HEIGHT = 20; @Before public void setup() { MockitoAnnotations.initMocks(this); - - mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), TOUCH_HEIGHT); - } - - // Verifies that a swipe down in the gesture region is captured by the shade touch handler. - @Test - public void testSwipeDown_captured() { - final boolean captured = swipe(Direction.DOWN); - - assertThat(captured).isTrue(); + mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController, + TOUCH_HEIGHT); } - // Verifies that a swipe in the upward direction is not catpured. + /** + * Verify that touches aren't handled when the bouncer is showing. + */ @Test - public void testSwipeUp_notCaptured() { - final boolean captured = swipe(Direction.UP); - - // Motion events not captured as the swipe is going in the wrong direction. - assertThat(captured).isFalse(); + public void testInactiveOnBouncer() { + when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); + mTouchHandler.onSessionStart(mTouchSession); + verify(mTouchSession).pop(); } - // Verifies that a swipe down forwards captured touches to the shade window for handling. + /** + * Make sure {@link ShadeTouchHandler} + */ @Test - public void testSwipeDown_sentToShadeWindow() { - swipe(Direction.DOWN); + public void testTouchPilferingOnScroll() { + final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class); + final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class); - // Both motion events are sent for the shade window to process. - verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any()); - } + final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor = + ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); - // Verifies that a swipe down is not forwarded to the shade window. - @Test - public void testSwipeUp_touchesNotSent() { - swipe(Direction.UP); + mTouchHandler.onSessionStart(mTouchSession); + verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture()); - // Motion events are not sent for the shade window to process as the swipe is going in the - // wrong direction. - verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any()); + assertThat(gestureListenerArgumentCaptor.getValue() + .onScroll(motionEvent1, motionEvent2, 1, 1)) + .isTrue(); } /** - * Simulates a swipe in the given direction and returns true if the touch was intercepted by the - * touch handler's gesture listener. - * <p> - * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge - * of the gesture region, {@link #TOUCH_HEIGHT}, and goes upward to 0. + * Ensure touches are propagated to the {@link ShadeViewController}. */ - private boolean swipe(Direction direction) { - Mockito.clearInvocations(mTouchSession); - mTouchHandler.onSessionStart(mTouchSession); - - verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture()); - verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture()); - - final float startY = direction == Direction.UP ? TOUCH_HEIGHT : 0; - final float endY = direction == Direction.UP ? 0 : TOUCH_HEIGHT; + @Test + public void testEventPropagation() { + final MotionEvent motionEvent = Mockito.mock(MotionEvent.class); - // Send touches to the input and gesture listener. - final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, startY, 0); - final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, endY, 0); - mInputListenerCaptor.getValue().onInputEvent(event1); - mInputListenerCaptor.getValue().onInputEvent(event2); - final boolean captured = mGestureListenerCaptor.getValue().onScroll(event1, event2, 0, - startY - endY); + final ArgumentCaptor<InputChannelCompat.InputEventListener> + inputEventListenerArgumentCaptor = + ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class); - return captured; + mTouchHandler.onSessionStart(mTouchSession); + verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture()); + inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent); + verify(mShadeViewController).handleExternalTouch(motionEvent); } - private enum Direction { - DOWN, UP, - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index ecfcc90982c0..a5acf724dcff 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -66,15 +66,12 @@ class BouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val authenticationInteractor by lazy { kosmos.authenticationInteractor } - private val bouncerInteractor by lazy { kosmos.bouncerInteractor } - private val sceneContainerStartable = kosmos.sceneContainerStartable private lateinit var underTest: BouncerViewModel @Before fun setUp() { - sceneContainerStartable.start() + kosmos.sceneContainerStartable.start() underTest = kosmos.bouncerViewModel } @@ -164,11 +161,11 @@ class BouncerViewModelTest : SysuiTestCase() { assertThat(isInputEnabled).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { - bouncerInteractor.authenticate(WRONG_PIN) + kosmos.bouncerInteractor.authenticate(WRONG_PIN) } assertThat(isInputEnabled).isFalse() - val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0 + val lockoutEndMs = kosmos.authenticationInteractor.lockoutEndTimestamp ?: 0 advanceTimeBy(lockoutEndMs - testScope.currentTime) assertThat(isInputEnabled).isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt index 312c14df9505..fec56ed919de 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt @@ -18,9 +18,13 @@ package com.android.systemui.brightness.data.repository import android.content.applicationContext import android.os.UserManager -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import androidx.test.filters.SmallTest import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin +import com.android.systemui.Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher @@ -40,14 +44,18 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class BrightnessPolicyRepositoryImplTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class BrightnessPolicyRepositoryImplTest(flags: FlagsParameterization) : SysuiTestCase() { - private val kosmos = testKosmos() + init { + mSetFlagsRule.setFlagsParameterization(flags) + } - private val fakeUserRepository = kosmos.fakeUserRepository + private val kosmos = testKosmos() private val mockUserRestrictionChecker: UserRestrictionChecker = mock { whenever(checkIfRestrictionEnforced(any(), anyString(), anyInt())).thenReturn(null) @@ -130,7 +138,83 @@ class BrightnessPolicyRepositoryImplTest : SysuiTestCase() { } } - private companion object { - val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS + @Test + @DisableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION) + fun brightnessBaseUserRestriction_flagOff_noRestriction() = + with(kosmos) { + testScope.runTest { + whenever( + mockUserRestrictionChecker.hasBaseUserRestriction( + any(), + eq(RESTRICTION), + eq(userRepository.getSelectedUserInfo().id) + ) + ) + .thenReturn(true) + + val restrictions by collectLastValue(underTest.restrictionPolicy) + + assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction) + } + } + + @Test + fun bothRestrictions_returnsSetEnforcedAdminFromCheck() = + with(kosmos) { + testScope.runTest { + val enforcedAdmin: EnforcedAdmin = + EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION) + + whenever( + mockUserRestrictionChecker.checkIfRestrictionEnforced( + any(), + eq(RESTRICTION), + eq(userRepository.getSelectedUserInfo().id) + ) + ) + .thenReturn(enforcedAdmin) + + whenever( + mockUserRestrictionChecker.hasBaseUserRestriction( + any(), + eq(RESTRICTION), + eq(userRepository.getSelectedUserInfo().id) + ) + ) + .thenReturn(true) + + val restrictions by collectLastValue(underTest.restrictionPolicy) + + assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin)) + } + } + + @Test + @EnableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION) + fun brightnessBaseUserRestriction_flagOn_emptyRestriction() = + with(kosmos) { + testScope.runTest { + whenever( + mockUserRestrictionChecker.hasBaseUserRestriction( + any(), + eq(RESTRICTION), + eq(userRepository.getSelectedUserInfo().id) + ) + ) + .thenReturn(true) + + val restrictions by collectLastValue(underTest.restrictionPolicy) + + assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin())) + } + } + + companion object { + private const val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt index 85a4bcf62223..11f523846268 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt @@ -48,7 +48,6 @@ class BrightnessPolicyEnforcementInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val mockActivityStarter = kosmos.activityStarter - private val fakeBrightnessPolicyEnforcementInteractor = kosmos.fakeBrightnessPolicyRepository private val underTest = with(kosmos) { @@ -70,7 +69,18 @@ class BrightnessPolicyEnforcementInteractorTest : SysuiTestCase() { fakeBrightnessPolicyRepository.setCurrentUserRestricted() - assertThat(restriction).isInstanceOf(PolicyRestriction.Restricted::class.java) + assertThat(restriction) + .isEqualTo( + PolicyRestriction.Restricted( + EnforcedAdmin.createDefaultEnforcedAdminWithRestriction( + BrightnessPolicyRepository.RESTRICTION + ) + ) + ) + + fakeBrightnessPolicyRepository.setBaseUserRestriction() + + assertThat(restriction).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin())) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index b4b812d60a1a..0ab09595d6b0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -265,7 +265,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is dreaming and on communal. - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -282,7 +282,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is not dreaming and on communal. - fakeKeyguardRepository.setDreaming(false) + updateDreaming(false) communalInteractor.changeScene(CommunalScenes.Communal) // Scene stays as Communal @@ -297,7 +297,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is dreaming and on communal. - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -309,7 +309,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Dream stops, timeout is cancelled and device stays on hub, because the regular // screen timeout will take effect at this point. - fakeKeyguardRepository.setDreaming(false) + updateDreaming(false) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) } @@ -320,7 +320,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is on communal, but not dreaming. - fakeKeyguardRepository.setDreaming(false) + updateDreaming(false) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -328,7 +328,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Wait a bit, but not long enough to timeout, then start dreaming. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Device times out after one screen timeout interval, dream doesn't reset timeout. @@ -338,11 +338,31 @@ class CommunalSceneStartableTest : SysuiTestCase() { } @Test + fun hubTimeout_dreamAfterInitialTimeout_goesToBlank() = + with(kosmos) { + testScope.runTest { + // Device is on communal. + communalInteractor.changeScene(CommunalScenes.Communal) + + // Device stays on the hub after the timeout since we're not dreaming. + advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) + val scene by collectLastValue(communalInteractor.desiredScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + // Start dreaming. + updateDreaming(true) + + // Hub times out immediately. + assertThat(scene).isEqualTo(CommunalScenes.Blank) + } + } + + @Test fun hubTimeout_userActivityTriggered_resetsTimeout() = with(kosmos) { testScope.runTest { // Device is dreaming and on communal. - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -371,7 +391,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) // Device is dreaming and on communal. - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -395,6 +415,12 @@ class CommunalSceneStartableTest : SysuiTestCase() { runCurrent() } + private fun TestScope.updateDreaming(dreaming: Boolean) = + with(kosmos) { + fakeKeyguardRepository.setDreaming(dreaming) + runCurrent() + } + private suspend fun TestScope.enableCommunal() = with(kosmos) { setCommunalAvailable(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 766798c2c2c3..83227e1fc597 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -204,14 +204,14 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun isCommunalAvailable_whenDreaming_true() = + fun isCommunalAvailable_whenKeyguardShowing_true() = testScope.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() keyguardRepository.setIsEncryptedOrLockdown(false) userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setDreaming(true) + keyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 2d079d7488d0..be44339bab8d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -51,6 +51,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState @@ -141,6 +142,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { testScope, context.resources, kosmos.keyguardTransitionInteractor, + kosmos.keyguardInteractor, kosmos.communalInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index 8bfa5cff8b97..f5c86e092a26 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -31,8 +31,11 @@ import android.app.DreamManager; import android.content.res.Resources; import android.graphics.Region; import android.os.Handler; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper.RunWithLooper; import android.view.AttachedSurfaceControl; +import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; @@ -42,6 +45,7 @@ import androidx.test.filters.SmallTest; import com.android.dream.lowlight.LowLightTransitionCoordinator; import com.android.keyguard.BouncerPanelExpansionCalculator; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor; @@ -94,6 +98,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { ViewGroup mDreamOverlayContentView; @Mock + View mHubGestureIndicatorView; + + @Mock Handler mHandler; @Mock @@ -142,6 +149,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mDreamOverlayContainerView, mComplicationHostViewController, mDreamOverlayContentView, + mHubGestureIndicatorView, mDreamOverlayStatusBarViewController, mLowLightTransitionCoordinator, mBlurUtils, @@ -161,6 +169,18 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mDreamManager); } + @DisableFlags(Flags.FLAG_COMMUNAL_HUB) + @Test + public void testHubGestureIndicatorGoneWhenFlagOff() { + verify(mHubGestureIndicatorView, never()).setVisibility(View.VISIBLE); + } + + @EnableFlags({Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE}) + @Test + public void testHubGestureIndicatorVisibleWhenFlagOn() { + verify(mHubGestureIndicatorView).setVisibility(View.VISIBLE); + } + @Test public void testRootSurfaceControlInsetSetOnAttach() { mController.onViewAttached(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java index e3c6deed1527..29fbee01a18b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java @@ -108,7 +108,7 @@ public class CommunalTouchHandlerTest extends SysuiTestCase { mTouchHandler.onSessionStart(mTouchSession); verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture()); inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent); - verify(mCentralSurfaces).handleExternalShadeWindowTouch(motionEvent); + verify(mCentralSurfaces).handleDreamTouch(motionEvent); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt new file mode 100644 index 000000000000..1e7ed6316f7f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 android.hardware.fingerprint.FingerprintManager +import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl +import com.android.systemui.coroutines.collectLastValue +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +@SmallTest +class FingerprintPropertyRepositoryTest : SysuiTestCase() { + @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() + private val testScope = TestScope() + private lateinit var underTest: FingerprintPropertyRepositoryImpl + @Mock private lateinit var fingerprintManager: FingerprintManager + @Captor + private lateinit var fingerprintCallbackCaptor: + ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> + + @Before + fun setup() { + underTest = + FingerprintPropertyRepositoryImpl( + testScope.backgroundScope, + Dispatchers.Main.immediate, + fingerprintManager, + ) + } + + @Test + fun propertiesInitialized_onStartFalse() = + testScope.runTest { + val propertiesInitialized by collectLastValue(underTest.propertiesInitialized) + assertThat(propertiesInitialized).isFalse() + } + + @Test + fun propertiesInitialized_onStartTrue() = + testScope.runTest { + // // collect sensorType to update fingerprintCallback before + // propertiesInitialized + // // is listened for + val sensorType by collectLastValue(underTest.sensorType) + runCurrent() + captureFingerprintCallback() + + fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList()) + val propertiesInitialized by collectLastValue(underTest.propertiesInitialized) + assertThat(propertiesInitialized).isTrue() + } + + @Test + fun propertiesInitialized_updatedToTrue() = + testScope.runTest { + val propertiesInitialized by collectLastValue(underTest.propertiesInitialized) + assertThat(propertiesInitialized).isFalse() + + captureFingerprintCallback() + fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList()) + assertThat(propertiesInitialized).isTrue() + } + + private fun captureFingerprintCallback() { + verify(fingerprintManager) + .addAuthenticatorsRegisteredCallback(fingerprintCallbackCaptor.capture()) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index cb2d4e07e3a2..addbdb664c77 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -60,17 +60,19 @@ class KeyguardInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val repository = kosmos.fakeKeyguardRepository - private val sceneInteractor = kosmos.sceneInteractor - private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor - private val commandQueue = kosmos.fakeCommandQueue - private val configRepository = kosmos.fakeConfigurationRepository - private val bouncerRepository = kosmos.keyguardBouncerRepository - private val shadeRepository = kosmos.shadeRepository - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val repository by lazy { kosmos.fakeKeyguardRepository } + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor } + private val commandQueue by lazy { kosmos.fakeCommandQueue } + private val configRepository by lazy { kosmos.fakeConfigurationRepository } + private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository } + private val shadeRepository by lazy { kosmos.shadeRepository } + private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } + private val transitionState: MutableStateFlow<ObservableTransitionState> = MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone)) - private val underTest = kosmos.keyguardInteractor + + private val underTest by lazy { kosmos.keyguardInteractor } @Before fun setUp() { @@ -275,6 +277,28 @@ class KeyguardInteractorTest : SysuiTestCase() { } @Test + fun keyguardTranslationY_whenNotGoneAndShadeIsReesetEmitsZero() = + testScope.runTest { + val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY) + + configRepository.setDimensionPixelSize( + R.dimen.keyguard_translate_distance_on_swipe_up, + 100 + ) + configRepository.onAnyConfigurationChange() + + shadeRepository.setLegacyShadeExpansion(1f) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + assertThat(keyguardTranslationY).isEqualTo(0f) + } + + @Test fun keyguardTranslationY_whenTransitioningToGoneAndShadeIsExpandingEmitsNonZero() = testScope.runTest { val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index bf0939c6c46f..99cccb282264 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -19,9 +19,13 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING @@ -29,36 +33,74 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertThrows +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @kotlinx.coroutines.ExperimentalCoroutinesApi -@android.platform.test.annotations.EnabledOnRavenwood class KeyguardTransitionInteractorTest : SysuiTestCase() { val kosmos = testKosmos() val underTest = kosmos.keyguardTransitionInteractor val repository = kosmos.fakeKeyguardTransitionRepository val testScope = kosmos.testScope + private val sceneTransitions = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + + private val lsToGone = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Gone, + flowOf(Scenes.Lockscreen), + flowOf(0f), + false, + flowOf(false) + ) + + private val goneToLs = + ObservableTransitionState.Transition( + Scenes.Gone, + Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), + flowOf(0f), + false, + flowOf(false) + ) + + @Before + fun setUp() { + kosmos.sceneContainerRepository.setTransitionState(sceneTransitions) + } + @Test fun transitionCollectorsReceivesOnlyAppropriateEvents() = testScope.runTest { - val lockscreenToAodSteps by collectValues(underTest.transition(LOCKSCREEN, AOD)) - val aodToLockscreenSteps by collectValues(underTest.transition(AOD, LOCKSCREEN)) + val lockscreenToAodSteps by + collectValues(underTest.transition(Edge.create(LOCKSCREEN, AOD))) + val aodToLockscreenSteps by + collectValues(underTest.transition(Edge.create(AOD, LOCKSCREEN))) val steps = mutableListOf<TransitionStep>() steps.add(TransitionStep(AOD, GONE, 0f, STARTED)) @@ -482,6 +524,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun isInTransitionToState() = testScope.runTest { val results by collectValues(underTest.isInTransitionToState(GONE)) @@ -586,7 +629,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, STARTED), + TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED), ) assertThat(results) @@ -598,7 +641,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, RUNNING), + TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING), ) assertThat(results) @@ -610,7 +653,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, FINISHED), + TransitionStep(DOZING, LOCKSCREEN, 0f, FINISHED), ) assertThat(results) @@ -623,9 +666,9 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(GONE, DOZING, 0f, STARTED), - TransitionStep(GONE, DOZING, 0f, RUNNING), - TransitionStep(GONE, DOZING, 1f, FINISHED), + TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED), + TransitionStep(LOCKSCREEN, DOZING, 0f, RUNNING), + TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED), ) assertThat(results) @@ -638,8 +681,8 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, STARTED), - TransitionStep(DOZING, GONE, 0f, RUNNING), + TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED), + TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING), ) assertThat(results) @@ -1404,6 +1447,143 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) } + @Test + @DisableSceneContainer + fun transition_no_conversion_with_flag_off() = + testScope.runTest { + val currentStates by + collectValues(underTest.transition(Edge.create(PRIMARY_BOUNCER, GONE))) + + val sendStep1 = TransitionStep(PRIMARY_BOUNCER, GONE, 0f, STARTED) + sendSteps(sendStep1) + + assertEquals(listOf(sendStep1), currentStates) + } + + @Test + @EnableSceneContainer + fun transition_conversion_with_flag_on() = + testScope.runTest { + val currentStates by + collectValues(underTest.transition(Edge.create(PRIMARY_BOUNCER, GONE))) + + val sendStep1 = TransitionStep(PRIMARY_BOUNCER, GONE, 0f, STARTED) + sendSteps(sendStep1) + + assertEquals(listOf<TransitionStep>(), currentStates) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_values_with_sceneContainer_in_correct_state() = + testScope.runTest { + val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, GONE))) + val currentStatesConverted by + collectValues(underTest.transition(Edge.create(LOCKSCREEN, UNDEFINED))) + + sceneTransitions.value = lsToGone + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3) + + assertEquals(listOf(sendStep1, sendStep2), currentStates) + assertEquals(listOf(sendStep1, sendStep2), currentStatesConverted) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_nothing_with_sceneContainer_in_wrong_state() = + testScope.runTest { + val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, GONE))) + + sceneTransitions.value = goneToLs + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3) + + assertEquals(listOf<TransitionStep>(), currentStates) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_values_when_edge_within_lockscreen_scene() = + testScope.runTest { + val currentStates by + collectValues(underTest.transition(Edge.create(LOCKSCREEN, DOZING))) + + sceneTransitions.value = goneToLs + val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED) + val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3) + + assertEquals(listOf(sendStep1, sendStep2), currentStates) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_values_with_null_edge_within_lockscreen_scene() = + testScope.runTest { + val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, null))) + val currentStatesReversed by + collectValues(underTest.transition(Edge.create(null, LOCKSCREEN))) + + sceneTransitions.value = goneToLs + val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED) + val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED) + val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3, sendStep4) + + assertEquals(listOf(sendStep1, sendStep2, sendStep3), currentStates) + assertEquals(listOf(sendStep4), currentStatesReversed) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_values_with_null_edge_out_of_lockscreen_scene() = + testScope.runTest { + val currentStates by collectValues(underTest.transition(Edge.create(null, UNDEFINED))) + val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE))) + + sceneTransitions.value = lsToGone + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED) + val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3, sendStep4) + + assertEquals(listOf(sendStep1, sendStep2), currentStates) + assertEquals(listOf(sendStep1, sendStep2), currentStatesMapped) + } + + @Test + @EnableSceneContainer + fun transition_conversion_does_not_emit_with_null_edge_with_wrong_stl_state() = + testScope.runTest { + val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE))) + + sceneTransitions.value = goneToLs + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED) + val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3, sendStep4) + + assertEquals(listOf<TransitionStep>(), currentStatesMapped) + } + + @Test + @EnableSceneContainer + fun transition_null_edges_throw() = + testScope.runTest { + assertThrows(IllegalStateException::class.java) { + underTest.transition(Edge.create(null, null)) + } + } + private suspend fun sendSteps(vararg steps: TransitionStep) { steps.forEach { repository.sendTransitionStep(it) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt index 0ac7ff5232a3..a0fed6bbfc2c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt @@ -23,11 +23,13 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.milliseconds @@ -50,11 +52,14 @@ class KeyguardTransitionAnimationFlowTest : SysuiTestCase() { @Before fun setUp() { underTest = - animationFlow.setup( - duration = 1000.milliseconds, - from = GONE, - to = DREAMING, - ) + animationFlow + .setup( + duration = 1000.milliseconds, + edge = Edge.create(from = Scenes.Gone, to = DREAMING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = DREAMING), + ) } @Test(expected = IllegalArgumentException::class) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt index d63293675034..7a9bd924afa4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt @@ -87,6 +87,7 @@ class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() = testScope.runTest { val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) @@ -137,6 +138,7 @@ class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun scrimBehindAlpha_leaveShadeOpen() = testScope.runTest { val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) @@ -161,6 +163,7 @@ class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun showAllNotifications_isTrue_whenLeaveShadeOpen() = testScope.runTest { val showAllNotifications by @@ -177,6 +180,7 @@ class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun showAllNotifications_isFalse_whenLeaveShadeIsNotOpen() = testScope.runTest { val showAllNotifications by @@ -193,6 +197,7 @@ class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(330311871) fun scrimBehindAlpha_doNotLeaveShadeOpen() = testScope.runTest { val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 838b2a79adff..20ffa3312fa6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -30,6 +30,8 @@ import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.parameterizeSceneContainerFlag import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -37,7 +39,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor import com.android.systemui.statusbar.phone.dozeParameters @@ -49,6 +53,8 @@ import com.android.systemui.util.ui.stopAnimating import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -75,6 +81,11 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() private val viewState = ViewStateAccessor() + private val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + companion object { @JvmStatic @Parameters(name = "{0}") @@ -96,6 +107,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, ) } + kosmos.sceneContainerRepository.setTransitionState(transitionState) } @Test @@ -309,6 +321,32 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test + @EnableSceneContainer + fun alpha_transitionToHub_isZero_scene_container() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Communal, + emptyFlow(), + emptyFlow(), + false, + emptyFlow() + ) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + testScope, + ) + + assertThat(alpha).isEqualTo(0f) + } + + @Test + @DisableSceneContainer fun alpha_transitionToHub_isZero() = testScope.runTest { val alpha by collectLastValue(underTest.alpha(viewState)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index 58c6817c4270..1c1fcc450d73 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -18,8 +18,10 @@ package com.android.systemui.keyguard.ui.viewmodel import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -30,11 +32,16 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -58,6 +65,11 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza private val keyguardRepository = kosmos.fakeKeyguardRepository private lateinit var underTest: LockscreenToPrimaryBouncerTransitionViewModel + private val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + companion object { @JvmStatic @Parameters(name = "{0}") @@ -76,6 +88,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza } @Test + @BrokenWithSceneContainer(330311871) fun deviceEntryParentViewAlpha_shadeExpanded() = testScope.runTest { val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) @@ -107,6 +120,17 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza shadeExpanded(false) runCurrent() + kosmos.sceneContainerRepository.setTransitionState(transitionState) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Bouncer, + emptyFlow(), + emptyFlow(), + false, + emptyFlow() + ) + runCurrent() // fade out repository.sendTransitionStep(step(0f, TransitionState.STARTED)) runCurrent() @@ -132,7 +156,9 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza ): TransitionStep { return TransitionStep( from = KeyguardState.LOCKSCREEN, - to = KeyguardState.PRIMARY_BOUNCER, + to = + if (SceneContainerFlag.isEnabled) KeyguardState.UNDEFINED + else KeyguardState.PRIMARY_BOUNCER, value = value, transitionState = state, ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt index bd3b77a678db..365a7c3a296a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt @@ -45,16 +45,16 @@ import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.notificationLockscreenUserManager import com.android.systemui.statusbar.policy.keyguardStateController import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @@ -121,7 +121,7 @@ class MediaControlInteractorTest : SysuiTestCase() { whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())) .thenReturn(true) - val clickIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) } + val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } val expandable = mock<Expandable>() underTest.startClickIntent(expandable, clickIntent) @@ -133,7 +133,7 @@ class MediaControlInteractorTest : SysuiTestCase() { fun startClickIntent_hideOverLockscreen() { whenever(keyguardStateController.isShowing).thenReturn(false) - val clickIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) } + val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } val expandable = mock<Expandable>() val activityController = mock<ActivityTransitionAnimator.Controller>() whenever(expandable.activityTransitionController(any())).thenReturn(activityController) @@ -150,7 +150,7 @@ class MediaControlInteractorTest : SysuiTestCase() { whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())) .thenReturn(true) - val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) } + val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } underTest.startDeviceIntent(deviceIntent) @@ -163,7 +163,7 @@ class MediaControlInteractorTest : SysuiTestCase() { whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())) .thenReturn(true) - val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(false) } + val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(false) } underTest.startDeviceIntent(deviceIntent) @@ -174,7 +174,7 @@ class MediaControlInteractorTest : SysuiTestCase() { fun startDeviceIntent_hideOverLockscreen() { whenever(keyguardStateController.isShowing).thenReturn(false) - val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) } + val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } underTest.startDeviceIntent(deviceIntent) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt index 4226a9d89ad1..0551bfb89865 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt @@ -109,7 +109,7 @@ class MediaCarouselViewModelTest : SysuiTestCase() { assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) - underTest.onAttached() + underTest.onReorderingAllowed() mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt index 5661bd388757..9d8ec951dfe7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt @@ -51,8 +51,8 @@ class NotificationsShadeSceneViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor - private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } private val underTest = kosmos.notificationsShadeSceneViewModel diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt index bf48784407b8..02a81419ea78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt @@ -69,6 +69,15 @@ class QSTileIntentUserInputHandlerTest : SysuiTestCase() { } @Test + fun testPassesIntentToStarter_dismissShadeAndShowOverLockScreenWhenLocked() { + val intent = Intent("test.ACTION") + + underTest.handle(null, intent, true) + + verify(activityStarter).startActivity(eq(intent), eq(true), any(), eq(true)) + } + + @Test fun testPassesActivityPendingIntentToStarterAsPendingIntent() { val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt new file mode 100644 index 000000000000..c41ce2f7854c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.domain.interactor + +import android.content.Intent +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.Callback +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class QRCodeScannerTileDataInteractorTest : SysuiTestCase() { + + private val testUser = UserHandle.of(1)!! + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val testIntent = mock<Intent>() + private val qrCodeScannerController = + mock<QRCodeScannerController> { + whenever(intent).thenReturn(testIntent) + whenever(isAbleToLaunchScannerActivity).thenReturn(false) + } + private val testAvailableModel = QRCodeScannerTileModel.Available(testIntent) + private val testUnavailableModel = QRCodeScannerTileModel.TemporarilyUnavailable + + private val underTest: QRCodeScannerTileDataInteractor = + QRCodeScannerTileDataInteractor( + testDispatcher, + scope.backgroundScope, + qrCodeScannerController, + ) + + @Test + fun availability_matchesController_cameraNotAvailable() = + scope.runTest { + val expectedAvailability = false + whenever(qrCodeScannerController.isCameraAvailable).thenReturn(false) + + val availability by collectLastValue(underTest.availability(testUser)) + + assertThat(availability).isEqualTo(expectedAvailability) + } + + @Test + fun availability_matchesController_cameraIsAvailable() = + scope.runTest { + val expectedAvailability = true + whenever(qrCodeScannerController.isCameraAvailable).thenReturn(true) + + val availability by collectLastValue(underTest.availability(testUser)) + + assertThat(availability).isEqualTo(expectedAvailability) + } + + @Test + fun data_matchesController() = + scope.runTest { + val captor = argumentCaptor<Callback>() + val lastData by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + + verify(qrCodeScannerController).addCallback(captor.capture()) + val callback = captor.value + + assertThat(lastData!!).isEqualTo(testUnavailableModel) + + whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(true) + callback.onQRCodeScannerActivityChanged() + runCurrent() + assertThat(lastData!!).isEqualTo(testAvailableModel) + + whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(false) + callback.onQRCodeScannerActivityChanged() + runCurrent() + assertThat(lastData!!).isEqualTo(testUnavailableModel) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..312f18029570 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.domain.interactor + +import android.content.Intent +import android.platform.test.annotations.EnabledOnRavenwood +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.impl.qr.qrCodeScannerTileUserActionInteractor +import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class QRCodeScannerTileUserActionInteractorTest : SysuiTestCase() { + val kosmos = Kosmos() + private val inputHandler = kosmos.qsTileIntentUserInputHandler + private val underTest = kosmos.qrCodeScannerTileUserActionInteractor + private val intent = mock<Intent>() + + @Test + fun handleClick_available() = runTest { + val inputModel = QRCodeScannerTileModel.Available(intent) + + underTest.handleInput(QSTileInputTestKtx.click(inputModel)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + intent + } + } + + @Test + fun handleClick_temporarilyUnavailable() = runTest { + val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable + + underTest.handleInput(QSTileInputTestKtx.click(inputModel)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs() + } + + @Test + fun handleLongClick_available() = runTest { + val inputModel = QRCodeScannerTileModel.Available(intent) + + underTest.handleInput(QSTileInputTestKtx.longClick(inputModel)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs() + } + + @Test + fun handleLongClick_temporarilyUnavailable() = runTest { + val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable + + underTest.handleInput(QSTileInputTestKtx.longClick(inputModel)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt new file mode 100644 index 000000000000..d26a21365f54 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.ui + +import android.content.Intent +import android.graphics.drawable.TestStubDrawable +import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.impl.qr.qsQRCodeScannerTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.util.mockito.mock +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class QRCodeScannerTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val config = kosmos.qsQRCodeScannerTileConfig + + private lateinit var mapper: QRCodeScannerTileMapper + + @Before + fun setup() { + mapper = + QRCodeScannerTileMapper( + context.orCreateTestableResources + .apply { + addOverride( + com.android.systemui.res.R.drawable.ic_qr_code_scanner, + TestStubDrawable() + ) + } + .resources, + context.theme + ) + } + + @Test + fun availableModel() { + val mockIntent = mock<Intent>() + val inputModel = QRCodeScannerTileModel.Available(mockIntent) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createQRCodeScannerTileState( + QSTileState.ActivationState.INACTIVE, + null, + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun temporarilyUnavailableModel() { + val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createQRCodeScannerTileState( + QSTileState.ActivationState.UNAVAILABLE, + context.getString( + com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createQRCodeScannerTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String?, + ): QSTileState { + val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title) + return QSTileState( + { + Icon.Loaded( + context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!, + null + ) + }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK), + label, + null, + QSTileState.SideViewIcon.Chevron, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt index c75e297f23c8..e3108ad1b8f1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt @@ -45,11 +45,11 @@ class SceneBackInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor - private val sceneContainerStartable = kosmos.sceneContainerStartable - private val authenticationInteractor = kosmos.authenticationInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val sceneContainerStartable by lazy { kosmos.sceneContainerStartable } + private val authenticationInteractor by lazy { kosmos.authenticationInteractor } - private val underTest = kosmos.sceneBackInteractor + private val underTest by lazy { kosmos.sceneBackInteractor } @Test @EnableSceneContainer diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt index e11a8f1e0ba9..851b7b986527 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt @@ -52,9 +52,9 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor - private val sceneInteractor = kosmos.sceneInteractor - private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor + private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val shadeAnimationInteractor by lazy { kosmos.shadeAnimationInteractor } private val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(Scenes.Lockscreen) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index da17366a8416..82e2bb719818 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags @@ -107,18 +108,25 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S val testScope = kosmos.testScope val configurationRepository get() = kosmos.fakeConfigurationRepository + val keyguardRepository get() = kosmos.fakeKeyguardRepository + val keyguardInteractor get() = kosmos.keyguardInteractor + val keyguardRootViewModel get() = kosmos.keyguardRootViewModel + val keyguardTransitionRepository get() = kosmos.fakeKeyguardTransitionRepository + val shadeTestUtil get() = kosmos.shadeTestUtil + val sharedNotificationContainerInteractor get() = kosmos.sharedNotificationContainerInteractor + val largeScreenHeaderHelper get() = kosmos.mockLargeScreenHeaderHelper @@ -814,6 +822,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S } @Test + @BrokenWithSceneContainer(330311871) fun alphaDoesNotUpdateWhileGoneTransitionIsRunning() = testScope.runTest { val viewState = ViewStateAccessor() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt index 1cd12f043a72..7bc6948edf31 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt @@ -35,6 +35,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify @@ -46,30 +47,32 @@ class DozeServiceHostCoroutinesTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneContainerRepository = kosmos.sceneContainerRepository - private val keyguardInteractor = kosmos.keyguardInteractor + lateinit var underTest: DozeServiceHost - val underTest = - kosmos.dozeServiceHost.apply { - initialize( - /* centralSurfaces = */ mock(), - /* statusBarKeyguardViewManager = */ mock(), - /* notificationShadeWindowViewController = */ mock(), - /* ambientIndicationContainer = */ mock(), - ) - } + @Before + fun setup() { + underTest = + kosmos.dozeServiceHost.apply { + initialize( + /* centralSurfaces = */ mock(), + /* statusBarKeyguardViewManager = */ mock(), + /* notificationShadeWindowViewController = */ mock(), + /* ambientIndicationContainer = */ mock(), + ) + } + } @Test @EnableSceneContainer fun startStopDozing() = testScope.runTest { - val isDozing by collectLastValue(keyguardInteractor.isDozing) + val isDozing by collectLastValue(kosmos.keyguardInteractor.isDozing) // GIVEN a callback is set val callback: DozeHost.Callback = mock() underTest.addCallback(callback) // AND we are on the lock screen - sceneContainerRepository.changeScene(Scenes.Lockscreen) + kosmos.sceneContainerRepository.changeScene(Scenes.Lockscreen) // AND dozing is not requested yet assertThat(underTest.dozingRequested).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt index 675136c7cf26..a163ca08691e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt @@ -36,7 +36,6 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -47,19 +46,8 @@ class AudioVolumeInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() - private lateinit var underTest: AudioVolumeInteractor - - @Before - fun setup() { - with(kosmos) { - underTest = AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor) - - audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_NORMAL)) - - notificationsSoundPolicyRepository.updateNotificationPolicy() - notificationsSoundPolicyRepository.updateZenMode(ZenMode(Settings.Global.ZEN_MODE_OFF)) - } - } + private val underTest: AudioVolumeInteractor = + with(kosmos) { AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor) } @Test fun setMuted_mutesStream() { @@ -236,6 +224,55 @@ class AudioVolumeInteractorTest : SysuiTestCase() { } } + @Test + fun testReducingVolumeToMin_mutes() = + with(kosmos) { + testScope.runTest { + val audioStreamModel by + collectLastValue(audioRepository.getAudioStream(audioStream)) + runCurrent() + + underTest.setVolume(audioStream, audioStreamModel!!.minVolume) + runCurrent() + + assertThat(audioStreamModel!!.isMuted).isTrue() + } + } + + @Test + fun testIncreasingVolumeFromMin_unmutes() = + with(kosmos) { + testScope.runTest { + val audioStreamModel by + collectLastValue(audioRepository.getAudioStream(audioStream)) + audioRepository.setMuted(audioStream, true) + audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume) + runCurrent() + + underTest.setVolume(audioStream, audioStreamModel!!.maxVolume) + runCurrent() + + assertThat(audioStreamModel!!.isMuted).isFalse() + } + } + + @Test + fun testUnmutingMinVolume_increasesVolume() = + with(kosmos) { + testScope.runTest { + val audioStreamModel by + collectLastValue(audioRepository.getAudioStream(audioStream)) + audioRepository.setMuted(audioStream, true) + audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume) + runCurrent() + + underTest.setMuted(audioStream, false) + runCurrent() + + assertThat(audioStreamModel!!.volume).isGreaterThan(audioStreamModel!!.minVolume) + } + } + private companion object { val audioStream = AudioStream(AudioManager.STREAM_SYSTEM) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt index 64c9429cbe25..46df0c227f1c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt @@ -16,17 +16,16 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor -import android.os.Handler import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.volume.localMediaController import com.android.systemui.volume.mediaControllerRepository +import com.android.systemui.volume.mediaDeviceSessionInteractor import com.android.systemui.volume.mediaOutputInteractor import com.android.systemui.volume.panel.shared.model.filterData import com.android.systemui.volume.remoteMediaController @@ -55,12 +54,7 @@ class MediaDeviceSessionInteractorTest : SysuiTestCase() { listOf(localMediaController, remoteMediaController) ) - underTest = - MediaDeviceSessionInteractor( - testScope.testScheduler, - Handler(TestableLooper.get(kosmos.testCase).looper), - mediaControllerRepository, - ) + underTest = mediaDeviceSessionInteractor } } diff --git a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml index a751f58344a9..370677ac0890 100644 --- a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml +++ b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml @@ -16,5 +16,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/material_dynamic_neutral20" /> - <corners android:radius="@dimen/ongoing_call_chip_corner_radius" /> + <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" /> </shape> diff --git a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml b/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml deleted file mode 100644 index 4181220ed68c..000000000000 --- a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -* Copyright 2023, The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ ---> -<selector - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - - <item android:state_selected="true"> - <shape android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurfaceHighlight" /> - <stroke - android:color="?androidprv:attr/colorAccentPrimary" - android:width="@dimen/contrast_dialog_button_stroke_width" /> - <corners android:radius="@dimen/contrast_dialog_button_radius"/> - </shape> - </item> - - <item> - <layer-list> - <item android:top="@dimen/contrast_dialog_button_stroke_width" - android:bottom="@dimen/contrast_dialog_button_stroke_width" - android:left="@dimen/contrast_dialog_button_stroke_width" - android:right="@dimen/contrast_dialog_button_stroke_width"> - <shape android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurfaceHighlight" /> - <corners android:radius="@dimen/contrast_dialog_button_radius"/> - </shape> - </item> - </layer-list> - </item> -</selector> diff --git a/packages/SystemUI/res/drawable/hub_handle.xml b/packages/SystemUI/res/drawable/hub_handle.xml new file mode 100644 index 000000000000..8bc276ffed9e --- /dev/null +++ b/packages/SystemUI/res/drawable/hub_handle.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:radius="4dp" /> + <solid android:color="#FFFFFF" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_contrast_high.xml b/packages/SystemUI/res/drawable/ic_contrast_high.xml deleted file mode 100644 index aa5b5abc33aa..000000000000 --- a/packages/SystemUI/res/drawable/ic_contrast_high.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector android:autoMirrored="true" android:height="20dp" - android:viewportHeight="20" android:viewportWidth="66" - android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="#F2F1E8" - android:pathData="M0.5,8C0.5,3.858 3.858,0.5 8,0.5H58C62.142,0.5 65.5,3.858 65.5,8V12C65.5,16.142 62.142,19.5 58,19.5H8C3.858,19.5 0.5,16.142 0.5,12V8Z" - android:strokeColor="#1B1C17" android:strokeWidth="1"/> - <path android:fillColor="#1B1C17" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/> - <path android:fillColor="#1B1C17" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/> - <path android:fillColor="#1B1C17" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_contrast_medium.xml b/packages/SystemUI/res/drawable/ic_contrast_medium.xml deleted file mode 100644 index 89519b86b974..000000000000 --- a/packages/SystemUI/res/drawable/ic_contrast_medium.xml +++ /dev/null @@ -1,23 +0,0 @@ -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector android:autoMirrored="true" android:height="20dp" - android:viewportHeight="20" android:viewportWidth="66" - android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="#F2F1E8" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/> - <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/> - <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/> - <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_contrast_standard.xml b/packages/SystemUI/res/drawable/ic_contrast_standard.xml deleted file mode 100644 index f914975823da..000000000000 --- a/packages/SystemUI/res/drawable/ic_contrast_standard.xml +++ /dev/null @@ -1,23 +0,0 @@ -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector android:autoMirrored="true" android:height="20dp" - android:viewportHeight="20" android:viewportWidth="66" - android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="#C7C8B7" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/> - <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/> - <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/> - <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml index bdd6270bb50b..b9a4cbfc683e 100644 --- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml +++ b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml @@ -16,5 +16,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="?android:attr/colorAccent" /> - <corners android:radius="@dimen/ongoing_call_chip_corner_radius" /> + <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml index 5755dcd70a82..01b9f7e2e38a 100644 --- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml @@ -26,8 +26,8 @@ android:layout_height="match_parent"> android:paddingVertical="16dp" android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/rightGuideline" - app:layout_constraintStart_toStartOf="@+id/leftGuideline" + app:layout_constraintRight_toLeftOf="@+id/rightGuideline" + app:layout_constraintLeft_toLeftOf="@+id/leftGuideline" app:layout_constraintTop_toTopOf="@+id/topGuideline" /> <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper @@ -35,8 +35,8 @@ android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" tools:srcCompat="@tools:sample/avatars" /> @@ -63,8 +63,8 @@ android:layout_height="match_parent"> android:paddingTop="24dp" android:fadeScrollbars="false" app:layout_constraintBottom_toTopOf="@+id/button_bar" - app:layout_constraintEnd_toStartOf="@+id/midGuideline" - app:layout_constraintStart_toStartOf="@id/leftGuideline" + app:layout_constraintRight_toLeftOf="@+id/midGuideline" + app:layout_constraintLeft_toLeftOf="@id/leftGuideline" app:layout_constraintTop_toTopOf="@+id/topGuideline"> <androidx.constraintlayout.widget.ConstraintLayout @@ -89,7 +89,7 @@ android:layout_height="match_parent"> android:layout_width="0dp" android:layout_height="wrap_content" android:textAlignment="viewStart" - android:paddingLeft="16dp" + android:paddingStart="16dp" app:layout_constraintBottom_toBottomOf="@+id/logo" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/logo" @@ -209,6 +209,7 @@ android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + app:guidelineUseRtl="false" app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" /> <androidx.constraintlayout.widget.Guideline @@ -216,6 +217,7 @@ android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + app:guidelineUseRtl="false" app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" /> <androidx.constraintlayout.widget.Guideline @@ -223,6 +225,7 @@ android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + app:guidelineUseRtl="false" app:layout_constraintGuide_begin="406dp" /> <androidx.constraintlayout.widget.Guideline diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml index 4d2310a2a6ca..0bbe73c36fc7 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml @@ -27,7 +27,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginLeft="24dp" + android:layout_marginStart="24dp" android:ellipsize="end" android:maxLines="2" android:visibility="invisible" @@ -41,7 +41,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginLeft="24dp" + android:layout_marginStart="24dp" android:text="@string/cancel" android:visibility="invisible" app:layout_constraintBottom_toBottomOf="parent" @@ -54,7 +54,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginLeft="24dp" + android:layout_marginStart="24dp" android:visibility="invisible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> @@ -66,7 +66,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginRight="24dp" + android:layout_marginEnd="24dp" android:ellipsize="end" android:maxLines="2" android:text="@string/biometric_dialog_confirm" @@ -81,7 +81,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginRight="24dp" + android:layout_marginEnd="24dp" android:ellipsize="end" android:maxLines="2" android:text="@string/biometric_dialog_try_again" diff --git a/packages/SystemUI/res/layout/contrast_dialog.xml b/packages/SystemUI/res/layout/contrast_dialog.xml deleted file mode 100644 index 8e885cf39e2b..000000000000 --- a/packages/SystemUI/res/layout/contrast_dialog.xml +++ /dev/null @@ -1,127 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <Space - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1"/> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <FrameLayout - android:id="@+id/contrast_button_standard" - android:layout_width="@dimen/contrast_dialog_button_total_size" - android:layout_height="@dimen/contrast_dialog_button_total_size" - android:background="@drawable/contrast_dialog_button_background"> - - <ImageView - android:layout_gravity="center" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_contrast_standard"/> - </FrameLayout> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing" - android:gravity="center_horizontal|top" - android:textSize="@dimen/contrast_dialog_button_text_size" - android:text="@string/quick_settings_contrast_standard" - android:textColor="?androidprv:attr/textColorPrimary"/> - </LinearLayout> - - <Space - android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing" - android:layout_height="match_parent" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <FrameLayout - android:id="@+id/contrast_button_medium" - android:layout_width="@dimen/contrast_dialog_button_total_size" - android:layout_height="@dimen/contrast_dialog_button_total_size" - android:background="@drawable/contrast_dialog_button_background"> - - <ImageView - android:layout_gravity="center" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_contrast_medium"/> - </FrameLayout> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing" - android:gravity="center_horizontal|top" - android:textSize="@dimen/contrast_dialog_button_text_size" - android:text="@string/quick_settings_contrast_medium" - android:textColor="?androidprv:attr/textColorPrimary"/> - </LinearLayout> - - <Space - android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing" - android:layout_height="match_parent" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <FrameLayout - android:id="@+id/contrast_button_high" - android:layout_width="@dimen/contrast_dialog_button_total_size" - android:layout_height="@dimen/contrast_dialog_button_total_size" - android:background="@drawable/contrast_dialog_button_background"> - - <ImageView - android:layout_gravity="center" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_contrast_high"/> - - </FrameLayout> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing" - android:gravity="center_horizontal|top" - android:textSize="@dimen/contrast_dialog_button_text_size" - android:text="@string/quick_settings_contrast_high" - android:textColor="?androidprv:attr/textColorPrimary"/> - </LinearLayout> - - <Space - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1"/> -</LinearLayout> diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml index 19fb874ea2be..4234fca55e3c 100644 --- a/packages/SystemUI/res/layout/dream_overlay_container.xml +++ b/packages/SystemUI/res/layout/dream_overlay_container.xml @@ -21,6 +21,19 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + <ImageView + android:id="@+id/glanceable_hub_handle" + android:layout_width="4dp" + android:layout_height="220dp" + android:layout_centerVertical="true" + android:layout_marginEnd="12dp" + android:background="@drawable/hub_handle" + android:visibility="gone" + android:contentDescription="UI indicator for swiping open the glanceable hub" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/dream_overlay_content" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml index 6a0217ec5fe8..a33be12a655a 100644 --- a/packages/SystemUI/res/layout/ongoing_call_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml @@ -17,43 +17,45 @@ the chip. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/ongoing_call_chip" + android:id="@+id/ongoing_activity_chip" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical|start" android:layout_marginStart="5dp" > - <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer - android:id="@+id/ongoing_call_chip_background" + <!-- TODO(b/332662551): Update this content description when this supports more than just + phone calls. --> + <com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer + android:id="@+id/ongoing_activity_chip_background" android:layout_width="wrap_content" android:layout_height="@dimen/ongoing_appops_chip_height" android:layout_gravity="center_vertical" android:gravity="center" - android:background="@drawable/ongoing_call_chip_bg" - android:paddingStart="@dimen/ongoing_call_chip_side_padding" - android:paddingEnd="@dimen/ongoing_call_chip_side_padding" + android:background="@drawable/ongoing_activity_chip_bg" + android:paddingStart="@dimen/ongoing_activity_chip_side_padding" + android:paddingEnd="@dimen/ongoing_activity_chip_side_padding" android:contentDescription="@string/ongoing_phone_call_content_description" android:minWidth="@dimen/min_clickable_item_size" > <ImageView android:src="@*android:drawable/ic_phone" - android:layout_width="@dimen/ongoing_call_chip_icon_size" - android:layout_height="@dimen/ongoing_call_chip_icon_size" + android:layout_width="@dimen/ongoing_activity_chip_icon_size" + android:layout_height="@dimen/ongoing_activity_chip_icon_size" android:tint="?android:attr/colorPrimary" /> - <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallChronometer - android:id="@+id/ongoing_call_chip_time" + <com.android.systemui.statusbar.chips.ui.view.ChipChronometer + android:id="@+id/ongoing_activity_chip_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" android:gravity="center|start" - android:paddingStart="@dimen/ongoing_call_chip_icon_text_padding" + android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding" android:textAppearance="@android:style/TextAppearance.Material.Small" android:fontFamily="@*android:string/config_headlineFontFamily" android:textColor="?android:attr/colorPrimary" /> - </com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer> + </com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer> </FrameLayout> diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml index 2cb4b0233a90..49d3a8ec8ad8 100644 --- a/packages/SystemUI/res/layout/screenshot_shelf.xml +++ b/packages/SystemUI/res/layout/screenshot_shelf.xml @@ -135,11 +135,12 @@ android:id="@+id/screenshot_scrollable_preview" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:clipToOutline="true" android:scaleType="matrix" android:visibility="gone" app:layout_constraintStart_toStartOf="@id/screenshot_preview" app:layout_constraintTop_toTopOf="@id/screenshot_preview" - android:elevation="7dp"/> + android:elevation="3dp"/> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" @@ -170,6 +171,13 @@ </FrameLayout> </androidx.constraintlayout.widget.ConstraintLayout> <ImageView + android:id="@+id/screenshot_scrolling_scrim" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + android:clickable="true" + android:importantForAccessibility="no"/> + <ImageView android:id="@+id/screenshot_flash" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 452bc317e2d5..4247c7eef0d0 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -99,7 +99,7 @@ android:gravity="center_vertical|start" /> - <include layout="@layout/ongoing_call_chip" /> + <include layout="@layout/ongoing_activity_chip" /> <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/notification_icon_area" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 517b44f455d7..9d0319c2471b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -914,8 +914,8 @@ <dimen name="communal_enforced_rounded_corner_max_radius">28dp</dimen> <!-- Width and height used to filter widgets displayed in the communal widget picker --> - <dimen name="communal_widget_picker_desired_width">424dp</dimen> - <dimen name="communal_widget_picker_desired_height">282dp</dimen> + <dimen name="communal_widget_picker_desired_width">360dp</dimen> + <dimen name="communal_widget_picker_desired_height">240dp</dimen> <!-- The width/height of the unlock icon view on keyguard. --> <dimen name="keyguard_lock_height">42dp</dimen> @@ -1713,12 +1713,12 @@ <dimen name="wallet_button_horizontal_padding">24dp</dimen> <dimen name="wallet_button_vertical_padding">8dp</dimen> - <!-- Ongoing call chip --> - <dimen name="ongoing_call_chip_side_padding">12dp</dimen> - <dimen name="ongoing_call_chip_icon_size">16dp</dimen> + <!-- Ongoing activity chip --> + <dimen name="ongoing_activity_chip_side_padding">12dp</dimen> + <dimen name="ongoing_activity_chip_icon_size">16dp</dimen> <!-- The padding between the icon and the text. --> - <dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen> - <dimen name="ongoing_call_chip_corner_radius">28dp</dimen> + <dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen> + <dimen name="ongoing_activity_chip_corner_radius">28dp</dimen> <!-- Status bar user chip --> <dimen name="status_bar_user_chip_avatar_size">16dp</dimen> @@ -1963,15 +1963,6 @@ <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen> <dimen name="broadcast_dialog_margin">16dp</dimen> - <!-- Contrast dialog --> - <dimen name="contrast_dialog_button_total_size">90dp</dimen> - <dimen name="contrast_dialog_button_inner_size">82dp</dimen> - <dimen name="contrast_dialog_button_radius">20dp</dimen> - <dimen name="contrast_dialog_button_stroke_width">4dp</dimen> - <dimen name="contrast_dialog_button_text_size">14sp</dimen> - <dimen name="contrast_dialog_button_text_spacing">4dp</dimen> - <dimen name="contrast_dialog_button_horizontal_spacing">16dp</dimen> - <!-- Shadow for dream overlay clock complication --> <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen> <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index aecc9066b552..8da8316f624b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -902,15 +902,6 @@ <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] --> <string name="quick_settings_onehanded_label">One-handed mode</string> - <!-- QuickSettings: Contrast tile [CHAR LIMIT=NONE] --> - <string name="quick_settings_contrast_label">Contrast</string> - <!-- QuickSettings: Contrast tile description: standard [CHAR LIMIT=NONE] --> - <string name="quick_settings_contrast_standard">Standard</string> - <!-- QuickSettings: Contrast tile description: medium [CHAR LIMIT=NONE] --> - <string name="quick_settings_contrast_medium">Medium</string> - <!-- QuickSettings: Contrast tile description: high [CHAR LIMIT=NONE] --> - <string name="quick_settings_contrast_high">High</string> - <!-- Hearing devices --> <!-- QuickSettings: Hearing devices [CHAR LIMIT=NONE] --> <string name="quick_settings_hearing_devices_label">Hearing devices</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 2c4cdb9ee796..393a1aaec74a 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -523,10 +523,6 @@ <item name="android:windowExitAnimation">@anim/instant_fade_out</item> </style> - <style name="Theme.SystemUI.ContrastDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> - <item name="android:windowBackground">@android:color/transparent</item> - </style> - <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings"> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java index 8ac1de898be8..c33b7ce1d002 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java @@ -99,7 +99,7 @@ public class PipSurfaceTransactionHelper { final float startScale = sourceRectHint.width() <= sourceRectHint.height() ? (float) destinationBounds.width() / sourceBounds.width() : (float) destinationBounds.height() / sourceBounds.height(); - scale = (1 - progress) * startScale + progress * endScale; + scale = Math.min((1 - progress) * startScale + progress * endScale, 1.0f); } final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale; final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java deleted file mode 100644 index 0b0df833e916..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.recents.model; - -import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static android.graphics.Bitmap.Config.ARGB_8888; - -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.HardwareBuffer; -import android.util.Log; -import android.view.WindowInsetsController.Appearance; -import android.window.TaskSnapshot; - -import java.util.HashMap; - -/** - * Data for a single thumbnail. - */ -public class ThumbnailData { - - public final Bitmap thumbnail; - public int orientation; - public int rotation; - public Rect insets; - public Rect letterboxInsets; - public boolean reducedResolution; - public boolean isRealSnapshot; - public boolean isTranslucent; - public int windowingMode; - public @Appearance int appearance; - public float scale; - public long snapshotId; - - public ThumbnailData() { - thumbnail = null; - orientation = ORIENTATION_UNDEFINED; - rotation = ROTATION_UNDEFINED; - insets = new Rect(); - letterboxInsets = new Rect(); - reducedResolution = false; - scale = 1f; - isRealSnapshot = true; - isTranslucent = false; - windowingMode = WINDOWING_MODE_UNDEFINED; - snapshotId = 0; - } - - public void recycleBitmap() { - if (thumbnail != null) { - thumbnail.recycle(); - } - } - - private static Bitmap makeThumbnail(TaskSnapshot snapshot) { - Bitmap thumbnail = null; - try (final HardwareBuffer buffer = snapshot.getHardwareBuffer()) { - if (buffer != null) { - thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.getColorSpace()); - } - } catch (IllegalArgumentException ex) { - // TODO(b/157562905): Workaround for a crash when we get a snapshot without this state - Log.e("ThumbnailData", "Unexpected snapshot without USAGE_GPU_SAMPLED_IMAGE: " - + snapshot.getHardwareBuffer(), ex); - } - if (thumbnail == null) { - Point taskSize = snapshot.getTaskSize(); - thumbnail = Bitmap.createBitmap(taskSize.x, taskSize.y, ARGB_8888); - thumbnail.eraseColor(Color.BLACK); - } - return thumbnail; - } - - public static HashMap<Integer, ThumbnailData> wrap(int[] taskIds, TaskSnapshot[] snapshots) { - HashMap<Integer, ThumbnailData> temp = new HashMap<>(); - if (taskIds == null || snapshots == null || taskIds.length != snapshots.length) { - return temp; - } - - for (int i = snapshots.length - 1; i >= 0; i--) { - temp.put(taskIds[i], new ThumbnailData(snapshots[i])); - } - return temp; - } - - public ThumbnailData(TaskSnapshot snapshot) { - thumbnail = makeThumbnail(snapshot); - insets = new Rect(snapshot.getContentInsets()); - letterboxInsets = new Rect(snapshot.getLetterboxInsets()); - orientation = snapshot.getOrientation(); - rotation = snapshot.getRotation(); - reducedResolution = snapshot.isLowResolution(); - // TODO(b/149579527): Pass task size instead of computing scale. - // Assume width and height were scaled the same; compute scale only for width - scale = (float) thumbnail.getWidth() / snapshot.getTaskSize().x; - isRealSnapshot = snapshot.isRealSnapshot(); - isTranslucent = snapshot.isTranslucent(); - windowingMode = snapshot.getWindowingMode(); - appearance = snapshot.getAppearance(); - snapshotId = snapshot.getId(); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt new file mode 100644 index 000000000000..dcf7754221bb --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.recents.model + +import android.app.WindowConfiguration +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Bitmap.Config.ARGB_8888 +import android.graphics.Color +import android.graphics.Rect +import android.util.Log +import android.view.WindowInsetsController.Appearance +import android.window.TaskSnapshot + +/** Data for a single thumbnail. */ +data class ThumbnailData( + val thumbnail: Bitmap? = null, + var orientation: Int = Configuration.ORIENTATION_UNDEFINED, + @JvmField var rotation: Int = WindowConfiguration.ROTATION_UNDEFINED, + @JvmField var insets: Rect = Rect(), + @JvmField var letterboxInsets: Rect = Rect(), + @JvmField var reducedResolution: Boolean = false, + @JvmField var isRealSnapshot: Boolean = true, + var isTranslucent: Boolean = false, + @JvmField var windowingMode: Int = WindowConfiguration.WINDOWING_MODE_UNDEFINED, + @JvmField @Appearance var appearance: Int = 0, + @JvmField var scale: Float = 1f, + var snapshotId: Long = 0, +) { + fun recycleBitmap() { + thumbnail?.recycle() + } + + companion object { + private fun makeThumbnail(snapshot: TaskSnapshot): Bitmap { + var thumbnail: Bitmap? = null + try { + snapshot.hardwareBuffer?.use { buffer -> + thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.colorSpace) + } + } catch (ex: IllegalArgumentException) { + // TODO(b/157562905): Workaround for a crash when we get a snapshot without this + // state + Log.e( + "ThumbnailData", + "Unexpected snapshot without USAGE_GPU_SAMPLED_IMAGE: " + + "${snapshot.hardwareBuffer}", + ex + ) + } + + return thumbnail + ?: Bitmap.createBitmap(snapshot.taskSize.x, snapshot.taskSize.y, ARGB_8888).apply { + eraseColor(Color.BLACK) + } + } + + @JvmStatic + fun wrap(taskIds: IntArray?, snapshots: Array<TaskSnapshot>?): HashMap<Int, ThumbnailData> { + return if (taskIds == null || snapshots == null || taskIds.size != snapshots.size) { + HashMap() + } else { + HashMap(taskIds.associateWith { taskId -> fromSnapshot(snapshots[taskId]) }) + } + } + + @JvmStatic + fun fromSnapshot(snapshot: TaskSnapshot): ThumbnailData { + val thumbnail = makeThumbnail(snapshot) + return ThumbnailData( + thumbnail = thumbnail, + insets = Rect(snapshot.contentInsets), + letterboxInsets = Rect(snapshot.letterboxInsets), + orientation = snapshot.orientation, + rotation = snapshot.rotation, + reducedResolution = snapshot.isLowResolution, + // TODO(b/149579527): Pass task size instead of computing scale. + // Assume width and height were scaled the same; compute scale only for width + scale = thumbnail.width.toFloat() / snapshot.taskSize.x, + isRealSnapshot = snapshot.isRealSnapshot, + isTranslucent = snapshot.isTranslucent, + windowingMode = snapshot.windowingMode, + appearance = snapshot.appearance, + snapshotId = snapshot.id, + ) + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index ca63483f656a..845ca5e8b9ec 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -147,7 +147,7 @@ public class ActivityManagerWrapper { Log.w(TAG, "Failed to retrieve task snapshot", e); } if (snapshot != null) { - return new ThumbnailData(snapshot); + return ThumbnailData.fromSnapshot(snapshot); } else { return new ThumbnailData(); } @@ -167,7 +167,7 @@ public class ActivityManagerWrapper { Log.w(TAG, "Failed to take task snapshot", e); } if (snapshot != null) { - return new ThumbnailData(snapshot); + return ThumbnailData.fromSnapshot(snapshot); } else { return new ThumbnailData(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index a6e04cec5f86..bbf46984208f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -42,7 +42,7 @@ public class RecentsAnimationControllerCompat { try { final TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId); if (snapshot != null) { - return new ThumbnailData(snapshot); + return ThumbnailData.fromSnapshot(snapshot); } } catch (RemoteException e) { Log.e(TAG, "Failed to screenshot task", e); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index 473719fa76df..cf8ec62f19ef 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -351,7 +351,7 @@ public class TaskStackChangeListeners { case ON_TASK_SNAPSHOT_CHANGED: { Trace.beginSection("onTaskSnapshotChanged"); final TaskSnapshot snapshot = (TaskSnapshot) msg.obj; - final ThumbnailData thumbnail = new ThumbnailData(snapshot); + final ThumbnailData thumbnail = ThumbnailData.fromSnapshot(snapshot); boolean snapshotConsumed = false; for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { boolean consumed = mTaskStackListeners.get(i).onTaskSnapshotChanged( diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index f33acf21e8a0..3f3bb0bc94b6 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -42,6 +42,7 @@ import com.android.systemui.flags.Flags.REGION_SAMPLING import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN @@ -544,10 +545,10 @@ constructor( internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { return scope.launch { merge( - keyguardTransitionInteractor.transition(AOD, LOCKSCREEN).map { step -> - step.copy(value = 1f - step.value) + keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)).map { + it.copy(value = 1f - it.value) }, - keyguardTransitionInteractor.transition(LOCKSCREEN, AOD), + keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)), ).filter { it.transitionState != TransitionState.FINISHED } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 905a98c2e181..42838aeddd6b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -326,7 +326,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardTransitionInteractor.startDismissKeyguardTransition(); + mKeyguardTransitionInteractor.startDismissKeyguardTransition( + "KeyguardSecurityContainerController#finish"); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index 1f0459978c3c..d5e911efe570 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -37,7 +37,6 @@ import androidx.dynamicanimation.animation.SpringForce; import androidx.recyclerview.widget.RecyclerView; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Flags; import java.util.HashMap; @@ -339,15 +338,11 @@ class MenuAnimationController { mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true); final PointF position = mMenuView.getMenuPosition(); final PointF tuckedPosition = getTuckedMenuPosition(); - if (Flags.floatingMenuAnimatedTuck()) { - flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X, - Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY, - FLING_FRICTION_SCALAR, - createDefaultSpringForce(), - tuckedPosition.x); - } else { - moveToPosition(tuckedPosition); - } + flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X, + Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY, + FLING_FRICTION_SCALAR, + createDefaultSpringForce(), + tuckedPosition.x); // Keep the touch region let users could click extra space to pop up the menu view // from the screen edge @@ -359,23 +354,19 @@ class MenuAnimationController { void moveOutEdgeAndShow() { mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); - if (Flags.floatingMenuAnimatedTuck()) { - PointF position = mMenuView.getMenuPosition(); - springMenuWith(DynamicAnimation.TRANSLATION_X, - createDefaultSpringForce(), - 0, - position.x, - true - ); - springMenuWith(DynamicAnimation.TRANSLATION_Y, - createDefaultSpringForce(), - 0, - position.y, - true - ); - } else { - mMenuView.onPositionChanged(); - } + PointF position = mMenuView.getMenuPosition(); + springMenuWith(DynamicAnimation.TRANSLATION_X, + createDefaultSpringForce(), + 0, + position.x, + true + ); + springMenuWith(DynamicAnimation.TRANSLATION_Y, + createDefaultSpringForce(), + 0, + position.y, + true + ); mMenuView.onEdgeChangedIfNeeded(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index be75e1035ea6..9d9e7dfb7032 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -321,22 +321,6 @@ class MenuView extends FrameLayout implements if (mMoveToTuckedListener != null) { mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked); } - - if (!Flags.floatingMenuAnimatedTuck()) { - if (isMoveToTucked) { - final float halfWidth = getMenuWidth() / 2.0f; - final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide(); - final Rect clipBounds = new Rect( - (int) (!isOnLeftSide ? 0 : halfWidth), - 0, - (int) (!isOnLeftSide ? halfWidth : getMenuWidth()), - getMenuHeight() - ); - setClipBounds(clipBounds); - } else { - setClipBounds(null); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 6dce1bb22921..0c67c5093faf 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -322,9 +322,8 @@ class MenuViewLayer extends FrameLayout implements } addView(mMessageView, LayerIndex.MESSAGE_VIEW); - if (Flags.floatingMenuAnimatedTuck()) { - setClipChildren(true); - } + setClipChildren(true); + setClickable(false); setFocusable(false); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); @@ -476,10 +475,8 @@ class MenuViewLayer extends FrameLayout implements mMenuAnimationController.startTuckedAnimationPreview(); } - if (Flags.floatingMenuAnimatedTuck()) { - if (!mMenuView.isMoveToTucked()) { - setClipBounds(null); - } + if (!mMenuView.isMoveToTucked()) { + setClipBounds(null); } mMenuView.onArrivalAtPosition(false); } diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java index 9c7fc9dd307f..9ef9938ab8ad 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java @@ -23,8 +23,7 @@ import android.graphics.Region; import android.view.GestureDetector; import android.view.MotionEvent; -import androidx.annotation.NonNull; - +import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.phone.CentralSurfaces; import java.util.Optional; @@ -38,34 +37,29 @@ import javax.inject.Named; */ public class ShadeTouchHandler implements TouchHandler { private final Optional<CentralSurfaces> mSurfaces; + private final ShadeViewController mShadeViewController; private final int mInitiationHeight; - /** - * Tracks whether or not we are capturing a given touch. Will be null before and after a touch. - */ - private Boolean mCapture; - @Inject ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces, + ShadeViewController shadeViewController, @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) { mSurfaces = centralSurfaces; + mShadeViewController = shadeViewController; mInitiationHeight = initiationHeight; } @Override public void onSessionStart(TouchSession session) { - if (mSurfaces.isEmpty()) { + if (mSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) { session.pop(); return; } - session.registerCallback(() -> mCapture = null); - session.registerInputListener(ev -> { + mShadeViewController.handleExternalTouch((MotionEvent) ev); + if (ev instanceof MotionEvent) { - if (mCapture != null && mCapture) { - mSurfaces.get().handleExternalShadeWindowTouch((MotionEvent) ev); - } if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) { session.pop(); } @@ -74,25 +68,15 @@ public class ShadeTouchHandler implements TouchHandler { session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() { @Override - public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (mCapture == null) { - // Only capture swipes that are going downwards. - mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0; - if (mCapture) { - // Send the initial touches over, as the input listener has already - // processed these touches. - mSurfaces.get().handleExternalShadeWindowTouch(e1); - mSurfaces.get().handleExternalShadeWindowTouch(e2); - } - } - return mCapture; + return true; } @Override - public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - return mCapture; + return true; } }); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 9816896e3ea8..298b87d05f39 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -32,11 +32,18 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState @@ -131,6 +138,7 @@ open class UdfpsKeyguardViewControllerLegacy( override fun onUnlockedChanged() { updatePauseAuth() } + override fun onLaunchTransitionFadingAwayChanged() { launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway updatePauseAuth() @@ -211,7 +219,10 @@ open class UdfpsKeyguardViewControllerLegacy( suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job { return scope.launch { transitionInteractor - .transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD) + .transition( + edge = Edge.create(Scenes.Bouncer, AOD), + edgeWithoutSceneContainer = Edge.create(PRIMARY_BOUNCER, AOD) + ) .collect { transitionStep -> view.onDozeAmountChanged( transitionStep.value, @@ -225,8 +236,7 @@ open class UdfpsKeyguardViewControllerLegacy( @VisibleForTesting suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect { - transitionStep -> + transitionInteractor.transition(Edge.create(DREAMING, AOD)).collect { transitionStep -> view.onDozeAmountChanged( transitionStep.value, transitionStep.value, @@ -239,23 +249,21 @@ open class UdfpsKeyguardViewControllerLegacy( @VisibleForTesting suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor - .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD) - .collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, - ) - } + transitionInteractor.transition(Edge.create(ALTERNATE_BOUNCER, AOD)).collect { + transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, + ) + } } } @VisibleForTesting suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect { - transitionStep -> + transitionInteractor.transition(Edge.create(AOD, OCCLUDED)).collect { transitionStep -> view.onDozeAmountChanged( 1f - transitionStep.value, 1f - transitionStep.value, @@ -268,8 +276,7 @@ open class UdfpsKeyguardViewControllerLegacy( @VisibleForTesting suspend fun listenForOccludedToAodTransition(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD).collect { - transitionStep -> + transitionInteractor.transition(Edge.create(OCCLUDED, AOD)).collect { transitionStep -> view.onDozeAmountChanged( transitionStep.value, transitionStep.value, @@ -282,14 +289,18 @@ open class UdfpsKeyguardViewControllerLegacy( @VisibleForTesting suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor.transition(KeyguardState.GONE, KeyguardState.AOD).collect { - transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - ANIMATE_APPEAR_ON_SCREEN_OFF, + transitionInteractor + .transition( + edge = Edge.create(Scenes.Gone, AOD), + edgeWithoutSceneContainer = Edge.create(GONE, AOD) ) - } + .collect { transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + ANIMATE_APPEAR_ON_SCREEN_OFF, + ) + } } } @@ -298,13 +309,10 @@ open class UdfpsKeyguardViewControllerLegacy( return scope.launch { transitionInteractor.dozeAmountTransition.collect { transitionStep -> if ( - transitionStep.from == KeyguardState.AOD && + transitionStep.from == AOD && transitionStep.transitionState == TransitionState.CANCELED ) { - if ( - transitionInteractor.startedKeyguardTransitionStep.first().to != - KeyguardState.AOD - ) { + if (transitionInteractor.startedKeyguardTransitionStep.first().to != AOD) { // If the next started transition isn't transitioning back to AOD, force // doze amount to be 0f (as if the transition to the lockscreen completed). view.onDozeAmountChanged( @@ -557,6 +565,7 @@ open class UdfpsKeyguardViewControllerLegacy( private fun updateScaleFactor() { udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) } } + companion object { const val TAG = "UdfpsKeyguardViewController" } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt index cc524840947b..ca03a00cca0c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt @@ -34,6 +34,7 @@ import android.hardware.biometrics.events.AuthenticationStoppedInfo import android.hardware.biometrics.events.AuthenticationSucceededInfo import android.hardware.face.FaceManager import android.hardware.fingerprint.FingerprintManager +import android.util.Log import com.android.systemui.biometrics.shared.model.AuthenticationReason import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations import com.android.systemui.biometrics.shared.model.AuthenticationState @@ -52,6 +53,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn /** A repository for the state of biometric authentication. */ @@ -85,6 +87,7 @@ constructor( private val authenticationState: Flow<AuthenticationState> = conflatedCallbackFlow { val updateAuthenticationState = { state: AuthenticationState -> + Log.d(TAG, "authenticationState updated: $state") trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state") } @@ -187,6 +190,7 @@ constructor( it.biometricSourceType == BiometricSourceType.FINGERPRINT) } .map { it.requestReason } + .onEach { Log.d(TAG, "fingerprintAuthenticationReason updated: $it") } override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> = authenticationState diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt index 40d38dd83154..6b61adce3c84 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt @@ -30,10 +30,10 @@ import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.biometrics.shared.model.toSensorStrength import com.android.systemui.biometrics.shared.model.toSensorType import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -42,6 +42,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -52,7 +53,7 @@ import kotlinx.coroutines.withContext */ interface FingerprintPropertyRepository { /** Whether the fingerprint properties have been initialized yet. */ - val propertiesInitialized: StateFlow<Boolean> + val propertiesInitialized: Flow<Boolean> /** The id of fingerprint sensor. */ val sensorId: Flow<Int> @@ -110,14 +111,8 @@ constructor( initialValue = UNINITIALIZED_PROPS, ) - override val propertiesInitialized: StateFlow<Boolean> = - props - .map { it != UNINITIALIZED_PROPS } - .stateIn( - applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = props.value != UNINITIALIZED_PROPS, - ) + override val propertiesInitialized: Flow<Boolean> = + props.map { it != UNINITIALIZED_PROPS }.onStart { emit(props.value != UNINITIALIZED_PROPS) } override val sensorId: Flow<Int> = props.map { it.sensorId } @@ -141,7 +136,7 @@ constructor( companion object { private const val TAG = "FingerprintPropertyRepositoryImpl" - private val UNINITIALIZED_PROPS = + val UNINITIALIZED_PROPS = FingerprintSensorPropertiesInternal( -2 /* sensorId */, SensorProperties.STRENGTH_CONVENIENCE, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt index 6e79e4693728..83aefcaac36d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics.domain.interactor import android.app.ActivityTaskManager +import android.util.Log import com.android.systemui.biometrics.data.repository.BiometricStatusRepository import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.AuthenticationReason @@ -26,6 +27,7 @@ import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.onEach /** Encapsulates business logic for interacting with biometric authentication state. */ interface BiometricStatusInteractor { @@ -49,15 +51,20 @@ constructor( override val sfpsAuthenticationReason: Flow<AuthenticationReason> = combine( - biometricStatusRepository.fingerprintAuthenticationReason, - fingerprintPropertyRepository.sensorType - ) { reason: AuthenticationReason, sensorType -> - if (sensorType.isPowerButton() && reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)) { - reason - } else { - AuthenticationReason.NotRunning + biometricStatusRepository.fingerprintAuthenticationReason, + fingerprintPropertyRepository.sensorType + ) { reason: AuthenticationReason, sensorType -> + if ( + sensorType.isPowerButton() && + reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager) + ) { + reason + } else { + AuthenticationReason.NotRunning + } } - }.distinctUntilChanged() + .distinctUntilChanged() + .onEach { Log.d(TAG, "sfpsAuthenticationReason updated: $it") } override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> = biometricStatusRepository.fingerprintAcquiredStatus diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt index 3112b673d724..d5b450d1e2a8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt @@ -46,7 +46,7 @@ constructor( displayStateInteractor: DisplayStateInteractor, udfpsOverlayInteractor: UdfpsOverlayInteractor, ) { - val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized + val propertiesInitialized: Flow<Boolean> = repository.propertiesInitialized val isUdfps: StateFlow<Boolean> = repository.sensorType .map { it.isUdfps() } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index f0969eda4029..13ea3f56d911 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -199,29 +199,32 @@ object BiometricViewSizeBinder { iconParams.leftMargin = position.left mediumConstraintSet.clear( R.id.biometric_icon, - ConstraintSet.END + ConstraintSet.RIGHT ) mediumConstraintSet.connect( R.id.biometric_icon, - ConstraintSet.START, + ConstraintSet.LEFT, ConstraintSet.PARENT_ID, - ConstraintSet.START + ConstraintSet.LEFT ) mediumConstraintSet.setMargin( R.id.biometric_icon, - ConstraintSet.START, + ConstraintSet.LEFT, position.left ) - smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.END) + smallConstraintSet.clear( + R.id.biometric_icon, + ConstraintSet.RIGHT + ) smallConstraintSet.connect( R.id.biometric_icon, - ConstraintSet.START, + ConstraintSet.LEFT, ConstraintSet.PARENT_ID, - ConstraintSet.START + ConstraintSet.LEFT ) smallConstraintSet.setMargin( R.id.biometric_icon, - ConstraintSet.START, + ConstraintSet.LEFT, position.left ) } @@ -252,32 +255,32 @@ object BiometricViewSizeBinder { iconParams.rightMargin = position.right mediumConstraintSet.clear( R.id.biometric_icon, - ConstraintSet.START + ConstraintSet.LEFT ) mediumConstraintSet.connect( R.id.biometric_icon, - ConstraintSet.END, + ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, - ConstraintSet.END + ConstraintSet.RIGHT ) mediumConstraintSet.setMargin( R.id.biometric_icon, - ConstraintSet.END, + ConstraintSet.RIGHT, position.right ) smallConstraintSet.clear( R.id.biometric_icon, - ConstraintSet.START + ConstraintSet.LEFT ) smallConstraintSet.connect( R.id.biometric_icon, - ConstraintSet.END, + ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, - ConstraintSet.END + ConstraintSet.RIGHT ) smallConstraintSet.setMargin( R.id.biometric_icon, - ConstraintSet.END, + ConstraintSet.RIGHT, position.right ) } @@ -383,15 +386,15 @@ object BiometricViewSizeBinder { // Move all content to other panel flipConstraintSet.connect( R.id.scrollView, - ConstraintSet.START, + ConstraintSet.LEFT, R.id.midGuideline, - ConstraintSet.START + ConstraintSet.LEFT ) flipConstraintSet.connect( R.id.scrollView, - ConstraintSet.END, + ConstraintSet.RIGHT, R.id.rightGuideline, - ConstraintSet.END + ConstraintSet.RIGHT ) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt index 4bdbfa272ecc..ff7ac35ba56b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -20,6 +20,7 @@ package com.android.systemui.biometrics.ui.binder import android.content.Context import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.WindowManager @@ -91,6 +92,13 @@ constructor( showIndicatorForDeviceEntry, progressBarIsVisible) = combinedFlows + Log.d( + TAG, + "systemServerAuthReason = $systemServerAuthReason, " + + "showIndicatorForDeviceEntry = " + + "$showIndicatorForDeviceEntry, " + + "progressBarIsVisible = $progressBarIsVisible" + ) if (!isInRearDisplayMode) { if (progressBarIsVisible) { hide() @@ -114,6 +122,10 @@ constructor( /** Show the side fingerprint sensor indicator */ private fun show() { if (overlayView?.isAttachedToWindow == true) { + Log.d( + TAG, + "show(): overlayView $overlayView isAttachedToWindow already, ignoring show request" + ) return } @@ -128,6 +140,7 @@ constructor( ) bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get()) overlayView!!.visibility = View.INVISIBLE + Log.d(TAG, "show(): adding overlayView $overlayView") windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams) } @@ -137,6 +150,7 @@ constructor( val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation) lottie.pauseAnimation() lottie.removeAllLottieOnCompositionLoadedListener() + Log.d(TAG, "hide(): removing overlayView $overlayView, setting to null") windowManager.get().removeView(overlayView) overlayView = null } diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt index f0230beaa967..911145b62661 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt @@ -419,8 +419,7 @@ internal constructor( const val ACTION_PREVIOUSLY_CONNECTED_DEVICE = "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE" const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS" - const val ACTION_AUDIO_SHARING = - "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS" + const val ACTION_AUDIO_SHARING = "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS" const val DISABLED_ALPHA = 0.3f const val ENABLED_ALPHA = 1f const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt index c018ecb25835..0544a4f66295 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt @@ -18,6 +18,8 @@ package com.android.systemui.brightness.data.repository import android.content.Context import android.os.UserManager +import com.android.settingslib.RestrictedLockUtils +import com.android.systemui.Flags.enforceBrightnessBaseUserRestriction import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -66,7 +68,18 @@ constructor( user.id ) ?.let { PolicyRestriction.Restricted(it) } - ?: PolicyRestriction.NoRestriction + ?: if ( + enforceBrightnessBaseUserRestriction() && + userRestrictionChecker.hasBaseUserRestriction( + applicationContext, + UserManager.DISALLOW_CONFIG_BRIGHTNESS, + user.id + ) + ) { + PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin()) + } else { + PolicyRestriction.NoRestriction + } } .flowOn(backgroundDispatcher) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 971ab111d2f6..6f20a8daf00a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -42,6 +42,7 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine @@ -73,6 +74,10 @@ constructor( ) : CoreStartable { private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT + private var timeoutJob: Job? = null + + private var isDreaming: Boolean = false + override fun start() { // Handle automatically switching based on keyguard state. keyguardTransitionInteractor.startedKeyguardTransitionStep @@ -112,31 +117,35 @@ constructor( } .launchIn(bgScope) - // Handle timing out back to the dream. + // The hub mode timeout should start as soon as the user enters hub mode. At the end of the + // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the + // dream is not running, nothing will happen. However if the dream starts again underneath + // hub mode after the initial timeout expires, such as if the device is docked or the dream + // app is updated by the Play store, a new timeout should be started. bgScope.launch { combine( communalInteractor.desiredScene, // Emit a value on start so the combine starts. communalInteractor.userActivity.emitOnStart() ) { scene, _ -> - // Time out should run whenever we're dreaming and the hub is open, even if not - // docked. + // Only timeout if we're on the hub is open. scene == CommunalScenes.Communal } - // mapLatest cancels the previous action block when new values arrive, so any - // already running timeout gets cancelled when conditions change or user interaction - // is detected. - .mapLatest { shouldTimeout -> - if (!shouldTimeout) { - return@mapLatest false + .collectLatest { shouldTimeout -> + cancelHubTimeout() + if (shouldTimeout) { + startHubTimeout() } - - delay(screenTimeout.milliseconds) - true } - .sample(keyguardInteractor.isDreaming, ::Pair) - .collect { (shouldTimeout, isDreaming) -> - if (isDreaming && shouldTimeout) { + } + bgScope.launch { + keyguardInteractor.isDreaming + .sample(communalInteractor.desiredScene, ::Pair) + .collectLatest { (isDreaming, scene) -> + this@CommunalSceneStartable.isDreaming = isDreaming + if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) { + // If dreaming starts after timeout has expired, ex. if dream restarts under + // the hub, just close the hub immediately. communalInteractor.changeScene(CommunalScenes.Blank) } } @@ -151,6 +160,24 @@ constructor( } } + private fun cancelHubTimeout() { + timeoutJob?.cancel() + timeoutJob = null + } + + private fun startHubTimeout() { + if (timeoutJob == null) { + timeoutJob = + bgScope.launch { + delay(screenTimeout.milliseconds) + if (isDreaming) { + communalInteractor.changeScene(CommunalScenes.Blank) + } + timeoutJob = null + } + } + } + private suspend fun determineSceneAfterTransition( lastStartedTransition: TransitionStep, ): SceneKey? { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 06c83962df6b..9599a8864bcc 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -61,7 +61,6 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.UserTracker import com.android.systemui.smartspace.data.repository.SmartspaceRepository import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf -import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject @@ -130,7 +129,7 @@ constructor( allOf( communalSettingsInteractor.isCommunalEnabled, not(keyguardInteractor.isEncryptedOrLockdown), - anyOf(keyguardInteractor.isKeyguardShowing, keyguardInteractor.isDreaming) + keyguardInteractor.isKeyguardShowing ) .distinctUntilChanged() .onEach { available -> diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index f6122ad48300..c0dc313e14f7 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -96,6 +96,8 @@ constructor( uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL) } + val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal + /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */ suspend fun onOpenWidgetPicker( resources: Resources, diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 656e5cbafa97..97db43bdf0f8 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -25,6 +25,7 @@ import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer @@ -63,6 +64,7 @@ constructor( @Application private val scope: CoroutineScope, @Main private val resources: Resources, keyguardTransitionInteractor: KeyguardTransitionInteractor, + keyguardInteractor: KeyguardInteractor, private val communalInteractor: CommunalInteractor, tutorialInteractor: CommunalTutorialInteractor, private val shadeInteractor: ShadeInteractor, @@ -236,6 +238,14 @@ constructor( */ val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded) + // TODO(b/339667383): remove this temporary swipe gesture handle + /** + * The dream overlay has its own gesture handle as the SysUI window is not visible above the + * dream. This flow will be false when dreaming so that we don't show a duplicate handle when + * opening the hub over the dream. + */ + val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming) + companion object { const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt index 840c3a83b758..25591378938e 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.widgets import android.appwidget.AppWidgetHostView +import android.appwidget.AppWidgetProviderInfo import android.content.Context import android.graphics.Outline import android.graphics.Rect @@ -50,6 +51,11 @@ class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), enforceRoundedCorners() } + override fun setAppWidget(appWidgetId: Int, info: AppWidgetProviderInfo?) { + super.setAppWidget(appWidgetId, info) + setPadding(0, 0, 0, 0) + } + private val cornerRadiusEnforcementOutline: ViewOutlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View?, outline: Outline) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index f20fafccfd19..426f484e4d02 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -44,6 +44,7 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog import javax.inject.Inject +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** An Activity for editing the widgets that appear in hub mode. */ @@ -69,6 +70,8 @@ constructor( private var shouldOpenWidgetPickerOnStart = false + private var lockOnDestroy = false + private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(StartActivityForResult()) { result -> when (result.resultCode) { @@ -149,15 +152,18 @@ constructor( } private fun onEditDone() { - try { + lifecycleScope.launch { communalViewModel.changeScene( CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade ) - checkNotNull(windowManagerService).lockNow(/* options */ null) + + // Wait for the current scene to be idle on communal. + communalViewModel.isIdleOnCommunal.first { it } + // Then finish the activity (this helps to avoid a flash of lockscreen when locking + // in onDestroy()). + lockOnDestroy = true finish() - } catch (e: RemoteException) { - Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") } } @@ -190,5 +196,15 @@ constructor( override fun onDestroy() { super.onDestroy() communalViewModel.setEditModeOpen(false) + + if (lockOnDestroy) lockNow() + } + + private fun lockNow() { + try { + checkNotNull(windowManagerService).lockNow(/* options */ null) + } catch (e: RemoteException) { + Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") + } } } diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt deleted file mode 100644 index 0daa058720ba..000000000000 --- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.contrast - -import android.app.UiModeManager -import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH -import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM -import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD -import android.app.UiModeManager.ContrastUtils.fromContrastLevel -import android.app.UiModeManager.ContrastUtils.toContrastLevel -import android.os.Bundle -import android.provider.Settings -import android.view.View -import android.widget.FrameLayout -import com.android.internal.annotations.VisibleForTesting -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.res.R -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.util.settings.SecureSettings -import java.util.concurrent.Executor -import javax.inject.Inject - -/** Dialog to select contrast options */ -class ContrastDialogDelegate -@Inject -constructor( - private val sysuiDialogFactory: SystemUIDialog.Factory, - @Main private val mainExecutor: Executor, - private val uiModeManager: UiModeManager, - private val userTracker: UserTracker, - private val secureSettings: SecureSettings, -) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener { - - @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout> - lateinit var dialogView: View - @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD) - - override fun createDialog(): SystemUIDialog { - val dialog = sysuiDialogFactory.create(this) - dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null) - with(dialog) { - setView(dialogView) - - setTitle(R.string.quick_settings_contrast_label) - setNeutralButton(R.string.cancel) { _, _ -> - secureSettings.putFloatForUser( - Settings.Secure.CONTRAST_LEVEL, - initialContrast, - userTracker.userId - ) - dialog.dismiss() - } - setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() } - } - - return dialog - } - - override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { - contrastButtons = - mapOf( - CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard), - CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium), - CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high) - ) - - contrastButtons.forEach { (contrastLevel, contrastButton) -> - contrastButton.setOnClickListener { - val contrastValue = fromContrastLevel(contrastLevel) - secureSettings.putFloatForUser( - Settings.Secure.CONTRAST_LEVEL, - contrastValue, - userTracker.userId - ) - } - } - - initialContrast = uiModeManager.contrast - highlightContrast(toContrastLevel(initialContrast)) - } - - override fun onStart(dialog: SystemUIDialog) { - uiModeManager.addContrastChangeListener(mainExecutor, this) - } - - override fun onStop(dialog: SystemUIDialog) { - uiModeManager.removeContrastChangeListener(this) - } - - override fun onContrastChanged(contrast: Float) { - highlightContrast(toContrastLevel(contrast)) - } - - private fun highlightContrast(contrast: Int) { - contrastButtons.forEach { (level, button) -> button.isSelected = level == contrast } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index d2df276002cc..c2e1e33f5318 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -20,7 +20,6 @@ import android.app.Activity; import com.android.systemui.ForegroundServicesDialog; import com.android.systemui.communal.widgets.EditWidgetsActivity; -import com.android.systemui.contrast.ContrastDialogActivity; import com.android.systemui.keyguard.WorkLockActivity; import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.people.widget.LaunchConversationActivity; @@ -72,12 +71,6 @@ public abstract class DefaultActivityBinder { @ClassKey(BrightnessDialog.class) public abstract Activity bindBrightnessDialog(BrightnessDialog activity); - /** Inject into ContrastDialogActivity. */ - @Binds - @IntoMap - @ClassKey(ContrastDialogActivity.class) - public abstract Activity bindContrastDialogActivity(ContrastDialogActivity activity); - /** Inject into UsbDebuggingActivity. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt index 30a56a21e322..813fccffb62f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt @@ -48,6 +48,7 @@ import com.android.systemui.keyguard.data.repository.FaceDetectTableLog import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions @@ -302,7 +303,7 @@ constructor( private fun listenForSchedulingWatchdog() { keyguardTransitionInteractor - .transition(to = KeyguardState.GONE) + .transition(Edge.create(to = KeyguardState.GONE)) .filter { it.transitionState == TransitionState.FINISHED } .onEach { // We deliberately want to run this in background because scheduleWatchdog does diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt index 6c6683a483c7..669cd94c1f21 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt @@ -38,6 +38,7 @@ import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.DevicePosture +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN @@ -126,9 +127,9 @@ constructor( .launchIn(applicationScope) merge( - keyguardTransitionInteractor.transition(AOD, LOCKSCREEN), - keyguardTransitionInteractor.transition(OFF, LOCKSCREEN), - keyguardTransitionInteractor.transition(DOZING, LOCKSCREEN), + keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)), + keyguardTransitionInteractor.transition(Edge.create(OFF, LOCKSCREEN)), + keyguardTransitionInteractor.transition(Edge.create(DOZING, LOCKSCREEN)), ) .filter { it.transitionState == TransitionState.STARTED } .sample(powerInteractor.detailedWakefulness) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 60006c68639d..1e725eb71dde 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -21,6 +21,8 @@ import static android.service.dreams.Flags.dreamHandlesBeingObscured; import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress; import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion; import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion; +import static com.android.systemui.Flags.communalHub; +import static com.android.systemui.Flags.glanceableHubGestureHandle; import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM; import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; @@ -185,6 +187,7 @@ public class DreamOverlayContainerViewController extends DreamOverlayContainerView containerView, ComplicationHostViewController complicationHostViewController, @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView, + @Named(DreamOverlayModule.HUB_GESTURE_INDICATOR_VIEW) View hubGestureIndicatorView, DreamOverlayStatusBarViewController statusBarViewController, LowLightTransitionCoordinator lowLightTransitionCoordinator, BlurUtils blurUtils, @@ -220,6 +223,12 @@ public class DreamOverlayContainerViewController extends mComplicationHostViewController = complicationHostViewController; mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize( R.dimen.dream_overlay_y_offset); + + if (communalHub() && glanceableHubGestureHandle()) { + // TODO(b/339667383): remove this temporary swipe gesture handle + hubGestureIndicatorView.setVisibility(View.VISIBLE); + } + final View view = mComplicationHostViewController.getView(); mDreamOverlayContentView.addView(view, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index 999e6813ea55..789b7f8550d7 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -18,6 +18,7 @@ package com.android.systemui.dreams.dagger; import android.content.res.Resources; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; import androidx.lifecycle.Lifecycle; @@ -39,6 +40,7 @@ import javax.inject.Named; @Module public abstract class DreamOverlayModule { public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view"; + public static final String HUB_GESTURE_INDICATOR_VIEW = "hub_gesture_indicator_view"; public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset"; public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL = "burn_in_protection_update_interval"; @@ -71,6 +73,18 @@ public abstract class DreamOverlayModule { "R.id.dream_overlay_content must not be null"); } + /** + * Gesture indicator bar on the right edge of the screen to indicate to users that they can + * swipe to see their widgets on lock screen. + */ + @Provides + @DreamOverlayComponent.DreamOverlayScope + @Named(HUB_GESTURE_INDICATOR_VIEW) + public static View providesHubGestureIndicatorView(DreamOverlayContainerView view) { + return Preconditions.checkNotNull(view.findViewById(R.id.glanceable_hub_handle), + "R.id.glanceable_hub_handle must not be null"); + } + /** */ @Provides public static TouchInsetManager.TouchInsetSession providesTouchInsetSession( diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java index fff0c58eecb8..1c047ddcd3d8 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java @@ -98,7 +98,7 @@ public class CommunalTouchHandler implements TouchHandler { // Notification shade window has its own logic to be visible if the hub is open, no need to // do anything here other than send touch events over. session.registerInputListener(ev -> { - surfaces.handleExternalShadeWindowTouch((MotionEvent) ev); + surfaces.handleDreamTouch((MotionEvent) ev); if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) { var unused = session.pop(); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt index 221f790b1ab2..c5b3c5335fc8 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel @@ -97,7 +98,7 @@ constructor( .distinctUntilChanged() val transitionEnded = - keyguardTransitionInteractor.transition(from = DREAMING).filter { step -> + keyguardTransitionInteractor.transition(Edge.create(from = DREAMING)).filter { step -> step.transitionState == TransitionState.FINISHED || step.transitionState == TransitionState.CANCELED } diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt index 9876fe4482c0..f04cbb87214f 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt @@ -477,7 +477,7 @@ constructor( } private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) { - Trace.beginSection(entry.name) + Trace.beginSection(entry.name.take(Trace.MAX_SECTION_NAME_LEN)) preamble(entry) val dumpTime = measureTimeMillis(block) footer(entry, dumpTime) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt index d3f7e24bb87f..44f1c1e8305f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt @@ -17,19 +17,43 @@ package com.android.systemui.keyboard.shortcut.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperRepository import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState +import com.android.systemui.model.SysUiState +import com.android.systemui.settings.DisplayTracker +import com.android.systemui.shared.system.QuickStepContract import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch @SysUISingleton class ShortcutHelperInteractor @Inject -constructor(private val repository: ShortcutHelperRepository) { +constructor( + private val displayTracker: DisplayTracker, + @Background private val backgroundScope: CoroutineScope, + private val sysUiState: SysUiState, + private val repository: ShortcutHelperRepository +) { val state: Flow<ShortcutHelperState> = repository.state - fun onUserLeave() { + fun onViewClosed() { repository.hide() + setSysUiStateFlagEnabled(false) + } + + fun onViewOpened() { + setSysUiStateFlagEnabled(true) + } + + private fun setSysUiStateFlagEnabled(enabled: Boolean) { + backgroundScope.launch { + sysUiState + .setFlag(QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING, enabled) + .commitUpdate(displayTracker.defaultDisplayId) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt index 934f9ee9e90d..ef4156da4f7b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt @@ -63,12 +63,13 @@ constructor( setUpSheetDismissListener() setUpDismissOnTouchOutside() observeFinishRequired() + viewModel.onViewOpened() } override fun onDestroy() { super.onDestroy() if (isFinishing) { - viewModel.onUserLeave() + viewModel.onViewClosed() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt index 7e48c6523122..c623f5c23fd9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt @@ -38,7 +38,11 @@ constructor( .distinctUntilChanged() .flowOn(backgroundDispatcher) - fun onUserLeave() { - interactor.onUserLeave() + fun onViewClosed() { + interactor.onViewClosed() + } + + fun onViewOpened() { + interactor.onViewOpened() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 2cda72809a36..81c2d92d29e8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1076,6 +1076,33 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } }; + /** + * For now, the keyguard-appearing animation is a no-op, because we assume that this is + * happening while the screen is already off or turning off. + * + * TODO(b/278086361): create an animation for keyguard appearing over a non-showWhenLocked + * activity. + */ + private final IRemoteAnimationRunner.Stub mAppearAnimationRunner = + new IRemoteAnimationRunner.Stub() { + @Override + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to finish transition", e); + } + } + + @Override + public void onAnimationCancelled() { + } + }; + private final IRemoteAnimationRunner mOccludeAnimationRunner = new OccludeActivityLaunchRemoteAnimationRunner(mOccludeAnimationController); @@ -1164,7 +1191,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, finishedCallback.onAnimationFinished(); mOccludeByDreamAnimator = null; } catch (RemoteException e) { - e.printStackTrace(); + Log.e(TAG, "Failed to finish transition", e); } } }); @@ -1279,7 +1306,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION); } catch (RemoteException e) { - e.printStackTrace(); + Log.e(TAG, "Failed to finish transition", e); } } }); @@ -1545,6 +1572,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardTransitions.register( KeyguardService.wrap(this, getExitAnimationRunner()), + KeyguardService.wrap(this, getAppearAnimationRunner()), KeyguardService.wrap(this, getOccludeAnimationRunner()), KeyguardService.wrap(this, getOccludeByDreamAnimationRunner()), KeyguardService.wrap(this, getUnoccludeAnimationRunner())); @@ -2123,6 +2151,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, return validatingRemoteAnimationRunner(mExitAnimationRunner); } + public IRemoteAnimationRunner getAppearAnimationRunner() { + return validatingRemoteAnimationRunner(mAppearAnimationRunner); + } + public IRemoteAnimationRunner getOccludeAnimationRunner() { if (KeyguardWmStateRefactor.isEnabled()) { return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner()); @@ -3356,7 +3388,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } catch (RemoteException e) { mSurfaceBehindRemoteAnimationRequested = false; - e.printStackTrace(); + Log.e(TAG, "Failed to report keyguardGoingAway", e); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt index a65a8827fa48..3cbcb2cb4a0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt @@ -29,15 +29,20 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.utils.GlobalWindowManager import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -59,6 +64,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, private val featureFlags: FeatureFlags, + private val sceneInteractor: SceneInteractor, ) : CoreStartable, WakefulnessLifecycle.Observer { override fun start() { @@ -84,9 +90,15 @@ constructor( applicationScope.launch(bgDispatcher) { // We drop 1 to avoid triggering on initial collect(). - keyguardTransitionInteractor.transition(to = GONE).collect { transition -> - if (transition.transitionState == TransitionState.FINISHED) { - onKeyguardGone() + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState + .filter { it.isIdle(Scenes.Gone) } + .collect { onKeyguardGone() } + } else { + keyguardTransitionInteractor.transition(Edge.create(to = GONE)).collect { + if (it.transitionState == TransitionState.FINISHED) { + onKeyguardGone() + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index 00f50023b263..1b342edb28fe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -23,6 +23,7 @@ import android.view.RemoteAnimationTarget import android.view.WindowManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier import com.android.systemui.statusbar.policy.KeyguardStateController import java.util.concurrent.Executor @@ -40,6 +41,7 @@ constructor( private val activityTaskManagerService: IActivityTaskManager, private val keyguardStateController: KeyguardStateController, private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { /** @@ -141,6 +143,14 @@ constructor( finishedCallback: IRemoteAnimationFinishedCallback ) { if (apps.isNotEmpty()) { + // Ensure that we've started a dismiss keyguard transition. WindowManager can start the + // going away animation on its own, if an activity launches and then requests dismissing + // the keyguard. In this case, this is the first and only signal we'll receive to start + // a transition to GONE. + keyguardTransitionInteractor.startDismissKeyguardTransition( + reason = "Going away remote animation started" + ) + goingAwayRemoteAnimationFinishedCallback = finishedCallback keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0]) } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 7655d7a89dc3..f488d3b67fc7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -34,6 +34,7 @@ import java.util.UUID import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -42,6 +43,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.sync.Mutex /** * The source of truth for all keyguard transitions. @@ -129,6 +131,7 @@ constructor( private var lastStep: TransitionStep = TransitionStep() private var lastAnimator: ValueAnimator? = null + private val _currentTransitionMutex = Mutex() private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> = MutableStateFlow( TransitionInfo( @@ -146,6 +149,9 @@ constructor( */ private var updateTransitionId: UUID? = null + // Only used in a test environment + var forceDelayForRaceConditionTest = false + init { // Start with a FINISHED transition in OFF. KeyguardBootInteractor will transition from OFF // to either GONE or LOCKSCREEN once we're booted up and can determine which state we should @@ -162,9 +168,21 @@ constructor( override suspend fun startTransition(info: TransitionInfo): UUID? { _currentTransitionInfo.value = info + Log.d(TAG, "(Internal) Setting current transition info: $info") + + // There is no fairness guarantee with 'withContext', which means that transitions could + // be processed out of order. Use a Mutex to guarantee ordering. + _currentTransitionMutex.lock() + + // Only used in a test environment + if (forceDelayForRaceConditionTest) { + delay(50L) + } // Animators must be started on the main thread. return withContext("$TAG#startTransition", mainDispatcher) { + _currentTransitionMutex.unlock() + if (lastStep.from == info.from && lastStep.to == info.to) { Log.i(TAG, "Duplicate call to start the transition, rejecting: $info") return@withContext null diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt index eef4b97ae34d..96260770d89f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context +import android.util.Log import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor @@ -39,6 +40,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch /** @@ -96,10 +98,13 @@ constructor( keyguardUpdateMonitor.isFingerprintDetectionRunning && keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed } + .onEach { Log.d(TAG, "showIndicatorForPrimaryBouncer updated: $it") } private val showIndicatorForAlternateBouncer: Flow<Boolean> = // Note: this interactor internally verifies that SideFPS is enabled and running. - alternateBouncerInteractor.isVisible + alternateBouncerInteractor.isVisible.onEach { + Log.d(TAG, "showIndicatorForAlternateBouncer updated: $it") + } /** * Indicates whether the primary or alternate bouncers request showing the side fingerprint @@ -112,6 +117,7 @@ constructor( showForPrimaryBouncer || showForAlternateBouncer } .distinctUntilChanged() + .onEach { Log.d(TAG, "showIndicatorForDeviceEntry updated: $it") } private fun isBouncerActive(): Boolean { if (SceneContainerFlag.isEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index dad2d9692dbc..f1e98f3bbe6d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -263,7 +263,9 @@ constructor( } fun dismissKeyguard() { - scope.launch("$TAG#dismissKeyguard") { startTransitionTo(KeyguardState.GONE) } + scope.launch("$TAG#dismissKeyguard") { + startTransitionTo(KeyguardState.GONE, ownerReason = "#dismissKeyguard()") + } } private fun listenForLockscreenToGone() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index 857096e1c03b..b1ef76eba481 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context +import android.util.Log import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton @@ -42,6 +43,7 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @SysUISingleton @@ -78,7 +80,15 @@ constructor( private val refreshEvents: Flow<Unit> = merge( configurationInteractor.onAnyConfigurationChange, - fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map { Unit }, + fingerprintPropertyInteractor.propertiesInitialized + .filter { it } + .map { Unit } + .onEach { + Log.d( + "KeyguardBlueprintInteractor", + "triggering refreshEvent from fpPropertiesInitialized" + ) + }, ) init { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt index 08d29d4fc0a6..1aac1c5940b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt @@ -25,6 +25,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardDone import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -48,6 +51,7 @@ constructor( transitionInteractor: KeyguardTransitionInteractor, val dismissInteractor: KeyguardDismissInteractor, @Application private val applicationScope: CoroutineScope, + sceneInteractor: SceneInteractor, ) { val dismissAction: Flow<DismissAction> = repository.dismissAction @@ -72,7 +76,12 @@ constructor( ) private val finishedTransitionToGone: Flow<Unit> = - transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {} // map to Unit + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }.map {} + } else { + transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {} + } + val executeDismissAction: Flow<() -> KeyguardDone> = merge( finishedTransitionToGone, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 72857391793f..c44a40f33857 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -55,6 +55,7 @@ import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -181,7 +182,11 @@ constructor( } .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake } .debounce(50L) - .distinctUntilChanged() + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) /** Whether the keyguard is showing or not. */ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState") @@ -225,7 +230,19 @@ constructor( @JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow /** Whether the alternate bouncer is showing or not. */ - val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible + val alternateBouncerShowing: Flow<Boolean> = + bouncerRepository.alternateBouncerVisible.sample(isAbleToDream) { + alternateBouncerVisible, + isAbleToDream -> + if (isAbleToDream) { + // If the alternate bouncer will show over a dream, it is likely that the dream has + // requested a dismissal, which will stop the dream. By delaying this slightly, the + // DREAMING->LOCKSCREEN transition will now happen first, followed by + // LOCKSCREEN->ALTERNATE_BOUNCER. + delay(600L) + } + alternateBouncerVisible + } /** Observable for the [StatusBarState] */ val statusBarState: Flow<StatusBarState> = repository.statusBarState @@ -301,10 +318,12 @@ constructor( shadeRepository.legacyShadeExpansion.onStart { emit(0f) }, keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) }, ) { legacyShadeExpansion, goneValue -> - if (goneValue == 1f || (goneValue == 0f && legacyShadeExpansion == 0f)) { + val isLegacyShadeInResetPosition = + legacyShadeExpansion == 0f || legacyShadeExpansion == 1f + if (goneValue == 1f || (goneValue == 0f && isLegacyShadeInResetPosition)) { // Reset the translation value emit(0f) - } else if (legacyShadeExpansion > 0f && legacyShadeExpansion < 1f) { + } else if (!isLegacyShadeInResetPosition) { // On swipe up, translate the keyguard to reveal the bouncer, OR a GONE // transition is running, which means this is a swipe to dismiss. Values of // 0f and 1f need to be ignored in the legacy shade expansion. These can @@ -322,7 +341,11 @@ constructor( } } } - .distinctUntilChanged() + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = 0f, + ) val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index e711edc0c302..cf6942e2f245 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.keyguard.logging.KeyguardLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -26,6 +27,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch private val TAG = KeyguardTransitionAuditLogger::class.simpleName!! @@ -41,6 +43,7 @@ constructor( private val logger: KeyguardLogger, private val powerInteractor: PowerInteractor, private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + private val keyguardRootViewModel: KeyguardRootViewModel, private val shadeInteractor: ShadeInteractor, private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) { @@ -53,12 +56,6 @@ constructor( } scope.launch { - sharedNotificationContainerViewModel - .getMaxNotifications { height, useExtraShelfSpace -> height.toInt() } - .collect { logger.log(TAG, VERBOSE, "Notif: max height in px", it) } - } - - scope.launch { sharedNotificationContainerViewModel.isOnLockscreen.collect { logger.log(TAG, VERBOSE, "Notif: isOnLockscreen", it) } @@ -72,8 +69,8 @@ constructor( if (!SceneContainerFlag.isEnabled) { scope.launch { - sharedNotificationContainerViewModel.bounds.collect { - logger.log(TAG, VERBOSE, "Notif: bounds", it) + sharedNotificationContainerViewModel.bounds.debounce(20L).collect { + logger.log(TAG, VERBOSE, "Notif: bounds (debounced)", it) } } } @@ -113,6 +110,18 @@ constructor( } scope.launch { + keyguardInteractor.keyguardTranslationY.collect { + logger.log(TAG, VERBOSE, "keyguardTranslationY", it) + } + } + + scope.launch { + keyguardRootViewModel.burnInModel.debounce(20L).collect { + logger.log(TAG, VERBOSE, "BurnInModel (debounced)", it) + } + } + + scope.launch { keyguardInteractor.isKeyguardDismissible.collect { logger.log(TAG, VERBOSE, "isDismissible", it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 8f9a709801ae..c65dc305b0cc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import android.annotation.FloatRange import android.annotation.SuppressLint import android.util.Log +import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -31,10 +32,13 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.pairwise import java.util.UUID import javax.inject.Inject @@ -71,8 +75,9 @@ constructor( private val fromAlternateBouncerTransitionInteractor: dagger.Lazy<FromAlternateBouncerTransitionInteractor>, private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>, + private val sceneInteractor: dagger.Lazy<SceneInteractor>, ) { - private val transitionMap = mutableMapOf<Edge, MutableSharedFlow<TransitionStep>>() + private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>() /** * Numerous flows are derived from, or care directly about, the transition value in and out of a @@ -128,11 +133,11 @@ constructor( scope.launch { repository.transitions.collect { // FROM->TO - transitionMap[Edge(it.from, it.to)]?.emit(it) + transitionMap[Edge.create(it.from, it.to)]?.emit(it) // FROM->(ANY) - transitionMap[Edge(it.from, null)]?.emit(it) + transitionMap[Edge.create(it.from, null)]?.emit(it) // (ANY)->TO - transitionMap[Edge(null, it.to)]?.emit(it) + transitionMap[Edge.create(null, it.to)]?.emit(it) } } @@ -152,26 +157,70 @@ constructor( } } - /** Given an [edge], return a SharedFlow to collect only relevant [TransitionStep]. */ + fun transition(edge: Edge, edgeWithoutSceneContainer: Edge): Flow<TransitionStep> { + return transition(if (SceneContainerFlag.isEnabled) edge else edgeWithoutSceneContainer) + } + + /** Given an [edge], return a Flow to collect only relevant [TransitionStep]s. */ @SuppressLint("SharedFlowCreation") - fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> { - return transitionMap.getOrPut(edge) { - MutableSharedFlow( - extraBufferCapacity = 10, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) + fun transition(edge: Edge): Flow<TransitionStep> { + edge.verifyValidKeyguardStates() + val mappedEdge = getMappedEdge(edge) + + val flow: Flow<TransitionStep> = + transitionMap.getOrPut(mappedEdge) { + MutableSharedFlow( + extraBufferCapacity = 10, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + } + + return if (SceneContainerFlag.isEnabled) { + flow.filter { + val fromScene = + when (edge) { + is Edge.StateToState -> edge.from?.mapToSceneContainerScene() + is Edge.StateToScene -> edge.from.mapToSceneContainerScene() + is Edge.SceneToState -> edge.from + } + + val toScene = + when (edge) { + is Edge.StateToState -> edge.to?.mapToSceneContainerScene() + is Edge.StateToScene -> edge.to + is Edge.SceneToState -> edge.to.mapToSceneContainerScene() + } + + fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null + + return@filter (fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()) || + sceneInteractor.get().transitionState.value.isTransitioning(fromScene, toScene) + } + } else { + flow } } /** - * Receive all [TransitionStep] matching a filter of [from]->[to]. Allow nulls in order to match - * any transition, for instance (any)->GONE. + * Converts old KTF states to UNDEFINED when [SceneContainerFlag] is enabled. + * + * Does nothing otherwise. + * + * This method should eventually be removed when new code is only written for scene container. + * Even when all edges are ported today, there is still development on going in production that + * might utilize old states. */ - fun transition(from: KeyguardState? = null, to: KeyguardState? = null): Flow<TransitionStep> { - if (from == null && to == null) { - throw IllegalArgumentException("from and to cannot both be null") + private fun getMappedEdge(edge: Edge): Edge.StateToState { + if (!SceneContainerFlag.isEnabled) return edge as Edge.StateToState + return when (edge) { + is Edge.StateToState -> + Edge.create( + from = edge.from?.mapToSceneContainerState(), + to = edge.to?.mapToSceneContainerState() + ) + is Edge.SceneToState -> Edge.create(UNDEFINED, edge.to) + is Edge.StateToScene -> Edge.create(edge.from, UNDEFINED) } - return getOrCreateFlow(Edge(from = from, to = to)) } /** @@ -367,32 +416,37 @@ constructor( val isInTransitionToAnyState = isInTransitionWhere({ true }, { true }) fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> { - return getOrCreateFlow(Edge(from = fromState, to = null)) + return transition(Edge.create(from = fromState, to = null)) } fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> { - return getOrCreateFlow(Edge(from = null, to = toState)) + return transition(Edge.create(from = null, to = toState)) } /** * Called to start a transition that will ultimately dismiss the keyguard from the current * state. + * + * This is called exclusively by sources that can authoritatively say we should be unlocked, + * including KeyguardSecurityContainerController and WindowManager. */ - fun startDismissKeyguardTransition() { + fun startDismissKeyguardTransition(reason: String = "") { // TODO(b/336576536): Check if adaptation for scene framework is needed if (SceneContainerFlag.isEnabled) return - when (val startedState = startedKeyguardState.replayCache.last()) { + Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)") + when (val startedState = currentTransitionInfoInternal.value.to) { LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard() PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer() ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer() AOD -> fromAodTransitionInteractor.get().dismissAod() DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing() - else -> - Log.e( - "KeyguardTransitionInteractor", - "We don't know how to dismiss keyguard from state $startedState." + KeyguardState.GONE -> + Log.i( + TAG, + "Already transitioning to GONE; ignoring startDismissKeyguardTransition." ) + else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.") } } @@ -400,7 +454,7 @@ constructor( fun isInTransitionToState( state: KeyguardState, ): Flow<Boolean> { - return getOrCreateFlow(Edge(from = null, to = state)) + return transition(Edge.create(from = null, to = state)) .mapLatest { it.transitionState.isTransitioning() } .onStart { emit(false) } .distinctUntilChanged() @@ -409,12 +463,16 @@ constructor( /** * Whether we're in a transition to and from the given [KeyguardState]s, but haven't yet * completed it. + * + * Provide [edgeWithoutSceneContainer] when the edge is different from what it is without it. If + * the edges are equal before and after the flag it is sufficient to provide just [edge]. */ - fun isInTransition( - from: KeyguardState, - to: KeyguardState, - ): Flow<Boolean> { - return getOrCreateFlow(Edge(from = from, to = to)) + fun isInTransition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<Boolean> { + return if (SceneContainerFlag.isEnabled) { + transition(edge) + } else { + transition(edgeWithoutSceneContainer ?: edge) + } .mapLatest { it.transitionState.isTransitioning() } .onStart { emit(false) } .distinctUntilChanged() @@ -426,7 +484,7 @@ constructor( fun isInTransitionFromState( state: KeyguardState, ): Flow<Boolean> { - return getOrCreateFlow(Edge(from = state, to = null)) + return transition(Edge.create(from = state, to = null)) .mapLatest { it.transitionState.isTransitioning() } .onStart { emit(false) } .distinctUntilChanged() @@ -477,7 +535,7 @@ constructor( * If you only care about a single state for both from and to, instead use the optimized * [isInTransition]. */ - fun isInTransitionWhere( + private fun isInTransitionWhere( fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean ): Flow<Boolean> { return repository.transitions @@ -529,4 +587,8 @@ constructor( @FloatRange(from = 0.0, to = 1.0) value: Float, state: TransitionState ) = repository.updateTransition(transitionId, value, state) + + companion object { + private val TAG = KeyguardTransitionInteractor::class.simpleName + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index b2a24ca85b07..323ceef06a97 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -229,6 +229,7 @@ sealed class TransitionInteractor( startTransitionTo( toState = KeyguardState.OCCLUDED, modeOnCanceled = TransitionModeOnCanceled.RESET, + ownerReason = "keyguardInteractor.onCameraLaunchDetected", ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index dc35e4311d25..1e2db7c36432 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockMode +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.scene.domain.interactor.SceneInteractor diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt index a0f9be629132..4f516f586216 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt @@ -15,8 +15,98 @@ */ package com.android.systemui.keyguard.shared.model -/** FROM -> TO keyguard transition. null values are allowed to signify FROM -> *, or * -> TO */ -data class Edge( - val from: KeyguardState?, - val to: KeyguardState?, -) +import android.util.Log +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED +import com.android.systemui.scene.shared.flag.SceneContainerFlag + +/** + * Represents an edge either between two Keyguard Transition Framework states (KTF) or a KTF state + * and a scene container scene. Passing [null] in either [from] or [to] indicates a wildcard. + * + * Wildcards are not allowed for transitions involving a scene. Use [sceneInteractor] directly + * instead. Reason: [TransitionStep]s are not emitted for every edge leading into/out of a scene. + * For example: Lockscreen -> Gone would be emitted as LOCKSCREEN -> UNDEFINED but Bouncer -> Gone + * would not emit anything. + */ +sealed class Edge { + + fun verifyValidKeyguardStates() { + when (this) { + is StateToState -> verifyValidKeyguardStates(from, to) + is SceneToState -> verifyValidKeyguardStates(null, to) + is StateToScene -> verifyValidKeyguardStates(from, null) + } + } + + private fun verifyValidKeyguardStates(from: KeyguardState?, to: KeyguardState?) { + val mappedFrom = from?.mapToSceneContainerState() + val mappedTo = to?.mapToSceneContainerState() + + val fromChanged = from != mappedFrom + val toChanged = to != mappedTo + + if (SceneContainerFlag.isEnabled) { + if (fromChanged && toChanged) { + // TODO:(b/330311871) As we come close to having all current edges converted these + // error messages can be converted to throw such that future developers fail early + // when they introduce invalid edges. + Log.e( + TAG, + """ + The edge ${from?.name} => ${to?.name} was automatically converted to + ${mappedFrom?.name} => ${mappedTo?.name} but does not exist anymore in KTF. + Please remove or port this edge to scene container.""" + .trimIndent(), + ) + } else if ((fromChanged && to == null) || (toChanged && from == null)) { + Log.e( + TAG, + """ + The edge ${from?.name} => ${to?.name} was automatically converted to + ${mappedFrom?.name} => ${mappedTo?.name}. Wildcards are not allowed together + with UNDEFINED because it will only be tracking edges leading in and out of + the Lockscreen scene but miss others. Please remove or port this edge.""" + .trimIndent(), + Exception() + ) + } else if (fromChanged || toChanged) { + Log.w( + TAG, + """ + The edge ${from?.name} => ${to?.name} was automatically converted to + ${mappedFrom?.name} => ${mappedTo?.name} it probably exists but needs explicit + conversion. Please remove or port this edge to scene container.""" + .trimIndent(), + ) + } + } else { + if (from == UNDEFINED || to == UNDEFINED) { + Log.e( + TAG, + "UNDEFINED should not be used when scene container is disabled", + ) + } + } + } + + data class StateToState(val from: KeyguardState?, val to: KeyguardState?) : Edge() { + init { + check(!(from == null && to == null)) { "to and from can't both be null" } + } + } + + data class StateToScene(val from: KeyguardState, val to: SceneKey) : Edge() + + data class SceneToState(val from: SceneKey, val to: KeyguardState) : Edge() + + companion object { + private const val TAG = "Edge" + + fun create(from: KeyguardState? = null, to: KeyguardState? = null) = StateToState(from, to) + + fun create(from: KeyguardState, to: SceneKey) = StateToScene(from, to) + + fun create(from: SceneKey, to: KeyguardState) = SceneToState(from, to) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt index 6d96db34e23a..6a2bb5f51c5d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt @@ -15,6 +15,9 @@ */ package com.android.systemui.keyguard.shared.model +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.scene.shared.model.Scenes + /** List of all possible states to transition to/from */ enum class KeyguardState { /** @@ -84,6 +87,40 @@ enum class KeyguardState { /** An activity is displaying over the keyguard. */ OCCLUDED; + fun mapToSceneContainerState(): KeyguardState { + return when (this) { + OFF, + DOZING, + DREAMING, + DREAMING_LOCKSCREEN_HOSTED, + AOD, + ALTERNATE_BOUNCER, + OCCLUDED, + LOCKSCREEN -> this + GLANCEABLE_HUB, + PRIMARY_BOUNCER, + GONE, + UNDEFINED -> UNDEFINED + } + } + + fun mapToSceneContainerScene(): SceneKey? { + return when (this) { + OFF, + DOZING, + DREAMING, + DREAMING_LOCKSCREEN_HOSTED, + AOD, + ALTERNATE_BOUNCER, + OCCLUDED, + LOCKSCREEN -> Scenes.Lockscreen + GLANCEABLE_HUB -> Scenes.Communal + PRIMARY_BOUNCER -> Scenes.Bouncer + GONE -> Scenes.Gone + UNDEFINED -> null + } + } + companion object { /** Whether the lockscreen is visible when we're FINISHED in the given state. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt index 735b10907c73..23aa21cfbf31 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.scene.shared.flag.SceneContainerFlag import javax.inject.Inject import kotlin.math.max import kotlin.math.min @@ -52,20 +53,20 @@ constructor( /** Invoke once per transition between FROM->TO states to get access to a shared flow. */ fun setup( duration: Duration, - from: KeyguardState?, - to: KeyguardState?, + edge: Edge, ): FlowBuilder { - if (from == null && to == null) { - throw IllegalArgumentException("from and to are both null") - } - - return FlowBuilder(duration, Edge(from, to)) + return FlowBuilder(duration, edge) } inner class FlowBuilder( private val transitionDuration: Duration, private val edge: Edge, ) { + fun setupWithoutSceneContainer(edge: Edge.StateToState): FlowBuilder { + if (SceneContainerFlag.isEnabled) return this + return setup(this.transitionDuration, edge) + } + /** * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted * in the range of [0, 1]. View animations should begin and end within a subset of this @@ -117,7 +118,7 @@ constructor( if (!duration.isPositive()) { throw IllegalArgumentException("duration must be a positive number: $duration") } - if ((startTime + duration).compareTo(transitionDuration) > 0) { + if ((startTime + duration) > transitionDuration) { throw IllegalArgumentException( "startTime($startTime) + duration($duration) must be" + " <= transitionDuration($transitionDuration)" @@ -153,7 +154,7 @@ constructor( } return transitionInteractor - .getOrCreateFlow(edge) + .transition(edge) .map { step -> StateToValue( from = step.from, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt index 4fd92d70fb07..9da11ceb8c1f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -42,8 +44,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.AOD, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = AOD), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt index 9649af73eadb..55a48b6bd49b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -42,8 +44,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromAlternateBouncerTransitionInteractor.TO_DOZING_DURATION, - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.DOZING, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = DOZING), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt index 8c6be989d8d9..bb4fb7961ed0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt @@ -19,11 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.SysuiStatusBarStateController import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -44,11 +46,14 @@ constructor( private val statusBarStateController: SysuiStatusBarStateController, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_GONE_DURATION, - from = ALTERNATE_BOUNCER, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = TO_GONE_DURATION, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = ALTERNATE_BOUNCER, to = GONE), + ) fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { var startAlpha = 1f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt index 27febd38a97c..3f2ef29c9570 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt @@ -18,8 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_OCCLUDED_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -40,8 +41,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_OCCLUDED_DURATION, - from = ALTERNATE_BOUNCER, - to = KeyguardState.OCCLUDED, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = OCCLUDED), ) override val deviceEntryParentViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt index 759288136783..f0bccacadc57 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -37,11 +40,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.PRIMARY_BOUNCER, - ) + animationFlow + .setup( + duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Bouncer), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = ALTERNATE_BOUNCER, to = PRIMARY_BOUNCER), + ) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt index 5cf100e78e6e..4128c529644d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt @@ -34,8 +34,8 @@ constructor( alternateBouncerInteractor: AlternateBouncerInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { - private val deviceSupportsAlternateBouncer: Flow<Boolean> = - alternateBouncerInteractor.alternateBouncerSupported + val canShowAlternateBouncer: Flow<Boolean> = alternateBouncerInteractor.canShowAlternateBouncer + private val isTransitioningToOrFromOrShowingAlternateBouncer: Flow<Boolean> = keyguardTransitionInteractor .transitionValue(KeyguardState.ALTERNATE_BOUNCER) @@ -43,8 +43,8 @@ constructor( .distinctUntilChanged() val alternateBouncerWindowRequired: Flow<Boolean> = - deviceSupportsAlternateBouncer.flatMapLatest { deviceSupportsAlternateBouncer -> - if (deviceSupportsAlternateBouncer) { + canShowAlternateBouncer.flatMapLatest { canShowAlternateBouncer -> + if (canShowAlternateBouncer) { isTransitioningToOrFromOrShowingAlternateBouncer } else { flowOf(false) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt index adc090de1d4a..8e8b09d5cd67 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -37,11 +40,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromAodTransitionInteractor.TO_GONE_DURATION, - from = KeyguardState.AOD, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = FromAodTransitionInteractor.TO_GONE_DURATION, + edge = Edge.create(from = AOD, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = AOD, to = GONE), + ) /** * AOD -> GONE should fade out the lockscreen contents. This transition plays both during wake diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt index cbbb82039329..b267ecb42ac2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -19,9 +19,10 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition @@ -39,7 +40,6 @@ import kotlinx.coroutines.flow.Flow class AodToLockscreenTransitionViewModel @Inject constructor( - deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, shadeInteractor: ShadeInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { @@ -47,8 +47,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = AOD, to = LOCKSCREEN), ) private var isShadeExpanded = false diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt index 445575f7e55d..2497defba5a2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -36,8 +38,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION, - from = KeyguardState.AOD, - to = KeyguardState.OCCLUDED, + edge = Edge.create(from = AOD, to = OCCLUDED), ) /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt index 9a23007eea4a..35f05f55caa1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -37,11 +40,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, - from = KeyguardState.AOD, - to = KeyguardState.PRIMARY_BOUNCER, - ) + animationFlow + .setup( + duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + edge = Edge.create(from = AOD, to = Scenes.Bouncer), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = AOD, to = PRIMARY_BOUNCER), + ) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt index 570f37710c24..caa043622e18 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt @@ -19,11 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.SysuiStatusBarStateController import dagger.Lazy @@ -73,8 +75,12 @@ constructor( return animationFlow .setup( duration = duration, - from = from, - to = GONE, + // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene -> scene + // transition + edge = Edge.create(from = from, to = Scenes.Gone) + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = from, to = GONE), ) .sharedFlow( duration = duration, @@ -96,11 +102,16 @@ constructor( var leaveShadeOpen: Boolean = false var willRunDismissFromKeyguard: Boolean = false val transitionAnimation = - animationFlow.setup( - duration = duration, - from = fromState, - to = GONE, - ) + animationFlow + .setup( + duration = duration, + // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene -> + // scene transition + edge = Edge.create(from = fromState, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = fromState, to = GONE), + ) return shadeInteractor.anyExpansion .map { it > 0f } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt index 8851a51f15b0..77ebfcea9c96 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GONE_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -37,11 +40,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_GONE_DURATION, - from = KeyguardState.DOZING, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = TO_GONE_DURATION, + edge = Edge.create(from = DOZING, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = DOZING, to = GONE), + ) fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { var startAlpha = 1f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt index 168d6e16daa2..a460d515e0b2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt @@ -18,7 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -39,8 +41,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION, - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = DOZING, to = LOCKSCREEN), ) val shortcutsAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt index c0b11959cbd9..f33752fc04d4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -38,8 +40,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION, - from = KeyguardState.DOZING, - to = KeyguardState.OCCLUDED, + edge = Edge.create(from = DOZING, to = OCCLUDED), ) /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt index 4395c3436a71..7ddf641e9e8e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -38,11 +41,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_PRIMARY_BOUNCER_DURATION, - from = KeyguardState.DOZING, - to = KeyguardState.PRIMARY_BOUNCER, - ) + animationFlow + .setup( + duration = TO_PRIMARY_BOUNCER_DURATION, + edge = Edge.create(from = DOZING, to = Scenes.Bouncer), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = DOZING, to = PRIMARY_BOUNCER), + ) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt index 67568e12a4a1..57ed455e7b13 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt @@ -18,7 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDreamingLockscreenHostedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -34,8 +36,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = DREAMING_LOCKSCREEN_HOSTED, to = LOCKSCREEN), ) val shortcutsAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt index 0fa74752ea0d..754ed6cc3327 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -40,12 +42,12 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromDreamingTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.DREAMING, - to = KeyguardState.AOD, + edge = Edge.create(from = DREAMING, to = AOD), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) + override val deviceEntryParentViewAlpha: Flow<Float> = deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt index a083c24e3d4d..00aa102ec5bb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt @@ -19,10 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -40,11 +43,14 @@ constructor( configurationInteractor: ConfigurationInteractor, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_GLANCEABLE_HUB_DURATION, - from = KeyguardState.DREAMING, - to = KeyguardState.GLANCEABLE_HUB, - ) + animationFlow + .setup( + duration = TO_GLANCEABLE_HUB_DURATION, + edge = Edge.create(from = DREAMING, to = Scenes.Communal), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = DREAMING, to = GLANCEABLE_HUB), + ) val dreamOverlayTranslationX: Flow<Float> = configurationInteractor diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt index ec7b931161f6..1bdf6d29f6af 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt @@ -18,10 +18,13 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow -import kotlinx.coroutines.flow.Flow +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject +import kotlinx.coroutines.flow.Flow @SysUISingleton class DreamingToGoneTransitionViewModel @@ -31,13 +34,15 @@ constructor( ) { private val transitionAnimation = - animationFlow.setup( + animationFlow + .setup( duration = FromDreamingTransitionInteractor.TO_GONE_DURATION, - from = KeyguardState.DREAMING, - to = KeyguardState.GONE, + edge = Edge.create(from = DREAMING, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = DREAMING, to = GONE), ) /** Lockscreen views alpha */ val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) - -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index f191aa7d7e4e..82381eb45f9a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -20,7 +20,9 @@ import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -45,8 +47,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.DREAMING, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = DREAMING, to = LOCKSCREEN), ) /** Dream overlay y-translation on exit */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt index 3716458079cd..d594488208a1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt @@ -19,10 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -41,11 +44,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FROM_GLANCEABLE_HUB_DURATION, - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.DREAMING, - ) + animationFlow + .setup( + duration = FROM_GLANCEABLE_HUB_DURATION, + edge = Edge.create(from = Scenes.Communal, to = DREAMING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GLANCEABLE_HUB, to = DREAMING), + ) val dreamOverlayAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt index e05b500620d5..046b95f0c6ae 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt @@ -20,10 +20,13 @@ import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,11 +48,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = - animationFlow.setup( - duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.LOCKSCREEN, - ) + animationFlow + .setup( + duration = TO_LOCKSCREEN_DURATION, + edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN), + ) val keyguardAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt index 300121facfd5..cd98bb00b9dc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_OCCLUDED_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -32,11 +35,12 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_OCCLUDED_DURATION, - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.OCCLUDED, - ) + animationFlow + .setup( + duration = TO_OCCLUDED_DURATION, + edge = Edge.create(from = Scenes.Communal, to = OCCLUDED), + ) + .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = OCCLUDED)) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt index 3540bec5d3e7..74f7d75fa326 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt @@ -20,10 +20,13 @@ import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,11 +45,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_AOD_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.AOD, - ) + animationFlow + .setup( + duration = TO_AOD_DURATION, + edge = Edge.create(from = Scenes.Gone, to = AOD), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = AOD), + ) /** y-translation from the top of the screen for AOD */ fun enterFromTopTranslationY(translatePx: Int): Flow<StateToValue> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt index 80a6bda65b99..70c0032a30b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DOZING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -40,11 +43,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_DOZING_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.DOZING, - ) + animationFlow + .setup( + duration = TO_DOZING_DURATION, + edge = Edge.create(from = Scenes.Gone, to = DOZING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = DOZING), + ) val lockscreenAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt index b52746364a8b..627f0de696d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt @@ -18,8 +18,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -36,11 +39,14 @@ constructor( ) { private val transitionAnimation = - animationFlow.setup( - duration = TO_DREAMING_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, - ) + animationFlow + .setup( + duration = TO_DREAMING_DURATION, + edge = Edge.create(from = Scenes.Gone, to = DREAMING_LOCKSCREEN_HOSTED), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = DREAMING_LOCKSCREEN_HOSTED), + ) /** Lockscreen views alpha - hide immediately */ val lockscreenAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt index 102242a4a7b0..f8b6e2819b9b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt @@ -19,8 +19,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -34,11 +37,14 @@ constructor( ) { private val transitionAnimation = - animationFlow.setup( - duration = TO_DREAMING_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.DREAMING, - ) + animationFlow + .setup( + duration = TO_DREAMING_DURATION, + edge = Edge.create(from = Scenes.Gone, to = DREAMING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = DREAMING), + ) /** Lockscreen views y-translation */ fun lockscreenTranslationY(translatePx: Int): Flow<Float> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt index a2ce408955a1..08ec43f9ae5f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -33,11 +36,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN - ) + animationFlow + .setup( + duration = TO_LOCKSCREEN_DURATION, + edge = Edge.create(from = Scenes.Gone, to = LOCKSCREEN), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = LOCKSCREEN), + ) val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index bbcea56799ea..f405b9d5a07c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -38,6 +39,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.ui.StateToValue +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor import com.android.systemui.statusbar.phone.DozeParameters @@ -115,14 +117,18 @@ constructor( private val shadeInteractor: ShadeInteractor, ) { private var burnInJob: Job? = null - private val burnInModel = MutableStateFlow(BurnInModel()) + internal val burnInModel = MutableStateFlow(BurnInModel()) val burnInLayerVisibility: Flow<Int> = keyguardTransitionInteractor.startedKeyguardState .filter { it == AOD || it == LOCKSCREEN } .map { VISIBLE } - val goneToAodTransition = keyguardTransitionInteractor.transition(from = GONE, to = AOD) + val goneToAodTransition = + keyguardTransitionInteractor.transition( + edge = Edge.create(Scenes.Gone, AOD), + edgeWithoutSceneContainer = Edge.create(GONE, AOD) + ) private val goneToAodTransitionRunning: Flow<Boolean> = goneToAodTransition @@ -144,7 +150,10 @@ constructor( private val alphaOnShadeExpansion: Flow<Float> = combineTransform( - keyguardTransitionInteractor.isInTransition(from = LOCKSCREEN, to = GONE), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone), + edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE), + ), isOnLockscreen, shadeInteractor.qsExpansion, shadeInteractor.shadeExpansion, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt index 1f9f3043dfdf..8b5b347a763d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt @@ -20,7 +20,9 @@ import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -45,8 +47,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromLockscreenTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, + edge = Edge.create(from = LOCKSCREEN, to = AOD), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt index c836f01e2ee9..27a1f7afb4e1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DOZING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -40,8 +42,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_DOZING_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.DOZING, + edge = Edge.create(from = LOCKSCREEN, to = DOZING), ) val lockscreenAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt index 19b9cf4733f9..778dbed90ec1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt @@ -18,7 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_HOSTED_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -34,8 +36,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_DREAMING_HOSTED_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + edge = Edge.create(from = LOCKSCREEN, to = DREAMING_LOCKSCREEN_HOSTED), ) val shortcutsAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt index 13522a6742ac..579abeb7e092 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -40,8 +42,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_DREAMING_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.DREAMING, + edge = Edge.create(from = LOCKSCREEN, to = DREAMING), ) /** Lockscreen views y-translation */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt index dae7897a2325..c7273b7cfd48 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt @@ -20,10 +20,13 @@ import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,11 +48,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = - animationFlow.setup( - duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GLANCEABLE_HUB, - ) + animationFlow + .setup( + duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION, + edge = Edge.create(from = LOCKSCREEN, to = Scenes.Communal), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB), + ) val keyguardAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt index f03625eda9b5..1314e8863c71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt @@ -19,10 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow.FlowBuilder import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.SysuiStatusBarStateController import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -42,11 +45,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation: FlowBuilder = - animationFlow.setup( - duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION, + edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = LOCKSCREEN, to = GONE), + ) val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt index dd6652e69792..fcf8c14fc326 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -20,7 +20,9 @@ import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R @@ -45,8 +47,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_OCCLUDED_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.OCCLUDED, + edge = Edge.create(from = KeyguardState.LOCKSCREEN, to = OCCLUDED), ) /** Lockscreen views alpha */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt index 0cfc75757b7d..23c44b0a38fb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -39,11 +42,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.PRIMARY_BOUNCER, - ) + animationFlow + .setup( + duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + edge = Edge.create(from = LOCKSCREEN, to = Scenes.Bouncer), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER), + ) val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt index d7ba46b6e708..706a3c440723 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -41,8 +43,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromOccludedTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.OCCLUDED, - to = KeyguardState.AOD, + edge = Edge.create(from = OCCLUDED, to = AOD), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt index 91554e3e914a..af019300c764 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt @@ -18,7 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -38,8 +40,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromOccludedTransitionInteractor.TO_DOZING_DURATION, - from = KeyguardState.OCCLUDED, - to = KeyguardState.DOZING, + edge = Edge.create(from = OCCLUDED, to = DOZING), ) /** Lockscreen views alpha */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt index 73a4a9d4d2bb..47e202b8fcc3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -32,11 +35,12 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_GLANCEABLE_HUB_DURATION, - from = KeyguardState.OCCLUDED, - to = KeyguardState.GLANCEABLE_HUB, - ) + animationFlow + .setup( + duration = TO_GLANCEABLE_HUB_DURATION, + edge = Edge.create(OCCLUDED, Scenes.Communal) + ) + .setupWithoutSceneContainer(edge = Edge.create(OCCLUDED, GLANCEABLE_HUB)) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt index d2c9cfbd71b8..98dba393a545 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt @@ -17,8 +17,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -33,11 +36,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = - animationFlow.setup( - duration = DEFAULT_DURATION, - from = KeyguardState.OCCLUDED, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = DEFAULT_DURATION, + edge = Edge.create(from = OCCLUDED, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = OCCLUDED, to = GONE), + ) fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> { var currentAlpha = 0f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index a09d58ac381b..36c7d5b98dba 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -23,7 +23,9 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsIntera import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R @@ -56,8 +58,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.OCCLUDED, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = OCCLUDED, to = LOCKSCREEN), ) /** Lockscreen views y-translation */ @@ -101,7 +102,7 @@ constructor( .filter { (wasOccluded, isOccluded) -> wasOccluded && !isOccluded && - keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED + keyguardTransitionInteractor.getCurrentState() == OCCLUDED } .map { 0f } ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt index cf6a533ed76b..1eecbd5fbda1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt @@ -17,7 +17,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -34,8 +36,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = 250.milliseconds, - from = KeyguardState.OFF, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = OFF, to = LOCKSCREEN), ) val shortcutsAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt index 942903bbabd7..009f85d4bcb9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,11 +45,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.AOD, - ) + animationFlow + .setup( + duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, + edge = Edge.create(from = Scenes.Bouncer, to = AOD), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = PRIMARY_BOUNCER, to = AOD), + ) val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt index 13f651a9ff5d..e5bb46432226 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_DOZING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -42,11 +45,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_DOZING_DURATION, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.DOZING, - ) + animationFlow + .setup( + duration = TO_DOZING_DURATION, + edge = Edge.create(from = Scenes.Bouncer, to = DOZING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = PRIMARY_BOUNCER, to = DOZING), + ) val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt index b1fa7101804f..7ae455818952 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt @@ -20,11 +20,13 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.SysuiStatusBarStateController import dagger.Lazy import javax.inject.Inject @@ -49,11 +51,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = - animationFlow.setup( - duration = TO_GONE_DURATION, - from = PRIMARY_BOUNCER, - to = GONE, - ) + animationFlow + .setup( + duration = TO_GONE_DURATION, + edge = Edge.create(from = PRIMARY_BOUNCER, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = PRIMARY_BOUNCER, to = GONE), + ) private var leaveShadeOpen: Boolean = false private var willRunDismissFromKeyguard: Boolean = false @@ -88,6 +93,7 @@ constructor( } else { createBouncerAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard) } + private fun createBouncerAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> { return transitionAnimation.sharedFlow( duration = 200.milliseconds, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt index 25750415e88f..7511101bf04e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -39,11 +42,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.LOCKSCREEN, - ) + animationFlow + .setup( + duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, + edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN), + ) val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt index d1fee903e6f5..1a0f582fb100 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt @@ -21,9 +21,7 @@ import android.app.BroadcastOptions import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.media.session.MediaController import android.media.session.MediaSession -import android.media.session.PlaybackState import android.provider.Settings import android.util.Log import com.android.internal.jank.Cuj @@ -42,7 +40,6 @@ import com.android.systemui.media.dialog.MediaOutputDialogManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.kotlin.pairwiseBy import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow @@ -70,19 +67,6 @@ constructor( .map { entries -> entries[instanceId]?.let { toMediaControlModel(it) } } .distinctUntilChanged() - val isStartedPlaying: Flow<Boolean> = - mediaControl - .map { mediaControl -> - mediaControl?.token?.let { token -> - MediaController(applicationContext, token).playbackState?.let { - it.state == PlaybackState.STATE_PLAYING - } - } - ?: false - } - .pairwiseBy(initialValue = false) { wasPlaying, isPlaying -> !wasPlaying && isPlaying } - .distinctUntilChanged() - val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange fun removeMediaControl( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt index 73fb5583ab3e..fed93f037638 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt @@ -260,44 +260,50 @@ object MediaControlViewBinder { } SEMANTIC_ACTIONS_ALL.forEachIndexed { index, id -> - val button = viewHolder.getAction(id) - val actionViewModel = viewModel.actionButtons[index] - if (button.id == R.id.actionPrev) { - actionViewModel?.let { - viewController.setUpPrevButtonInfo(true, it.notVisibleValue) - } - } else if (button.id == R.id.actionNext) { - actionViewModel?.let { - viewController.setUpNextButtonInfo(true, it.notVisibleValue) - } + val buttonView = viewHolder.getAction(id) + val buttonModel = viewModel.actionButtons[index] + if (buttonView.id == R.id.actionPrev) { + viewController.setUpPrevButtonInfo( + buttonModel.isEnabled, + buttonModel.notVisibleValue + ) + } else if (buttonView.id == R.id.actionNext) { + viewController.setUpNextButtonInfo( + buttonModel.isEnabled, + buttonModel.notVisibleValue + ) } - actionViewModel?.let { action -> - val animHandler = (button.tag ?: AnimationBindHandler()) as AnimationBindHandler - animHandler.tryExecute { - if (animHandler.updateRebindId(action.rebindId)) { + val animHandler = (buttonView.tag ?: AnimationBindHandler()) as AnimationBindHandler + animHandler.tryExecute { + if (buttonModel.isEnabled) { + if (animHandler.updateRebindId(buttonModel.rebindId)) { animHandler.unregisterAll() - animHandler.tryRegister(action.icon) - animHandler.tryRegister(action.background) + animHandler.tryRegister(buttonModel.icon) + animHandler.tryRegister(buttonModel.background) bindButtonCommon( - button, + buttonView, viewHolder.multiRippleView, - action, + buttonModel, viewController, falsingManager, ) } - val visible = action.isVisibleWhenScrubbing || !viewController.isScrubbing - setSemanticButtonVisibleAndAlpha( - viewHolder.getAction(id), - viewController.expandedLayout, - viewController.collapsedLayout, - visible, - action.notVisibleValue, - action.showInCollapsed - ) + } else { + animHandler.unregisterAll() + clearButton(buttonView) } + val visible = + buttonModel.isEnabled && + (buttonModel.isVisibleWhenScrubbing || !viewController.isScrubbing) + setSemanticButtonVisibleAndAlpha( + viewHolder.getAction(id), + viewController.expandedLayout, + viewController.collapsedLayout, + visible, + buttonModel.notVisibleValue, + buttonModel.showInCollapsed + ) } - ?: clearButton(button) } } else { // Hide buttons that only appear for semantic actions @@ -309,22 +315,16 @@ object MediaControlViewBinder { // Set all generic buttons genericButtons.forEachIndexed { index, button -> if (index < viewModel.actionButtons.size) { - viewModel.actionButtons[index]?.let { action -> - bindButtonCommon( - button, - viewHolder.multiRippleView, - action, - viewController, - falsingManager, - ) - setVisibleAndAlpha(expandedSet, button.id, visible = true) - setVisibleAndAlpha( - collapsedSet, - button.id, - visible = action.showInCollapsed - ) - } - ?: clearButton(button) + val action = viewModel.actionButtons[index] + bindButtonCommon( + button, + viewHolder.multiRippleView, + action, + viewController, + falsingManager, + ) + setVisibleAndAlpha(expandedSet, button.id, visible = true) + setVisibleAndAlpha(collapsedSet, button.id, visible = action.showInCollapsed) } else { // Hide any unused buttons clearButton(button) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index d09e997da20f..19e3e0715989 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -46,6 +46,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN @@ -73,6 +74,9 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PageIndicator import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.shared.system.SysUiStatsLog.SMARTSPACE_CARD_REPORTED import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD @@ -142,6 +146,7 @@ constructor( private val secureSettings: SecureSettings, private val mediaCarouselViewModel: MediaCarouselViewModel, private val mediaViewControllerFactory: Provider<MediaViewController>, + private val sceneInteractor: SceneInteractor, ) : Dumpable { /** The current width of the carousel */ var currentCarouselWidth: Int = 0 @@ -190,6 +195,7 @@ constructor( @VisibleForTesting lateinit var settingsButton: View private set + private val mediaContent: ViewGroup @VisibleForTesting var pageIndicator: PageIndicator private var needsReordering: Boolean = false @@ -302,7 +308,11 @@ constructor( * It will be called when the container is out of view. */ lateinit var updateUserVisibility: () -> Unit - lateinit var updateHostVisibility: () -> Unit + var updateHostVisibility: () -> Unit = {} + set(value) { + field = value + mediaCarouselViewModel.updateHostVisibility = value + } private val isReorderingAllowed: Boolean get() = visualStabilityProvider.isReorderingAllowed @@ -339,6 +349,20 @@ constructor( configurationController.addCallback(configListener) if (!mediaFlags.isMediaControlsRefactorEnabled()) { setUpListeners() + } else { + val visualStabilityCallback = OnReorderingAllowedListener { + mediaCarouselViewModel.onReorderingAllowed() + + // Update user visibility so that no extra impression will be logged when + // activeMediaIndex resets to 0 + if (this::updateUserVisibility.isInitialized) { + updateUserVisibility() + } + + // Let's reset our scroll position + mediaCarouselScrollHandler.scrollToStart() + } + visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback) } mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> // The pageIndicator is not laid out yet when we get the current state update, @@ -360,10 +384,6 @@ constructor( ) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) mediaCarousel.repeatWhenAttached { - if (mediaFlags.isMediaControlsRefactorEnabled()) { - mediaCarouselViewModel.onAttached() - mediaCarouselScrollHandler.scrollToStart() - } repeatOnLifecycle(Lifecycle.State.STARTED) { listenForAnyStateToGoneKeyguardTransition(this) listenForAnyStateToLockscreenTransition(this) @@ -586,9 +606,7 @@ constructor( if (!immediately) { // Although it wasn't requested, we were able to process the removal // immediately since reordering is allowed. So, notify hosts to update - if (this@MediaCarouselController::updateHostVisibility.isInitialized) { - updateHostVisibility() - } + updateHostVisibility() } } else { keysNeedRemoval.add(key) @@ -639,9 +657,13 @@ constructor( @VisibleForTesting internal fun listenForAnyStateToGoneKeyguardTransition(scope: CoroutineScope): Job { return scope.launch { - keyguardTransitionInteractor - .transition(to = GONE) - .filter { it.transitionState == TransitionState.FINISHED } + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) } + } else { + keyguardTransitionInteractor.transition(Edge.create(to = GONE)).filter { + it.transitionState == TransitionState.FINISHED + } + } .collect { showMediaCarousel() updateHostVisibility() @@ -653,7 +675,7 @@ constructor( internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job { return scope.launch { keyguardTransitionInteractor - .transition(to = LOCKSCREEN) + .transition(Edge.create(to = LOCKSCREEN)) .filter { it.transitionState == TransitionState.FINISHED } .collect { if (!allowMediaPlayerOnLockScreen) { @@ -741,6 +763,7 @@ constructor( } } viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded) + controllerByViewModel[commonViewModel] = viewController updateViewControllerToState(viewController, noAnimation = true) updatePageIndicator() if ( @@ -754,7 +777,6 @@ constructor( mediaCarouselScrollHandler.onPlayersChanged() mediaFrame.requiresRemeasuring = true commonViewModel.onAdded(commonViewModel) - controllerByViewModel[commonViewModel] = viewController } private fun onUpdated(commonViewModel: MediaCommonViewModel) { @@ -1598,6 +1620,7 @@ internal object MediaPlayerData { // Whether should prioritize Smartspace card. internal var shouldPrioritizeSs: Boolean = false private set + internal var smartspaceMediaData: SmartspaceMediaData? = null private set diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index 2b5985882a6e..38377088a2d7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -709,12 +709,6 @@ constructor( // For Turbulence noise. val loadingEffectView = mediaViewHolder.loadingEffectView - turbulenceNoiseAnimationConfig = - createTurbulenceNoiseConfig( - loadingEffectView, - turbulenceNoiseView, - colorSchemeTransition - ) noiseDrawCallback = object : PaintDrawCallback { override fun onDraw(paint: Paint) { @@ -809,6 +803,14 @@ constructor( fun setUpTurbulenceNoise() { if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!this::turbulenceNoiseAnimationConfig.isInitialized) { + turbulenceNoiseAnimationConfig = + createTurbulenceNoiseConfig( + mediaViewHolder.loadingEffectView, + mediaViewHolder.turbulenceNoiseView, + colorSchemeTransition + ) + } if (Flags.shaderlibLoadingEffectRefactor()) { if (!this::loadingEffect.isInitialized) { loadingEffect = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt index fd5f44594ae8..4e9093642c6b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt @@ -57,12 +57,12 @@ constructor( val mediaItems: StateFlow<List<MediaCommonViewModel>> = interactor.currentMedia .map { sortedItems -> - buildList { + val mediaList = buildList { sortedItems.forEach { commonModel -> // When view is started we should make sure to clean models that are pending // removal. // This action should only be triggered once. - if (!isAttached || !modelsPendingRemoval.contains(commonModel)) { + if (!allowReorder || !modelsPendingRemoval.contains(commonModel)) { when (commonModel) { is MediaCommonModel.MediaControl -> add(toViewModel(commonModel)) is MediaCommonModel.MediaRecommendations -> @@ -70,11 +70,16 @@ constructor( } } } - if (isAttached) { - modelsPendingRemoval.clear() + } + if (allowReorder) { + if (modelsPendingRemoval.size > 0) { + updateHostVisibility() } - isAttached = false + modelsPendingRemoval.clear() } + allowReorder = false + + mediaList } .stateIn( scope = applicationScope, @@ -82,6 +87,8 @@ constructor( initialValue = emptyList(), ) + var updateHostVisibility: () -> Unit = {} + private val mediaControlByInstanceId = mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>() @@ -89,15 +96,15 @@ constructor( private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf() - private var isAttached = false + private var allowReorder = false fun onSwipeToDismiss() { logger.logSwipeDismiss() interactor.onSwipeToDismiss() } - fun onAttached() { - isAttached = true + fun onReorderingAllowed() { + allowReorder = true interactor.reorderMedia() } @@ -194,7 +201,11 @@ constructor( ) { if (immediatelyRemove || isReorderingAllowed()) { interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L) - // TODO if not immediate remove update host visibility + if (!immediatelyRemove) { + // Although it wasn't requested, we were able to process the removal + // immediately since reordering is allowed. So, notify hosts to update + updateHostVisibility() + } } else { modelsPendingRemoval.add(commonModel) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index bc364c36a298..1944f072e7dd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.pm.PackageManager import android.media.session.MediaController import android.media.session.MediaSession.Token +import android.media.session.PlaybackState import android.text.TextUtils import android.util.Log import androidx.constraintlayout.widget.ConstraintSet @@ -40,16 +41,14 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.res.R -import com.android.systemui.util.kotlin.sample import java.util.concurrent.Executor import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map /** Models UI state and handles user input for a media control. */ class MediaControlViewModel( @@ -60,31 +59,20 @@ class MediaControlViewModel( private val logger: MediaUiEventLogger, ) { - private val isAnyButtonClicked: MutableStateFlow<Boolean> = MutableStateFlow(false) - - private val playTurbulenceNoise: Flow<Boolean> = - interactor.mediaControl.sample( - combine(isAnyButtonClicked, interactor.isStartedPlaying) { - isButtonClicked, - isStartedPlaying -> - isButtonClicked && isStartedPlaying - } - .distinctUntilChanged() - ) - @OptIn(ExperimentalCoroutinesApi::class) val player: Flow<MediaPlayerViewModel?> = interactor.onAnyMediaConfigurationChange .flatMapLatest { - combine(playTurbulenceNoise, interactor.mediaControl) { - playTurbulenceNoise, - mediaControl -> - mediaControl?.let { toViewModel(it, playTurbulenceNoise) } + interactor.mediaControl.map { mediaControl -> + mediaControl?.let { toViewModel(it) } } } .distinctUntilChanged() .flowOn(backgroundDispatcher) + private var isPlaying = false + private var isAnyButtonClicked = false + private fun onDismissMediaData( token: Token?, uid: Int, @@ -95,10 +83,8 @@ class MediaControlViewModel( interactor.removeMediaControl(token, instanceId, MEDIA_PLAYER_ANIMATION_DELAY) } - private suspend fun toViewModel( - model: MediaControlModel, - playTurbulenceNoise: Boolean - ): MediaPlayerViewModel? { + private suspend fun toViewModel(model: MediaControlModel): MediaPlayerViewModel? { + val mediaController = model.token?.let { MediaController(applicationContext, it) } val wallpaperColors = MediaArtworkHelper.getWallpaperColor( applicationContext, @@ -118,8 +104,14 @@ class MediaControlViewModel( val gutsViewModel = toGutsViewModel(model, scheme) + // Set playing state + val wasPlaying = isPlaying + isPlaying = + mediaController?.playbackState?.let { it.state == PlaybackState.STATE_PLAYING } ?: false + // Resetting button clicks state. - isAnyButtonClicked.value = false + val wasButtonClicked = isAnyButtonClicked + isAnyButtonClicked = false return MediaPlayerViewModel( contentDescription = { gutsVisible -> @@ -144,7 +136,7 @@ class MediaControlViewModel( shouldAddGradient = wallpaperColors != null, colorScheme = scheme, canShowTime = canShowScrubbingTimeViews(model.semanticActionButtons), - playTurbulenceNoise = playTurbulenceNoise, + playTurbulenceNoise = isPlaying && !wasPlaying && wasButtonClicked, useSemanticActions = model.semanticActionButtons != null, actionButtons = toActionViewModels(model), outputSwitcher = toOutputSwitcherViewModel(model), @@ -168,9 +160,7 @@ class MediaControlViewModel( seekBarViewModel.updateStaticProgress(model.resumeProgress) } else { backgroundExecutor.execute { - seekBarViewModel.updateController( - model.token?.let { MediaController(applicationContext, it) } - ) + seekBarViewModel.updateController(mediaController) } } } @@ -283,16 +273,17 @@ class MediaControlViewModel( ) } - private fun toActionViewModels(model: MediaControlModel): List<MediaActionViewModel?> { + private fun toActionViewModels(model: MediaControlModel): List<MediaActionViewModel> { val semanticActionButtons = model.semanticActionButtons?.let { mediaButton -> - with(mediaButton) { - val isScrubbingTimeEnabled = canShowScrubbingTimeViews(mediaButton) - SEMANTIC_ACTIONS_ALL.map { buttonId -> - getActionById(buttonId)?.let { - toSemanticActionViewModel(model, it, buttonId, isScrubbingTimeEnabled) - } - } + val isScrubbingTimeEnabled = canShowScrubbingTimeViews(mediaButton) + SEMANTIC_ACTIONS_ALL.map { buttonId -> + toSemanticActionViewModel( + model, + mediaButton.getActionById(buttonId), + buttonId, + isScrubbingTimeEnabled + ) } } val notifActionButtons = @@ -304,7 +295,7 @@ class MediaControlViewModel( private fun toSemanticActionViewModel( model: MediaControlModel, - mediaAction: MediaAction, + mediaAction: MediaAction?, buttonId: Int, canShowScrubbingTimeViews: Boolean ): MediaActionViewModel { @@ -312,9 +303,9 @@ class MediaControlViewModel( val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId) val shouldHideWhenScrubbing = canShowScrubbingTimeViews && hideWhenScrubbing return MediaActionViewModel( - icon = mediaAction.icon, - contentDescription = mediaAction.contentDescription, - background = mediaAction.background, + icon = mediaAction?.icon, + contentDescription = mediaAction?.contentDescription, + background = mediaAction?.background, isVisibleWhenScrubbing = !shouldHideWhenScrubbing, notVisibleValue = if ( @@ -326,11 +317,11 @@ class MediaControlViewModel( ConstraintSet.GONE }, showInCollapsed = showInCollapsed, - rebindId = mediaAction.rebindId, + rebindId = mediaAction?.rebindId, buttonId = buttonId, - isEnabled = mediaAction.action != null, + isEnabled = mediaAction?.action != null, onClicked = { id -> - mediaAction.action?.let { + mediaAction?.action?.let { onButtonClicked(id, model.uid, model.packageName, model.instanceId, it) } }, @@ -366,7 +357,7 @@ class MediaControlViewModel( ) { logger.logTapAction(id, uid, packageName, instanceId) // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT) - isAnyButtonClicked.value = true + isAnyButtonClicked = true action.run() } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt index d1014e83ea11..433434129b96 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt @@ -35,7 +35,7 @@ data class MediaPlayerViewModel( val canShowTime: Boolean, val playTurbulenceNoise: Boolean, val useSemanticActions: Boolean, - val actionButtons: List<MediaActionViewModel?>, + val actionButtons: List<MediaActionViewModel>, val outputSwitcher: MediaOutputSwitcherViewModel, val gutsMenu: GutsViewModel, val onClicked: (Expandable) -> Unit, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt index 412c006806bf..9265bfb2f66b 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt @@ -140,10 +140,11 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 val bitmapShader = bitmapShader ?: return val thumbnailData = thumbnailData ?: return + val thumbnail = thumbnailData.thumbnail ?: return val display = context.display ?: return val windowMetrics = windowManager.maximumWindowMetrics - previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height) + previewRect.set(0, 0, thumbnail.width, thumbnail.height) val currentRotation: Int = display.rotation val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java index 481b4761ccd9..67fe0e981b09 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java @@ -73,6 +73,10 @@ public class SysUiState implements Dumpable { return mFlags; } + public boolean isFlagEnabled(@SystemUiStateFlags long flag) { + return (mFlags & flag) != 0; + } + /** Methods to this call can be chained together before calling {@link #commitUpdate(int)}. */ public SysUiState setFlag(@SystemUiStateFlags long flag, boolean enabled) { final Boolean overrideOrNull = mSceneContainerPlugin.flagValueOverride(flag); diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt index 3907a7240258..5e6ee4d3c700 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt @@ -16,12 +16,20 @@ package com.android.systemui.qrcodescanner.dagger +import com.android.systemui.Flags import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.QRCodeScannerTile +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor +import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel import com.android.systemui.res.R import dagger.Binds import dagger.Module @@ -54,5 +62,24 @@ interface QRCodeScannerModule { ), instanceId = uiEventLogger.getNewInstanceId(), ) + + /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */ + @Provides + @IntoMap + @StringKey(QR_CODE_SCANNER_TILE_SPEC) + fun provideQRCodeScannerTileViewModel( + factory: QSTileViewModelFactory.Static<QRCodeScannerTileModel>, + mapper: QRCodeScannerTileMapper, + stateInteractor: QRCodeScannerTileDataInteractor, + userActionInteractor: QRCodeScannerTileUserActionInteractor + ): QSTileViewModel = + if (Flags.qsNewTilesFuture()) + factory.create( + TileSpec.create(QR_CODE_SCANNER_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + else StubQSTileViewModel } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt index 2c8a5a4981d0..1336d640a9e5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs import android.service.quicksettings.Tile import android.text.TextUtils +import android.widget.Switch import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.external.CustomTile import com.android.systemui.qs.nano.QsTileState @@ -44,8 +45,8 @@ fun QSTile.State.toProto(): QsTileState? { } label?.let { state.label = it.toString() } secondaryLabel?.let { state.secondaryLabel = it.toString() } - if (this is QSTile.BooleanState) { - state.booleanState = value + if (expandedAccessibilityClassName == Switch::class.java.name) { + state.booleanState = state.state == QsTileState.ACTIVE } return state } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index d26ae0a4dac8..5d35a69be910 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -42,6 +42,7 @@ import android.text.format.DateUtils; import android.util.Log; import android.view.IWindowManager; import android.view.WindowManagerGlobal; +import android.widget.Button; import android.widget.Switch; import androidx.annotation.Nullable; @@ -502,6 +503,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener, if (state instanceof BooleanState) { state.expandedAccessibilityClassName = Switch.class.getName(); ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE); + } else { + state.expandedAccessibilityClassName = Button.class.getName(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index 0696fbe996c0..2cc3985a88ad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt @@ -29,8 +29,10 @@ import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInter import com.android.systemui.qs.panels.shared.model.GridConsistencyLog import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType import com.android.systemui.qs.panels.ui.compose.GridLayout import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout import dagger.Binds import dagger.Module import dagger.Provides @@ -63,6 +65,14 @@ interface PanelsModule { } @Provides + @IntoSet + fun provideStretchedGridLayout( + gridLayout: StretchedGridLayout + ): Pair<GridLayoutType, GridLayout> { + return Pair(StretchedGridLayoutType, gridLayout) + } + + @Provides fun provideGridLayoutMap( entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>> ): Map<GridLayoutType, GridLayout> { @@ -70,6 +80,13 @@ interface PanelsModule { } @Provides + fun provideGridLayoutTypes( + entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>> + ): Set<GridLayoutType> { + return entries.map { it.first }.toSet() + } + + @Provides @IntoSet fun provideGridConsistencyInteractor( consistencyInteractor: InfiniteGridConsistencyInteractor @@ -78,6 +95,14 @@ interface PanelsModule { } @Provides + @IntoSet + fun provideStretchedGridConsistencyInteractor( + consistencyInteractor: NoopGridConsistencyInteractor + ): Pair<GridLayoutType, GridTypeConsistencyInteractor> { + return Pair(StretchedGridLayoutType, consistencyInteractor) + } + + @Provides fun provideGridConsistencyInteractorMap( entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>> ): Map<GridLayoutType, GridTypeConsistencyInteractor> { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt index 542d0cbc425e..31795d59bb2b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt @@ -26,10 +26,17 @@ import kotlinx.coroutines.flow.asStateFlow interface GridLayoutTypeRepository { val layout: StateFlow<GridLayoutType> + fun setLayout(type: GridLayoutType) } @SysUISingleton class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository { private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType) override val layout = _layout.asStateFlow() + + override fun setLayout(type: GridLayoutType) { + if (_layout.value != type) { + _layout.value = type + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt index b6be5780bb60..4af1b2223c4c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt @@ -20,9 +20,13 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository import com.android.systemui.qs.panels.shared.model.GridLayoutType import javax.inject.Inject -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow @SysUISingleton -class GridLayoutTypeInteractor @Inject constructor(repo: GridLayoutTypeRepository) { - val layout: Flow<GridLayoutType> = repo.layout +class GridLayoutTypeInteractor @Inject constructor(private val repo: GridLayoutTypeRepository) { + val layout: StateFlow<GridLayoutType> = repo.layout + + fun setLayoutType(type: GridLayoutType) { + repo.setLayout(type) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt index 74e906c621cb..b437f645d4bc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt @@ -18,6 +18,8 @@ package com.android.systemui.qs.panels.domain.interactor import android.util.Log import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.shared.model.TileRow import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject @@ -35,7 +37,7 @@ constructor( */ override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> { val newTiles: MutableList<TileSpec> = mutableListOf() - val row = TileRow(columns = gridSizeInteractor.columns.value) + val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value) val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value val tilesQueue = ArrayDeque( @@ -54,7 +56,7 @@ constructor( while (tilesQueue.isNotEmpty()) { if (row.isFull()) { - newTiles.addAll(row.tileSpecs()) + newTiles.addAll(row.tiles.map { it.tile }) row.clear() } @@ -66,13 +68,13 @@ constructor( // We'll try to either add an icon tile from the queue to complete the row, or // remove an icon tile from the current row to free up space. - val iconTile: SizedTile? = tilesQueue.firstOrNull { it.width == 1 } + val iconTile: SizedTile<TileSpec>? = tilesQueue.firstOrNull { it.width == 1 } if (iconTile != null) { tilesQueue.remove(iconTile) tilesQueue.addFirst(tile) row.maybeAddTile(iconTile) } else { - val tileToRemove: SizedTile? = row.findLastIconTile() + val tileToRemove: SizedTile<TileSpec>? = row.findLastIconTile() if (tileToRemove != null) { row.removeTile(tileToRemove) row.maybeAddTile(tile) @@ -84,7 +86,7 @@ constructor( // If the row does not have an icon tile, add the incomplete row. // Note: this shouldn't happen because an icon tile is guaranteed to be in a // row that doesn't have enough space for a large tile. - val tileSpecs = row.tileSpecs() + val tileSpecs = row.tiles.map { it.tile } Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs") newTiles.addAll(tileSpecs) row.clear() @@ -95,48 +97,11 @@ constructor( } // Add last row that might be incomplete - newTiles.addAll(row.tileSpecs()) + newTiles.addAll(row.tiles.map { it.tile }) return newTiles.toList() } - /** Tile with a width representing the number of columns it should take. */ - private data class SizedTile(val spec: TileSpec, val width: Int) - - private class TileRow(private val columns: Int) { - private var availableColumns = columns - private val tiles: MutableList<SizedTile> = mutableListOf() - - fun tileSpecs(): List<TileSpec> { - return tiles.map { it.spec } - } - - fun maybeAddTile(tile: SizedTile): Boolean { - if (availableColumns - tile.width >= 0) { - tiles.add(tile) - availableColumns -= tile.width - return true - } - return false - } - - fun findLastIconTile(): SizedTile? { - return tiles.findLast { it.width == 1 } - } - - fun removeTile(tile: SizedTile) { - tiles.remove(tile) - availableColumns += tile.width - } - - fun clear() { - tiles.clear() - availableColumns = columns - } - - fun isFull(): Boolean = availableColumns == 0 - } - private companion object { const val TAG = "InfiniteGridConsistencyInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt index 4e40042a49b0..97ceacc6926d 100644 --- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,22 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.contrast -import android.app.Activity -import android.os.Bundle -import javax.inject.Inject +package com.android.systemui.qs.panels.domain.interactor -/** Trampoline activity responsible for creating a [ContrastDialogDelegate] */ -class ContrastDialogActivity -@Inject -constructor( - private val contrastDialogDelegate : ContrastDialogDelegate -) : Activity() { +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - contrastDialogDelegate.createDialog().show() - finish() - } +@SysUISingleton +class NoopConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor { + override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt index 23110dcaa560..501730a7c8a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt @@ -25,3 +25,9 @@ interface GridLayoutType /** Grid type representing a scrollable vertical grid. */ data object InfiniteGridLayoutType : GridLayoutType + +/** + * Grid type representing a scrollable vertical grid where tiles will stretch to fill in empty + * spaces. + */ +data object StretchedGridLayoutType : GridLayoutType diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt new file mode 100644 index 000000000000..7e4381bbff03 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.shared.model + +/** Represents a tile of type [T] associated with a width */ +data class SizedTile<T>(val tile: T, val width: Int) + +/** Represents a row of [SizedTile] with a maximum width of [columns] */ +class TileRow<T>(private val columns: Int) { + private var availableColumns = columns + private val _tiles: MutableList<SizedTile<T>> = mutableListOf() + val tiles: List<SizedTile<T>> + get() = _tiles.toList() + + fun maybeAddTile(tile: SizedTile<T>): Boolean { + if (availableColumns - tile.width >= 0) { + _tiles.add(tile) + availableColumns -= tile.width + return true + } + return false + } + + fun findLastIconTile(): SizedTile<T>? { + return _tiles.findLast { it.width == 1 } + } + + fun removeTile(tile: SizedTile<T>) { + _tiles.remove(tile) + availableColumns += tile.width + } + + fun clear() { + _tiles.clear() + availableColumns = columns + } + + fun isFull(): Boolean = availableColumns == 0 +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt index bac0f604e294..f5ee720faff6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt @@ -16,85 +16,23 @@ package com.android.systemui.qs.panels.ui.compose -import android.graphics.drawable.Animatable -import android.text.TextUtils -import androidx.appcompat.content.res.AppCompatResources -import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi -import androidx.compose.animation.graphics.res.animatedVectorResource -import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter -import androidx.compose.animation.graphics.vector.AnimatedImageVector -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.basicMarquee -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Arrangement.spacedBy -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyGridScope -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Remove -import androidx.compose.material3.Icon -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.onClick -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.stateDescription -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.compose.animation.Expandable -import com.android.compose.theme.colorAttr -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.ui.compose.Icon -import com.android.systemui.common.ui.compose.load import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor -import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes -import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel -import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes -import com.android.systemui.qs.panels.ui.viewmodel.TileUiState import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel -import com.android.systemui.qs.panels.ui.viewmodel.toUiState -import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.mapLatest @SysUISingleton class InfiniteGridLayout @@ -104,8 +42,6 @@ constructor( private val gridSizeInteractor: InfiniteGridSizeInteractor ) : GridLayout { - private object TileType - @Composable override fun TileGrid( tiles: List<TileViewModel>, @@ -140,55 +76,6 @@ constructor( } } - @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class) - @Composable - private fun Tile( - tile: TileViewModel, - iconOnly: Boolean, - modifier: Modifier, - ) { - val state: TileUiState by - tile.state - .mapLatest { it.toUiState() } - .collectAsStateWithLifecycle(initialValue = tile.currentState.toUiState()) - val context = LocalContext.current - - Expandable( - color = colorAttr(state.colors.background), - shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)), - ) { - Row( - modifier = - modifier - .combinedClickable( - onClick = { tile.onClick(it) }, - onLongClick = { tile.onLongClick(it) } - ) - .tileModifier(state.colors), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = tileHorizontalArrangement(iconOnly), - ) { - val icon = - remember(state.icon) { - state.icon.get().let { - if (it is QSTileImpl.ResourceIcon) { - Icon.Resource(it.resId, null) - } else { - Icon.Loaded(it.getDrawable(context), null) - } - } - } - TileContent( - label = state.label.toString(), - secondaryLabel = state.secondaryLabel?.toString(), - icon = icon, - colors = state.colors, - iconOnly = iconOnly - ) - } - } - } - @Composable override fun EditTileGrid( tiles: List<EditTileViewModel>, @@ -196,262 +83,16 @@ constructor( onAddTile: (TileSpec, Int) -> Unit, onRemoveTile: (TileSpec) -> Unit, ) { - val (currentTiles, otherTiles) = tiles.partition { it.isCurrent } - val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null } - val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { - onAddTile(it, POSITION_AT_END) - } - val iconOnlySpecs by - iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle( - initialValue = emptySet() - ) - val isIconOnly: (TileSpec) -> Boolean = - remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } } + val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() - TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { - // These Text are just placeholders to see the different sections. Not final UI. - item(span = { GridItemSpan(maxLineSpan) }) { - Text("Current tiles", color = Color.White) - } - - editTiles( - currentTiles, - ClickAction.REMOVE, - onRemoveTile, - isIconOnly, - indicatePosition = true, - ) - - item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) } - - editTiles( - otherTilesStock, - ClickAction.ADD, - addTileToEnd, - isIconOnly, - ) - - item(span = { GridItemSpan(maxLineSpan) }) { - Text("Custom tiles to add", color = Color.White) - } - - editTiles( - otherTilesCustom, - ClickAction.ADD, - addTileToEnd, - isIconOnly, - ) - } - } - - private fun LazyGridScope.editTiles( - tiles: List<EditTileViewModel>, - clickAction: ClickAction, - onClick: (TileSpec) -> Unit, - isIconOnly: (TileSpec) -> Boolean, - indicatePosition: Boolean = false, - ) { - items( - count = tiles.size, - key = { tiles[it].tileSpec.spec }, - span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) }, - contentType = { TileType } - ) { - val viewModel = tiles[it] - val canClick = - when (clickAction) { - ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions - ClickAction.REMOVE -> - AvailableEditActions.REMOVE in viewModel.availableEditActions - } - val onClickActionName = - when (clickAction) { - ClickAction.ADD -> - stringResource(id = R.string.accessibility_qs_edit_tile_add_action) - ClickAction.REMOVE -> - stringResource(id = R.string.accessibility_qs_edit_remove_tile_action) - } - val stateDescription = - if (indicatePosition) { - stringResource(id = R.string.accessibility_qs_edit_position, it + 1) - } else { - "" - } - - Box( - modifier = - Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) } - .animateItem() - .semantics { - onClick(onClickActionName) { false } - this.stateDescription = stateDescription - } - ) { - EditTile( - tileViewModel = viewModel, - isIconOnly(viewModel.tileSpec), - modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) - ) - if (canClick) { - Badge(clickAction, Modifier.align(Alignment.TopEnd)) - } - } - } - } - - @Composable - private fun Badge(action: ClickAction, modifier: Modifier = Modifier) { - Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) { - Icon( - imageVector = - when (action) { - ClickAction.ADD -> Icons.Filled.Add - ClickAction.REMOVE -> Icons.Filled.Remove - }, - "", - tint = Color.Black, - ) - } - } - - @Composable - private fun EditTile( - tileViewModel: EditTileViewModel, - iconOnly: Boolean, - modifier: Modifier = Modifier, - ) { - val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec - val colors = ActiveTileColorAttributes - - Row( - modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label }, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = tileHorizontalArrangement(iconOnly) - ) { - TileContent( - label = label, - secondaryLabel = tileViewModel.appName?.load(), - colors = colors, - icon = tileViewModel.icon, - iconOnly = iconOnly, - animateIconToEnd = true, - ) - } - } - - private enum class ClickAction { - ADD, - REMOVE, - } -} - -@OptIn(ExperimentalAnimationGraphicsApi::class) -@Composable -private fun TileIcon( - icon: Icon, - color: Color, - animateToEnd: Boolean = false, -) { - val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size)) - val context = LocalContext.current - val loadedDrawable = - remember(icon, context) { - when (icon) { - is Icon.Loaded -> icon.drawable - is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res) - } - } - if (loadedDrawable !is Animatable) { - Icon( - icon = icon, - tint = color, + DefaultEditTileGrid( + tiles = tiles, + iconOnlySpecs = iconOnlySpecs, + columns = GridCells.Fixed(columns), modifier = modifier, + onAddTile = onAddTile, + onRemoveTile = onRemoveTile, ) - } else if (icon is Icon.Resource) { - val image = AnimatedImageVector.animatedVectorResource(id = icon.res) - val painter = - if (animateToEnd) { - rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true) - } else { - var atEnd by remember(icon.res) { mutableStateOf(false) } - LaunchedEffect(key1 = icon.res) { - delay(350) - atEnd = true - } - rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) - } - Image( - painter = painter, - contentDescription = null, - colorFilter = ColorFilter.tint(color = color), - modifier = modifier - ) - } -} - -@Composable -private fun TileLazyGrid( - modifier: Modifier = Modifier, - columns: GridCells, - content: LazyGridScope.() -> Unit, -) { - LazyVerticalGrid( - columns = columns, - verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), - horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), - modifier = modifier, - content = content, - ) -} - -@Composable -private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier { - return fillMaxWidth() - .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))) - .background(colorAttr(colors.background)) - .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin)) -} - -@Composable -private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal { - val horizontalAlignment = - if (iconOnly) { - Alignment.CenterHorizontally - } else { - Alignment.Start - } - return spacedBy( - space = dimensionResource(id = R.dimen.qs_label_container_margin), - alignment = horizontalAlignment - ) -} - -@Composable -private fun TileContent( - label: String, - secondaryLabel: String?, - icon: Icon, - colors: TileColorAttributes, - iconOnly: Boolean, - animateIconToEnd: Boolean = false, -) { - TileIcon(icon, colorAttr(colors.icon), animateIconToEnd) - - if (!iconOnly) { - Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) { - Text( - label, - color = colorAttr(colors.label), - modifier = Modifier.basicMarquee(), - ) - if (!TextUtils.isEmpty(secondaryLabel)) { - Text( - secondaryLabel ?: "", - color = colorAttr(colors.secondaryLabel), - modifier = Modifier.basicMarquee(), - ) - } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt new file mode 100644 index 000000000000..ddd97c2e8944 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor +import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.shared.model.TileRow +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.res.R +import javax.inject.Inject + +@SysUISingleton +class StretchedGridLayout +@Inject +constructor( + private val iconTilesInteractor: IconTilesInteractor, + private val gridSizeInteractor: InfiniteGridSizeInteractor, +) : GridLayout { + + @Composable + override fun TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + ) { + DisposableEffect(tiles) { + val token = Any() + tiles.forEach { it.startListening(token) } + onDispose { tiles.forEach { it.stopListening(token) } } + } + + // Tile widths [normal|stretched] + // Icon [3 | 4] + // Large [6 | 8] + val columns = 12 + val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() + val stretchedTiles = + remember(tiles) { + val sizedTiles = + tiles.map { + SizedTile( + it, + if (iconTilesSpecs.contains(it.spec)) { + 3 + } else { + 6 + } + ) + } + splitInRows(sizedTiles, columns) + } + + TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) { + items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index -> + Tile( + stretchedTiles[index].tile, + iconTilesSpecs.contains(stretchedTiles[index].tile.spec), + Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) + ) + } + } + } + + @Composable + override fun EditTileGrid( + tiles: List<EditTileViewModel>, + modifier: Modifier, + onAddTile: (TileSpec, Int) -> Unit, + onRemoveTile: (TileSpec) -> Unit + ) { + val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() + val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() + + DefaultEditTileGrid( + tiles = tiles, + iconOnlySpecs = iconOnlySpecs, + columns = GridCells.Fixed(columns), + modifier = modifier, + onAddTile = onAddTile, + onRemoveTile = onRemoveTile, + ) + } + + private fun splitInRows( + tiles: List<SizedTile<TileViewModel>>, + columns: Int + ): List<SizedTile<TileViewModel>> { + val row = TileRow<TileViewModel>(columns) + + return buildList { + for (tile in tiles) { + if (row.maybeAddTile(tile)) { + if (row.isFull()) { + // Row is full, no need to stretch tiles + addAll(row.tiles) + row.clear() + } + } else { + if (row.isFull()) { + addAll(row.tiles) + } else { + // Stretching tiles when row isn't full + addAll(row.tiles.map { it.copy(width = it.width + (it.width / 3)) }) + } + row.clear() + row.maybeAddTile(tile) + } + } + addAll(row.tiles) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt new file mode 100644 index 000000000000..eb45110533a4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import android.graphics.drawable.Animatable +import android.text.TextUtils +import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Remove +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.Expandable +import com.android.compose.theme.colorAttr +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.common.ui.compose.load +import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes +import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes +import com.android.systemui.qs.panels.ui.viewmodel.TileUiState +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.toUiState +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.res.R +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.mapLatest + +object TileType + +@OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class) +@Composable +fun Tile( + tile: TileViewModel, + iconOnly: Boolean, + modifier: Modifier, +) { + val state: TileUiState by + tile.state + .mapLatest { it.toUiState() } + .collectAsStateWithLifecycle(tile.currentState.toUiState()) + val context = LocalContext.current + + Expandable( + color = colorAttr(state.colors.background), + shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)), + ) { + Row( + modifier = + modifier + .combinedClickable( + onClick = { tile.onClick(it) }, + onLongClick = { tile.onLongClick(it) } + ) + .tileModifier(state.colors), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = tileHorizontalArrangement(iconOnly), + ) { + val icon = + remember(state.icon) { + state.icon.get().let { + if (it is QSTileImpl.ResourceIcon) { + Icon.Resource(it.resId, null) + } else { + Icon.Loaded(it.getDrawable(context), null) + } + } + } + TileContent( + label = state.label.toString(), + secondaryLabel = state.secondaryLabel.toString(), + icon = icon, + colors = state.colors, + iconOnly = iconOnly + ) + } + } +} + +@Composable +fun TileLazyGrid( + modifier: Modifier = Modifier, + columns: GridCells, + content: LazyGridScope.() -> Unit, +) { + LazyVerticalGrid( + columns = columns, + verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), + horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), + modifier = modifier, + content = content, + ) +} + +@Composable +fun DefaultEditTileGrid( + tiles: List<EditTileViewModel>, + iconOnlySpecs: Set<TileSpec>, + columns: GridCells, + modifier: Modifier, + onAddTile: (TileSpec, Int) -> Unit, + onRemoveTile: (TileSpec) -> Unit, +) { + val (currentTiles, otherTiles) = tiles.partition { it.isCurrent } + val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null } + val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { + onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) + } + val isIconOnly: (TileSpec) -> Boolean = + remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } } + + TileLazyGrid(modifier = modifier, columns = columns) { + // These Text are just placeholders to see the different sections. Not final UI. + item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) } + + editTiles( + currentTiles, + ClickAction.REMOVE, + onRemoveTile, + isIconOnly, + indicatePosition = true, + ) + + item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) } + + editTiles( + otherTilesStock, + ClickAction.ADD, + addTileToEnd, + isIconOnly, + ) + + item(span = { GridItemSpan(maxLineSpan) }) { + Text("Custom tiles to add", color = Color.White) + } + + editTiles( + otherTilesCustom, + ClickAction.ADD, + addTileToEnd, + isIconOnly, + ) + } +} + +private fun LazyGridScope.editTiles( + tiles: List<EditTileViewModel>, + clickAction: ClickAction, + onClick: (TileSpec) -> Unit, + isIconOnly: (TileSpec) -> Boolean, + indicatePosition: Boolean = false, +) { + items( + count = tiles.size, + key = { tiles[it].tileSpec.spec }, + span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) }, + contentType = { TileType } + ) { + val viewModel = tiles[it] + val canClick = + when (clickAction) { + ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions + ClickAction.REMOVE -> AvailableEditActions.REMOVE in viewModel.availableEditActions + } + val onClickActionName = + when (clickAction) { + ClickAction.ADD -> + stringResource(id = R.string.accessibility_qs_edit_tile_add_action) + ClickAction.REMOVE -> + stringResource(id = R.string.accessibility_qs_edit_remove_tile_action) + } + val stateDescription = + if (indicatePosition) { + stringResource(id = R.string.accessibility_qs_edit_position, it + 1) + } else { + "" + } + + Box( + modifier = + Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) } + .animateItem() + .semantics { + onClick(onClickActionName) { false } + this.stateDescription = stateDescription + } + ) { + EditTile( + tileViewModel = viewModel, + isIconOnly(viewModel.tileSpec), + modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) + ) + if (canClick) { + Badge(clickAction, Modifier.align(Alignment.TopEnd)) + } + } + } +} + +@Composable +fun Badge(action: ClickAction, modifier: Modifier = Modifier) { + Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) { + Icon( + imageVector = + when (action) { + ClickAction.ADD -> Icons.Filled.Add + ClickAction.REMOVE -> Icons.Filled.Remove + }, + "", + tint = Color.Black, + ) + } +} + +@Composable +fun EditTile( + tileViewModel: EditTileViewModel, + iconOnly: Boolean, + modifier: Modifier = Modifier, +) { + val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec + val colors = ActiveTileColorAttributes + + Row( + modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = tileHorizontalArrangement(iconOnly) + ) { + TileContent( + label = label, + secondaryLabel = tileViewModel.appName?.load(), + colors = colors, + icon = tileViewModel.icon, + iconOnly = iconOnly, + animateIconToEnd = true, + ) + } +} + +enum class ClickAction { + ADD, + REMOVE, +} + +@OptIn(ExperimentalAnimationGraphicsApi::class) +@Composable +private fun TileIcon( + icon: Icon, + color: Color, + animateToEnd: Boolean = false, +) { + val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size)) + val context = LocalContext.current + val loadedDrawable = + remember(icon, context) { + when (icon) { + is Icon.Loaded -> icon.drawable + is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res) + } + } + if (loadedDrawable !is Animatable) { + Icon( + icon = icon, + tint = color, + modifier = modifier, + ) + } else if (icon is Icon.Resource) { + val image = AnimatedImageVector.animatedVectorResource(id = icon.res) + val painter = + if (animateToEnd) { + rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true) + } else { + var atEnd by remember(icon.res) { mutableStateOf(false) } + LaunchedEffect(key1 = icon.res) { + delay(350) + atEnd = true + } + rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) + } + Image( + painter = painter, + contentDescription = null, + colorFilter = ColorFilter.tint(color = color), + modifier = modifier + ) + } +} + +@Composable +private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier { + return fillMaxWidth() + .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))) + .background(colorAttr(colors.background)) + .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin)) +} + +@Composable +private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal { + val horizontalAlignment = + if (iconOnly) { + Alignment.CenterHorizontally + } else { + Alignment.Start + } + return spacedBy( + space = dimensionResource(id = R.dimen.qs_label_container_margin), + alignment = horizontalAlignment + ) +} + +@Composable +private fun TileContent( + label: String, + secondaryLabel: String?, + icon: Icon, + colors: TileColorAttributes, + iconOnly: Boolean, + animateIconToEnd: Boolean = false, +) { + TileIcon(icon, colorAttr(colors.icon), animateIconToEnd) + + if (!iconOnly) { + Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) { + Text( + label, + color = colorAttr(colors.label), + modifier = Modifier.basicMarquee(), + ) + if (!TextUtils.isEmpty(secondaryLabel)) { + Text( + secondaryLabel ?: "", + color = colorAttr(colors.secondaryLabel), + modifier = Modifier.basicMarquee(), + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 206879905782..71b69c92b87d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles; +import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_AIRPLANE_MODE; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -32,9 +34,12 @@ import android.telephony.TelephonyManager; import android.widget.Switch; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.telephony.flags.Flags; +import com.android.settingslib.satellite.SatelliteDialogUtils; import com.android.systemui.animation.Expandable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; @@ -54,6 +59,8 @@ import com.android.systemui.util.settings.GlobalSettings; import dagger.Lazy; +import kotlinx.coroutines.Job; + import javax.inject.Inject; /** Quick settings tile: Airplane mode **/ @@ -66,6 +73,9 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { private final Lazy<ConnectivityManager> mLazyConnectivityManager; private boolean mListening; + @Nullable + @VisibleForTesting + Job mClickJob; @Inject public AirplaneModeTile( @@ -111,6 +121,21 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0); return; } + + if (Flags.oemEnabledSatelliteFlag()) { + if (mClickJob != null && !mClickJob.isCompleted()) { + return; + } + mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog( + mContext, this, TYPE_IS_AIRPLANE_MODE, isAllowClick -> { + if (isAllowClick) { + setEnabled(!airplaneModeEnabled); + } + return null; + }); + return; + } + setEnabled(!airplaneModeEnabled); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 9af34f6c9918..9f41d98b6969 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_BLUETOOTH; import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; import android.annotation.Nullable; @@ -33,11 +34,14 @@ import android.text.TextUtils; import android.util.Log; import android.widget.Switch; +import androidx.annotation.VisibleForTesting; + import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.satellite.SatelliteDialogUtils; import com.android.systemui.animation.Expandable; import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel; import com.android.systemui.dagger.qualifiers.Background; @@ -55,6 +59,8 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BluetoothController; +import kotlinx.coroutines.Job; + import java.util.List; import java.util.concurrent.Executor; @@ -78,6 +84,9 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { private final BluetoothTileDialogViewModel mDialogViewModel; private final FeatureFlags mFeatureFlags; + @Nullable + @VisibleForTesting + Job mClickJob; @Inject public BluetoothTile( @@ -110,6 +119,24 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { @Override protected void handleClick(@Nullable Expandable expandable) { + if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) { + if (mClickJob != null && !mClickJob.isCompleted()) { + return; + } + mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog( + mContext, this, TYPE_IS_BLUETOOTH, isAllowClick -> { + if (!isAllowClick) { + return null; + } + handleClickEvent(expandable); + return null; + }); + return; + } + handleClickEvent(expandable); + } + + private void handleClickEvent(@Nullable Expandable expandable) { if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) { mDialogViewModel.showDialog(expandable); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt index 2d3120a1dcce..972b20e138d9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt @@ -29,11 +29,15 @@ import javax.inject.Inject /** * Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard - * dismissing and tile from-view animations. + * dismissing and tile from-view animations, as well as the option to show over lockscreen. */ interface QSTileIntentUserInputHandler { - fun handle(expandable: Expandable?, intent: Intent) + fun handle( + expandable: Expandable?, + intent: Intent, + dismissShadeShowOverLockScreenWhenLocked: Boolean = false + ) /** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */ fun handle( @@ -52,12 +56,25 @@ constructor( private val userHandle: UserHandle, ) : QSTileIntentUserInputHandler { - override fun handle(expandable: Expandable?, intent: Intent) { + override fun handle( + expandable: Expandable?, + intent: Intent, + dismissShadeShowOverLockScreenWhenLocked: Boolean + ) { val animationController: ActivityTransitionAnimator.Controller? = expandable?.activityTransitionController( InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE ) - activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController) + if (dismissShadeShowOverLockScreenWhenLocked) { + activityStarter.startActivity( + intent, + true /* dismissShade */, + animationController, + true /* showOverLockscreenWhenLocked */ + ) + } else { + activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController) + } } // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java index c9c44434361d..c971f547c302 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java @@ -15,6 +15,7 @@ */ package com.android.systemui.qs.tiles.dialog; +import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI; import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT; @@ -57,6 +58,8 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.internal.telephony.flags.Flags; +import com.android.settingslib.satellite.SatelliteDialogUtils; import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.Prefs; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; @@ -73,6 +76,7 @@ import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.Job; import java.util.List; import java.util.concurrent.Executor; @@ -161,6 +165,9 @@ public class InternetDialogDelegate implements // Wi-Fi scanning progress bar protected boolean mIsProgressBarVisible; private SystemUIDialog mDialog; + private final CoroutineScope mCoroutineScope; + @Nullable + private Job mClickJob; @AssistedFactory public interface Factory { @@ -203,7 +210,7 @@ public class InternetDialogDelegate implements mCanConfigWifi = canConfigWifi; mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context); mKeyguard = keyguardStateController; - + mCoroutineScope = coroutineScope; mUiEventLogger = uiEventLogger; mDialogTransitionAnimator = dialogTransitionAnimator; mAdapter = new InternetAdapter(mInternetDialogController, coroutineScope); @@ -388,11 +395,9 @@ public class InternetDialogDelegate implements }); mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi); mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton); - mWiFiToggle.setOnCheckedChangeListener( - (buttonView, isChecked) -> { - if (mInternetDialogController.isWifiEnabled() == isChecked) return; - mInternetDialogController.setWifiEnabled(isChecked); - }); + mWiFiToggle.setOnClickListener(v -> { + handleWifiToggleClicked(mWiFiToggle.isChecked()); + }); mDoneButton.setOnClickListener(v -> dialog.dismiss()); mShareWifiButton.setOnClickListener(v -> { if (mInternetDialogController.mayLaunchShareWifiSettings(mConnectedWifiEntry, v)) { @@ -404,6 +409,32 @@ public class InternetDialogDelegate implements }); } + private void handleWifiToggleClicked(boolean isChecked) { + if (Flags.oemEnabledSatelliteFlag()) { + if (mClickJob != null && !mClickJob.isCompleted()) { + return; + } + mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog( + mDialog.getContext(), mCoroutineScope, TYPE_IS_WIFI, isAllowClick -> { + if (isAllowClick) { + setWifiEnable(isChecked); + } else { + mWiFiToggle.setChecked(!isChecked); + } + return null; + }); + return; + } + setWifiEnable(isChecked); + } + + private void setWifiEnable(boolean isChecked) { + if (mInternetDialogController.isWifiEnabled() == isChecked) { + return; + } + mInternetDialogController.setWifiEnabled(isChecked); + } + @MainThread private void updateEthernet() { mEthernetLayout.setVisibility( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt new file mode 100644 index 000000000000..1e8ce588b4e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.domain.interactor + +import android.os.UserHandle +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn + +/** Observes one qr scanner state changes providing the [QRCodeScannerTileModel]. */ +class QRCodeScannerTileDataInteractor +@Inject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, + private val qrController: QRCodeScannerController, +) : QSTileDataInteractor<QRCodeScannerTileModel> { + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<QRCodeScannerTileModel> = + conflatedCallbackFlow { + qrController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE) + val callback = + object : QRCodeScannerController.Callback { + override fun onQRCodeScannerActivityChanged() { + trySend(generateModel()) + } + } + qrController.addCallback(callback) + awaitClose { + qrController.removeCallback(callback) + qrController.unregisterQRCodeScannerChangeObservers( + DEFAULT_QR_CODE_SCANNER_CHANGE + ) + } + } + .onStart { emit(generateModel()) } + .flowOn(bgCoroutineContext) + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + QRCodeScannerTileModel.TemporarilyUnavailable + ) + + override fun availability(user: UserHandle): Flow<Boolean> = + flowOf(qrController.isCameraAvailable) + + private fun generateModel(): QRCodeScannerTileModel { + val intent = qrController.intent + + return if (qrController.isAbleToLaunchScannerActivity && intent != null) + QRCodeScannerTileModel.Available(intent) + else QRCodeScannerTileModel.TemporarilyUnavailable + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt new file mode 100644 index 000000000000..7c0c41eca4bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.domain.interactor + +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +/** Handles qr tile clicks. */ +class QRCodeScannerTileUserActionInteractor +@Inject +constructor( + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, +) : QSTileUserActionInteractor<QRCodeScannerTileModel> { + + override suspend fun handleInput(input: QSTileInput<QRCodeScannerTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + when (data) { + is QRCodeScannerTileModel.Available -> + qsTileIntentUserActionHandler.handle( + action.expandable, + data.intent, + true + ) + is QRCodeScannerTileModel.TemporarilyUnavailable -> {} // no-op + } + } + is QSTileUserAction.LongClick -> {} // no-op + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt new file mode 100644 index 000000000000..22c9b66b2806 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.domain.model + +import android.content.Intent + +/** qr scanner tile model. */ +sealed interface QRCodeScannerTileModel { + data class Available(val intent: Intent) : QRCodeScannerTileModel + data object TemporarilyUnavailable : QRCodeScannerTileModel +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt new file mode 100644 index 000000000000..45a77179fb3f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.ui + +import android.content.res.Resources +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [QRCodeScannerTileModel] to [QSTileState]. */ +class QRCodeScannerTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, +) : QSTileDataToStateMapper<QRCodeScannerTileModel> { + + override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + label = resources.getString(R.string.qr_code_scanner_title) + contentDescription = label + icon = { + Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null) + } + sideViewIcon = QSTileState.SideViewIcon.Chevron + supportedActions = setOf(QSTileState.UserAction.CLICK) + + when (data) { + is QRCodeScannerTileModel.Available -> { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = null + } + is QRCodeScannerTileModel.TemporarilyUnavailable -> { + activationState = QSTileState.ActivationState.UNAVAILABLE + secondaryLabel = + resources.getString(R.string.qr_code_scanner_updating_secondary_label) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index e4cb211a430f..0327ec760ace 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -115,7 +115,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder; -import com.android.wm.shell.desktopmode.DesktopModeStatus; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInterface; import dagger.Lazy; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt index caa67dff086f..1868b4a29f20 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt @@ -25,7 +25,6 @@ import android.content.Intent import android.os.UserHandle import android.util.Log import android.util.Pair -import android.view.View import android.view.Window import com.android.app.tracing.coroutines.launch import com.android.internal.app.ChooserActivity @@ -41,8 +40,8 @@ constructor( private val intentExecutor: ActionIntentExecutor, @Application private val applicationScope: CoroutineScope, @Assisted val window: Window, - @Assisted val transitionView: View, - @Assisted val onDismiss: (() -> Unit) + @Assisted val viewProxy: ScreenshotViewProxy, + @Assisted val finishDismiss: () -> Unit, ) { var isPendingSharedTransition = false @@ -50,6 +49,7 @@ constructor( fun startSharedTransition(intent: Intent, user: UserHandle, overrideTransition: Boolean) { isPendingSharedTransition = true + viewProxy.fadeForSharedTransition() val windowTransition = createWindowTransition() applicationScope.launch("$TAG#launchIntentAsync") { intentExecutor.launchIntent( @@ -70,7 +70,7 @@ constructor( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED ) pendingIntent.send(options.toBundle()) - onDismiss.invoke() + viewProxy.requestDismissal(null) } catch (e: PendingIntent.CanceledException) { Log.e(TAG, "Intent cancelled", e) } @@ -89,7 +89,7 @@ constructor( override fun hideSharedElements() { isPendingSharedTransition = false - onDismiss.invoke() + finishDismiss.invoke() } override fun onFinish() {} @@ -98,13 +98,20 @@ constructor( window, callbacks, null, - Pair.create(transitionView, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME) + Pair.create( + viewProxy.screenshotPreview, + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME + ) ) } @AssistedFactory interface Factory { - fun create(window: Window, transitionView: View, onDismiss: (() -> Unit)): ActionExecutor + fun create( + window: Window, + viewProxy: ScreenshotViewProxy, + finishDismiss: (() -> Unit) + ): ActionExecutor } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt index a0cef529ecde..15638d3496e9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt @@ -97,6 +97,7 @@ object ActionIntentCreator { .putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, owner) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) } private const val EXTRA_EDIT_SOURCE = "edit_source" diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt index 4cf18fb482d8..3d024a6a8ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt @@ -157,6 +157,8 @@ constructor( override fun restoreNonScrollingUi() = view.restoreNonScrollingUi() + override fun fadeForSharedTransition() {} // unused + override fun stopInputListening() = view.stopInputListening() override fun requestFocus() { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 2f026aee64d8..9ad6d0faea88 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -317,9 +317,9 @@ public class ScreenshotController { mConfigChanges.applyNewConfig(context.getResources()); reloadAssets(); - mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy.getScreenshotPreview(), + mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy, () -> { - requestDismissal(null); + finishDismiss(); return Unit.INSTANCE; }); @@ -623,9 +623,11 @@ public class ScreenshotController { (response) -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, response.getPackageName()); - if (screenshotShelfUi2() && mActionsProvider != null) { - mActionsProvider.onScrollChipReady( - () -> onScrollButtonClicked(owner, response)); + if (screenshotShelfUi2()) { + if (mActionsProvider != null) { + mActionsProvider.onScrollChipReady( + () -> onScrollButtonClicked(owner, response)); + } } else { mViewProxy.showScrollChip(response.getPackageName(), () -> onScrollButtonClicked(owner, response)); @@ -657,9 +659,7 @@ public class ScreenshotController { () -> { final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent( owner, mContext); - mActionIntentExecutor.launchIntentAsync(intent, owner, true, - ActivityOptions.makeCustomAnimation(mContext, 0, 0), null); - + mActionIntentExecutor.launchIntentAsync(intent, owner, true, null, null); }, mViewProxy::restoreNonScrollingUi, mViewProxy::startLongScreenshotTransition); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt index 846884fe4cf9..3ac070a28b2b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt @@ -90,15 +90,15 @@ constructor( override var isDismissing = false override var isPendingSharedTransition = false - private val animationController = ScreenshotAnimationController(view) + private val animationController = ScreenshotAnimationController(view, viewModel) init { shelfViewBinder.bind( view, viewModel, + animationController, LayoutInflater.from(context), onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) }, - onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() }, onUserInteraction = { callbacks?.onUserInteraction() } ) view.updateInsets(windowManager.currentWindowMetrics.windowInsets) @@ -188,24 +188,53 @@ constructor( override fun prepareScrollingTransition( response: ScrollCaptureResponse, - screenBitmap: Bitmap, + screenBitmap: Bitmap, // unused newScreenshot: Bitmap, screenshotTakenInPortrait: Boolean, onTransitionPrepared: Runnable, ) { - onTransitionPrepared.run() + viewModel.setScrollingScrimBitmap(newScreenshot) + viewModel.setScrollableRect(scrollableAreaOnScreen(response)) + animationController.fadeForLongScreenshotTransition() + view.post { onTransitionPrepared.run() } + } + + private fun scrollableAreaOnScreen(response: ScrollCaptureResponse): Rect { + val r = Rect(response.boundsInWindow) + val windowInScreen = response.windowBounds + r.offset(windowInScreen?.left ?: 0, windowInScreen?.top ?: 0) + r.intersect( + Rect( + 0, + 0, + context.resources.displayMetrics.widthPixels, + context.resources.displayMetrics.heightPixels + ) + ) + return r } override fun startLongScreenshotTransition( transitionDestination: Rect, onTransitionEnd: Runnable, - longScreenshot: ScrollCaptureController.LongScreenshot + longScreenshot: ScrollCaptureController.LongScreenshot, ) { - onTransitionEnd.run() - callbacks?.onDismiss() + val transitionAnimation = + animationController.runLongScreenshotTransition( + transitionDestination, + longScreenshot, + onTransitionEnd + ) + transitionAnimation.doOnEnd { callbacks?.onDismiss() } + transitionAnimation.start() } - override fun restoreNonScrollingUi() {} + override fun restoreNonScrollingUi() { + viewModel.setScrollableRect(null) + viewModel.setScrollingScrimBitmap(null) + animationController.restoreUI() + callbacks?.onUserInteraction() // reset the timeout + } override fun stopInputListening() {} @@ -228,6 +257,10 @@ constructor( ) } + override fun fadeForSharedTransition() { + animationController.fadeForSharedTransition() + } + private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) { val onBackInvokedCallback = OnBackInvokedCallback { debugLog(DEBUG_INPUT) { "Predictive Back callback dispatched" } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt index a4069d11f8fb..df93a5e56c22 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt @@ -63,6 +63,7 @@ interface ScreenshotViewProxy { longScreenshot: ScrollCaptureController.LongScreenshot ) fun restoreNonScrollingUi() + fun fadeForSharedTransition() fun stopInputListening() fun requestFocus() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java index 5e561cfb14a1..ee1944e0b5cf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java @@ -45,6 +45,7 @@ import androidx.customview.widget.ExploreByTouchHelper; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import com.android.internal.graphics.ColorUtils; +import com.android.systemui.Flags; import com.android.systemui.res.R; import java.util.List; @@ -378,8 +379,14 @@ public class CropView extends View { upper = 1; break; } - Log.i(TAG, "getAllowedValues: " + boundary + ", " - + "result=[lower=" + lower + ", upper=" + upper + "]"); + if (lower >= upper) { + Log.wtf(TAG, "getAllowedValues computed an invalid range " + + "[" + lower + ", " + upper + "]"); + if (Flags.screenshotScrollCropViewCrashFix()) { + lower = Math.min(lower, upper); + upper = lower; + } + } return new Range<>(lower, upper); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt index 06e88f46c5f1..a4906c12b487 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt @@ -20,6 +20,10 @@ import android.animation.Animator import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.animation.ValueAnimator +import android.content.res.ColorStateList +import android.graphics.BlendMode +import android.graphics.Color +import android.graphics.Matrix import android.graphics.PointF import android.graphics.Rect import android.util.MathUtils @@ -29,13 +33,21 @@ import android.widget.ImageView import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.android.systemui.res.R +import com.android.systemui.screenshot.scroll.ScrollCaptureController +import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import kotlin.math.abs import kotlin.math.max import kotlin.math.sign -class ScreenshotAnimationController(private val view: ScreenshotShelfView) { +class ScreenshotAnimationController( + private val view: ScreenshotShelfView, + private val viewModel: ScreenshotViewModel +) { private var animator: Animator? = null private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview) + private val scrollingScrim = view.requireViewById<ImageView>((R.id.screenshot_scrolling_scrim)) + private val scrollTransitionPreview = + view.requireViewById<ImageView>(R.id.screenshot_scrollable_preview) private val flashView = view.requireViewById<View>(R.id.screenshot_flash) private val actionContainer = view.requireViewById<View>(R.id.actions_container_background) private val fastOutSlowIn = @@ -46,6 +58,14 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { view.requireViewById(R.id.screenshot_badge), view.requireViewById(R.id.screenshot_dismiss_button) ) + private val fadeUI = + listOf<View>( + view.requireViewById(R.id.screenshot_preview_border), + view.requireViewById(R.id.actions_container_background), + view.requireViewById(R.id.screenshot_badge), + view.requireViewById(R.id.screenshot_dismiss_button), + view.requireViewById(R.id.screenshot_message_container), + ) fun getEntranceAnimation( bounds: Rect, @@ -96,15 +116,108 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { } entranceAnimation.play(fadeInAnimator).after(previewAnimator) entranceAnimation.doOnStart { + viewModel.setIsAnimating(true) for (child in staticUI) { child.alpha = 0f } } + entranceAnimation.doOnEnd { viewModel.setIsAnimating(false) } this.animator = entranceAnimation return entranceAnimation } + fun fadeForSharedTransition() { + animator?.cancel() + val fadeAnimator = ValueAnimator.ofFloat(1f, 0f) + fadeAnimator.addUpdateListener { + for (view in fadeUI) { + view.alpha = it.animatedValue as Float + } + } + animator = fadeAnimator + fadeAnimator.start() + } + + fun runLongScreenshotTransition( + destRect: Rect, + longScreenshot: ScrollCaptureController.LongScreenshot, + onTransitionEnd: Runnable + ): Animator { + val animSet = AnimatorSet() + + val scrimAnim = ValueAnimator.ofFloat(0f, 1f) + scrimAnim.addUpdateListener { animation: ValueAnimator -> + scrollingScrim.setAlpha(1 - animation.animatedFraction) + } + scrollTransitionPreview.visibility = View.VISIBLE + if (true) { + scrollTransitionPreview.setImageBitmap(longScreenshot.toBitmap()) + val startX: Float = scrollTransitionPreview.x + val startY: Float = scrollTransitionPreview.y + val locInScreen: IntArray = scrollTransitionPreview.getLocationOnScreen() + destRect.offset(startX.toInt() - locInScreen[0], startY.toInt() - locInScreen[1]) + scrollTransitionPreview.pivotX = 0f + scrollTransitionPreview.pivotY = 0f + scrollTransitionPreview.setAlpha(1f) + val currentScale: Float = scrollTransitionPreview.width / longScreenshot.width.toFloat() + val matrix = Matrix() + matrix.setScale(currentScale, currentScale) + matrix.postTranslate( + longScreenshot.left * currentScale, + longScreenshot.top * currentScale + ) + scrollTransitionPreview.setImageMatrix(matrix) + val destinationScale: Float = destRect.width() / scrollTransitionPreview.width.toFloat() + val previewAnim = ValueAnimator.ofFloat(0f, 1f) + previewAnim.addUpdateListener { animation: ValueAnimator -> + val t = animation.animatedFraction + val currScale = MathUtils.lerp(1f, destinationScale, t) + scrollTransitionPreview.scaleX = currScale + scrollTransitionPreview.scaleY = currScale + scrollTransitionPreview.x = MathUtils.lerp(startX, destRect.left.toFloat(), t) + scrollTransitionPreview.y = MathUtils.lerp(startY, destRect.top.toFloat(), t) + } + val previewFadeAnim = ValueAnimator.ofFloat(1f, 0f) + previewFadeAnim.addUpdateListener { animation: ValueAnimator -> + scrollTransitionPreview.setAlpha(1 - animation.animatedFraction) + } + previewAnim.doOnEnd { onTransitionEnd.run() } + animSet.play(previewAnim).with(scrimAnim).before(previewFadeAnim) + } else { + // if we switched orientations between the original screenshot and the long screenshot + // capture, just fade out the scrim instead of running the preview animation + scrimAnim.doOnEnd { onTransitionEnd.run() } + animSet.play(scrimAnim) + } + animator = animSet + return animSet + } + + fun fadeForLongScreenshotTransition() { + scrollingScrim.imageTintBlendMode = BlendMode.SRC_ATOP + val anim = ValueAnimator.ofFloat(0f, .3f) + anim.addUpdateListener { + scrollingScrim.setImageTintList( + ColorStateList.valueOf(Color.argb(it.animatedValue as Float, 0f, 0f, 0f)) + ) + } + for (view in fadeUI) { + view.alpha = 0f + } + screenshotPreview.alpha = 0f + anim.setDuration(200) + anim.start() + } + + fun restoreUI() { + animator?.cancel() + for (view in fadeUI) { + view.alpha = 1f + } + screenshotPreview.alpha = 1f + } + fun getSwipeReturnAnimation(): Animator { animator?.cancel() val animator = ValueAnimator.ofFloat(view.translationX, 0f) @@ -114,6 +227,7 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { } fun getSwipeDismissAnimation(requestedVelocity: Float?): Animator { + animator?.cancel() val velocity = getAdjustedVelocity(requestedVelocity) val screenWidth = view.resources.displayMetrics.widthPixels // translation at which point the visible UI is fully off the screen (in the direction @@ -131,6 +245,8 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { view.alpha = 1f - it.animatedFraction } animator.duration = ((abs(distance / velocity))).toLong() + animator.doOnStart { viewModel.setIsAnimating(true) } + animator.doOnEnd { viewModel.setIsAnimating(false) } this.animator = animator return animator diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt index c7bc50cb3802..442b3873be4d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt @@ -16,7 +16,11 @@ package com.android.systemui.screenshot.ui.binder +import android.content.res.Configuration import android.graphics.Bitmap +import android.graphics.Matrix +import android.graphics.Rect +import android.util.LayoutDirection import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -29,6 +33,7 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.screenshot.ScreenshotEvent +import com.android.systemui.screenshot.ui.ScreenshotAnimationController import com.android.systemui.screenshot.ui.ScreenshotShelfView import com.android.systemui.screenshot.ui.SwipeGestureListener import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel @@ -45,9 +50,9 @@ constructor(private val buttonViewBinder: ActionButtonViewBinder) { fun bind( view: ScreenshotShelfView, viewModel: ScreenshotViewModel, + animationController: ScreenshotAnimationController, layoutInflater: LayoutInflater, onDismissalRequested: (event: ScreenshotEvent, velocity: Float?) -> Unit, - onDismissalCancelled: () -> Unit, onUserInteraction: () -> Unit ) { val swipeGestureListener = @@ -56,7 +61,7 @@ constructor(private val buttonViewBinder: ActionButtonViewBinder) { onDismiss = { onDismissalRequested(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, it) }, - onCancel = onDismissalCancelled + onCancel = { animationController.getSwipeReturnAnimation().start() } ) view.onTouchInterceptListener = { swipeGestureListener.onMotionEvent(it) } view.userInteractionCallback = onUserInteraction @@ -66,11 +71,14 @@ constructor(private val buttonViewBinder: ActionButtonViewBinder) { val previewBorder = view.requireViewById<View>(R.id.screenshot_preview_border) previewView.clipToOutline = true previewViewBlur.clipToOutline = true + val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions) val dismissButton = view.requireViewById<View>(R.id.screenshot_dismiss_button) dismissButton.visibility = if (viewModel.showDismissButton) View.VISIBLE else View.GONE dismissButton.setOnClickListener { onDismissalRequested(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, null) } + val scrollingScrim: ImageView = view.requireViewById(R.id.screenshot_scrolling_scrim) + val scrollablePreview: ImageView = view.requireViewById(R.id.screenshot_scrollable_preview) val badgeView = view.requireViewById<ImageView>(R.id.screenshot_badge) // use immediate dispatcher to ensure screenshot bitmap is set before animation @@ -91,6 +99,29 @@ constructor(private val buttonViewBinder: ActionButtonViewBinder) { } } launch { + viewModel.scrollingScrim.collect { bitmap -> + if (bitmap != null) { + scrollingScrim.setImageBitmap(bitmap) + scrollingScrim.visibility = View.VISIBLE + } else { + scrollingScrim.visibility = View.GONE + } + } + } + launch { + viewModel.scrollableRect.collect { rect -> + if (rect != null) { + setScrollablePreview( + scrollablePreview, + viewModel.preview.value, + rect + ) + } else { + scrollablePreview.visibility = View.GONE + } + } + } + launch { viewModel.badge.collect { badge -> badgeView.setImageDrawable(badge) badgeView.visibility = if (badge != null) View.VISIBLE else View.GONE @@ -102,6 +133,14 @@ constructor(private val buttonViewBinder: ActionButtonViewBinder) { } } launch { + viewModel.isAnimating.collect { isAnimating -> + previewView.isClickable = !isAnimating + for (child in actionsContainer.children) { + child.isClickable = !isAnimating + } + } + } + launch { viewModel.actions.collect { actions -> updateActions( actions, @@ -191,4 +230,35 @@ constructor(private val buttonViewBinder: ActionButtonViewBinder) { screenshotPreview.layoutParams = params screenshotPreview.requestLayout() } + + private fun setScrollablePreview( + scrollablePreview: ImageView, + bitmap: Bitmap?, + scrollableRect: Rect + ) { + if (bitmap == null) { + return + } + val fixedSize = scrollablePreview.resources.getDimensionPixelSize(R.dimen.overlay_x_scale) + val inPortrait = + scrollablePreview.resources.configuration.orientation == + Configuration.ORIENTATION_PORTRAIT + val scale: Float = fixedSize / ((if (inPortrait) bitmap.width else bitmap.height).toFloat()) + val params = scrollablePreview.layoutParams + + params.width = (scale * scrollableRect.width()).toInt() + params.height = (scale * scrollableRect.height()).toInt() + val matrix = Matrix() + matrix.setScale(scale, scale) + matrix.postTranslate(-scrollableRect.left * scale, -scrollableRect.top * scale) + + scrollablePreview.translationX = + (scale * + if (scrollablePreview.layoutDirection == LayoutDirection.LTR) scrollableRect.left + else scrollableRect.right - (scrollablePreview.parent as View).width) + scrollablePreview.translationY = scale * scrollableRect.top + scrollablePreview.setImageMatrix(matrix) + scrollablePreview.setImageBitmap(bitmap) + scrollablePreview.setVisibility(View.VISIBLE) + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt index 81bc281191ed..3f99bc4597cb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.screenshot.ui.viewmodel import android.graphics.Bitmap +import android.graphics.Rect import android.graphics.drawable.Drawable import android.util.Log import android.view.accessibility.AccessibilityManager @@ -26,6 +27,8 @@ import kotlinx.coroutines.flow.StateFlow class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager) { private val _preview = MutableStateFlow<Bitmap?>(null) val preview: StateFlow<Bitmap?> = _preview + private val _scrollingScrim = MutableStateFlow<Bitmap?>(null) + val scrollingScrim: StateFlow<Bitmap?> = _scrollingScrim private val _badge = MutableStateFlow<Drawable?>(null) val badge: StateFlow<Drawable?> = _badge private val _previewAction = MutableStateFlow<(() -> Unit)?>(null) @@ -35,6 +38,10 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED) val animationState: StateFlow<AnimationState> = _animationState + private val _isAnimating = MutableStateFlow(false) + val isAnimating: StateFlow<Boolean> = _isAnimating + private val _scrollableRect = MutableStateFlow<Rect?>(null) + val scrollableRect: StateFlow<Rect?> = _scrollableRect val showDismissButton: Boolean get() = accessibilityManager.isEnabled @@ -42,6 +49,10 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager _preview.value = bitmap } + fun setScrollingScrimBitmap(bitmap: Bitmap?) { + _scrollingScrim.value = bitmap + } + fun setScreenshotBadge(badge: Drawable?) { _badge.value = badge } @@ -114,12 +125,23 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager _animationState.value = state } + fun setIsAnimating(isAnimating: Boolean) { + _isAnimating.value = isAnimating + } + + fun setScrollableRect(rect: Rect?) { + _scrollableRect.value = rect + } + fun reset() { _preview.value = null + _scrollingScrim.value = null _badge.value = null _previewAction.value = null _actions.value = listOf() _animationState.value = AnimationState.NOT_STARTED + _isAnimating.value = false + _scrollableRect.value = null } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java index 288ff09602f4..84156eeb9264 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java @@ -51,6 +51,16 @@ public class ToggleSeekBar extends SeekBar { return super.onTouchEvent(event); } + @Override + public boolean onHoverEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { + setHovered(true); + } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { + setHovered(false); + } + return true; + } + public void setAccessibilityLabel(String label) { mAccessibilityLabel = label; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index ee7b4beec64e..22aa492dbfe8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -123,15 +123,9 @@ constructor( private var anyBouncerShowing = false /** - * True if the shade is fully expanded and the user is not interacting with it anymore, meaning - * the hub should not receive any touch input. + * True if the shade is fully expanded, meaning the hub should not receive any touch input. * - * We need to not pause the touch handling lifecycle as soon as the shade opens because if the - * user swipes down, then back up without lifting their finger, the lifecycle will be paused - * then resumed, and resuming force-stops all active touch sessions. This means the shade will - * not receive the end of the gesture and will be stuck open. - * - * Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting]. + * Tracks [ShadeInteractor.isAnyFullyExpanded]. */ private var shadeShowing = false diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7051d5fda159..6bb30c7b97f4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -141,6 +141,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver; import com.android.systemui.keyguard.shared.ComposeLockscreen; +import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; @@ -170,6 +171,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.WakefulnessModel; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.shade.data.repository.FlingInfo; import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; @@ -1130,8 +1132,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump controller.setup(mNotificationContainerParent)); // Dreaming->Lockscreen - collectFlow(mView, mKeyguardTransitionInteractor.transition(DREAMING, LOCKSCREEN), - mDreamingToLockscreenTransition, mMainDispatcher); + collectFlow( + mView, + mKeyguardTransitionInteractor.transition( + Edge.Companion.create(DREAMING, LOCKSCREEN)), + mDreamingToLockscreenTransition, + mMainDispatcher); collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(), setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); @@ -1141,7 +1147,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Gone -> Dreaming hosted in lockscreen collectFlow(mView, mKeyguardTransitionInteractor - .transition(GONE, DREAMING_LOCKSCREEN_HOSTED), + .transition(Edge.Companion.create(Scenes.Gone, DREAMING_LOCKSCREEN_HOSTED), + Edge.Companion.create(GONE, DREAMING_LOCKSCREEN_HOSTED)), mGoneToDreamingLockscreenHostedTransition, mMainDispatcher); collectFlow(mView, mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha(), setTransitionAlpha(mNotificationStackScrollLayoutController), @@ -1149,16 +1156,17 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Lockscreen -> Dreaming hosted in lockscreen collectFlow(mView, mKeyguardTransitionInteractor - .transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED), + .transition(Edge.Companion.create(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)), mLockscreenToDreamingLockscreenHostedTransition, mMainDispatcher); // Dreaming hosted in lockscreen -> Lockscreen collectFlow(mView, mKeyguardTransitionInteractor - .transition(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN), + .transition(Edge.Companion.create(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN)), mDreamingLockscreenHostedToLockscreenTransition, mMainDispatcher); // Occluded->Lockscreen - collectFlow(mView, mKeyguardTransitionInteractor.transition(OCCLUDED, LOCKSCREEN), + collectFlow(mView, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(OCCLUDED, LOCKSCREEN)), mOccludedToLockscreenTransition, mMainDispatcher); if (!MigrateClocksToBlueprint.isEnabled()) { collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(), @@ -1169,7 +1177,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } // Lockscreen->Dreaming - collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING), + collectFlow(mView, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(LOCKSCREEN, DREAMING)), mLockscreenToDreamingTransition, mMainDispatcher); if (!MigrateClocksToBlueprint.isEnabled()) { collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(), @@ -1181,7 +1190,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); // Gone->Dreaming - collectFlow(mView, mKeyguardTransitionInteractor.transition(GONE, DREAMING), + collectFlow(mView, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(Scenes.Gone, DREAMING), + Edge.Companion.create(GONE, DREAMING)), mGoneToDreamingTransition, mMainDispatcher); if (!MigrateClocksToBlueprint.isEnabled()) { collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(), @@ -1192,7 +1203,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); // Lockscreen->Occluded - collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, OCCLUDED), + collectFlow(mView, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(LOCKSCREEN, OCCLUDED)), mLockscreenToOccludedTransition, mMainDispatcher); if (!MigrateClocksToBlueprint.isEnabled()) { collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(), diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index b50a3cd442d3..6efa6334b968 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -49,6 +49,7 @@ import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.res.R; @@ -137,11 +138,6 @@ public class NotificationShadeWindowViewController implements Dumpable { private final PanelExpansionInteractor mPanelExpansionInteractor; private final ShadeExpansionStateManager mShadeExpansionStateManager; - /** - * If {@code true}, an external touch sent in {@link #handleExternalTouch(MotionEvent)} has been - * intercepted and all future touch events for the gesture should be processed by this view. - */ - private boolean mExternalTouchIntercepted = false; private boolean mIsTrackingBarGesture = false; private boolean mIsOcclusionTransitionRunning = false; private DisableSubpixelTextTransitionListener mDisableSubpixelTextTransitionListener; @@ -225,7 +221,8 @@ public class NotificationShadeWindowViewController implements Dumpable { mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView); bouncerViewBinder.bind(mView.findViewById(R.id.keyguard_bouncer_container)); - collectFlow(mView, keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING), + collectFlow(mView, keyguardTransitionInteractor.transition( + Edge.Companion.create(LOCKSCREEN, DREAMING)), mLockscreenToDreamingTransition); collectFlow( mView, @@ -258,28 +255,11 @@ public class NotificationShadeWindowViewController implements Dumpable { } /** - * Handle a touch event while dreaming or on the hub by forwarding the event to the content - * view. - * <p> - * Since important logic for handling touches lives in the dispatch/intercept phases, we - * simulate going through all of these stages before sending onTouchEvent if intercepted. - * + * Handle a touch event while dreaming by forwarding the event to the content view. * @param event The event to forward. */ - public void handleExternalTouch(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mExternalTouchIntercepted = false; - } - - if (!mView.dispatchTouchEvent(event)) { - return; - } - if (!mExternalTouchIntercepted) { - mExternalTouchIntercepted = mView.onInterceptTouchEvent(event); - } - if (mExternalTouchIntercepted) { - mView.onTouchEvent(event); - } + public void handleDreamTouch(MotionEvent event) { + mView.dispatchTouchEvent(event); } /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt index ce88a5f5e55f..cae86a652245 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone.ongoingcall +package com.android.systemui.statusbar.chips.ui.view import android.content.Context import android.util.AttributeSet import com.android.systemui.animation.view.LaunchableLinearLayout /** - * A container view for the ongoing call chip background. Needed so that we can limit the height of - * the background when the font size is very large (200%), in which case the background would go + * A container view for the ongoing activity chip background. Needed so that we can limit the height + * of the background when the font size is very large (200%), in which case the background would go * past the bounds of the status bar. */ -class OngoingCallBackgroundContainer(context: Context, attrs: AttributeSet) : +class ChipBackgroundContainer(context: Context, attrs: AttributeSet) : LaunchableLinearLayout(context, attrs) { /** Sets where this view should fetch its max height from. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt index bb7ba4c4174f..ff3061e850d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt @@ -14,36 +14,34 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone.ongoingcall +package com.android.systemui.statusbar.chips.ui.view import android.content.Context import android.util.AttributeSet - import android.widget.Chronometer import androidx.annotation.UiThread /** - * A [Chronometer] specifically for the ongoing call chip in the status bar. + * A [Chronometer] specifically for chips in the status bar that show ongoing duration of an + * activity. * * This class handles: - * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would - * change slightly each second because the width of each number is slightly different. - * - * Instead, we save the largest number width seen so far and ensure that the chip is at least - * that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 - * to 1:00:00), but never smaller. + * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would change + * slightly each second because the width of each number is slightly different. * - * 2) Hiding the text if the time gets too long for the space available. Once the text has been - * hidden, it remains hidden for the duration of the call. + * Instead, we save the largest number width seen so far and ensure that the chip is at least + * that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to + * 1:00:00), but never smaller. + * 2) Hiding the text if the time gets too long for the space available. Once the text has been + * hidden, it remains hidden for the duration of the activity. * * Note that if the text was too big in portrait mode, resulting in the text being hidden, then the * text will also be hidden in landscape (even if there is enough space for it in landscape). */ -class OngoingCallChronometer @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0 -) : Chronometer(context, attrs, defStyle) { +class ChipChronometer +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : + Chronometer(context, attrs, defStyle) { // Minimum width that the text view can be. Corresponds with the largest number width seen so // far. @@ -53,8 +51,8 @@ class OngoingCallChronometer @JvmOverloads constructor( private var shouldHideText: Boolean = false override fun setBase(base: Long) { - // These variables may have changed during the previous call, so re-set them before the new - // call starts. + // These variables may have changed during the previous activity, so re-set them before the + // new activity starts. minimumTextWidth = 0 shouldHideText = false visibility = VISIBLE @@ -75,9 +73,7 @@ class OngoingCallChronometer @JvmOverloads constructor( } // Evaluate how wide the text *wants* to be if it had unlimited space. - super.onMeasure( - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - heightMeasureSpec) + super.onMeasure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec) val desiredTextWidth = measuredWidth // Evaluate how wide the text *can* be based on the enforced constraints diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index 968b591b9a09..5a616dfd1f63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.footer.ui.view; import static android.graphics.PorterDuff.Mode.SRC_ATOP; -import static com.android.systemui.Flags.notificationBackgroundTintOptimization; +import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization; import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.annotation.ColorInt; @@ -407,7 +407,7 @@ public class FooterView extends StackScrollerDecorView { final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background); final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background); final @ColorInt int scHigh; - if (!notificationBackgroundTintOptimization()) { + if (!notificationFooterBackgroundTintOptimization()) { scHigh = Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.materialColorSurfaceContainerHigh); if (scHigh != 0) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index bfc5932c1db0..a901c5f8ae2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -241,9 +241,6 @@ class AvalancheSuppressor( ) { val TAG = "AvalancheSuppressor" - override var reason: String = "avalanche" - protected set - enum class State { ALLOW_CONVERSATION_AFTER_AVALANCHE, ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME, @@ -257,19 +254,15 @@ class AvalancheSuppressor( override fun shouldSuppress(entry: NotificationEntry): Boolean { if (!isCooldownEnabled()) { - reason = "FALSE avalanche cooldown setting DISABLED" return false } val timeSinceAvalancheMs = systemClock.currentTimeMillis() - avalancheProvider.startTime val timedOut = timeSinceAvalancheMs >= avalancheProvider.timeoutMs if (timedOut) { - reason = "FALSE avalanche event TIMED OUT. " + - "${timeSinceAvalancheMs/1000} seconds since last avalanche" return false } val state = calculateState(entry) if (state != State.SUPPRESS) { - reason = "FALSE avalanche IN ALLOWLIST: $state" return false } return true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 4f1056ce9e57..edd2961fe119 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -101,6 +101,7 @@ import com.android.systemui.statusbar.notification.logging.NotificationCounters; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; +import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -845,6 +846,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** + * + * @return true when compact version of Heads Up is on the screen. + */ + public boolean isCompactConversationHeadsUpOnScreen() { + final NotificationViewWrapper viewWrapper = + getVisibleNotificationViewWrapper(); + + return viewWrapper instanceof NotificationCompactMessagingTemplateViewWrapper; + } + /** * @see NotificationChildrenContainer#setUntruncatedChildCount(int) */ public void setUntruncatedChildCount(int childCount) { @@ -2790,7 +2801,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - protected void expandNotification() { + /** + * Triggers expand click listener to expand the notification. + */ + public void expandNotification() { mExpandClickListener.onClick(this); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt index db3cf5abe618..9d0fcd3acceb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row import android.app.Flags +import android.os.SystemProperties import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import javax.inject.Inject @@ -34,8 +35,13 @@ constructor(private val statusBarModeRepositoryStore: StatusBarModeRepositorySto HeadsUpStyleProvider { override fun shouldApplyCompactStyle(): Boolean { - // Use compact HUN for immersive mode. - return Flags.compactHeadsUpNotification() && - statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value + return Flags.compactHeadsUpNotification() && (isInImmersiveMode() || alwaysShow()) } + + private fun isInImmersiveMode() = + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value + + /** developer setting to always show Minimal HUN, even if the device is not in full screen */ + private fun alwaysShow() = + SystemProperties.getBoolean("persist.compact_heads_up_notification.always_show", false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt index ce87d2f46d90..3a5f3b201c97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt @@ -24,7 +24,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow /** * Compact Heads up Notifications template that doesn't set feedback icon and audibly alert icons */ -class NotificationCompactHeadsUpTemplateViewWrapper( +open class NotificationCompactHeadsUpTemplateViewWrapper( ctx: Context, view: View, row: ExpandableNotificationRow diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt new file mode 100644 index 000000000000..bb40b5622159 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification.row.wrapper + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import com.android.internal.R +import com.android.internal.widget.CachingIconView +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow + +/** Wraps a notification containing a messaging or conversation template */ +class NotificationCompactMessagingTemplateViewWrapper +constructor(ctx: Context, view: View, row: ExpandableNotificationRow) : + NotificationCompactHeadsUpTemplateViewWrapper(ctx, view, row) { + + private val compactMessagingView: ViewGroup = requireNotNull(view as? ViewGroup) + + private var conversationIconView: CachingIconView? = null + private var expandBtn: View? = null + override fun onContentUpdated(row: ExpandableNotificationRow?) { + resolveViews() + super.onContentUpdated(row) + } + + private fun resolveViews() { + conversationIconView = compactMessagingView.requireViewById(R.id.conversation_icon) + expandBtn = compactMessagingView.requireViewById(R.id.expand_button) + } + + override fun updateTransformedTypes() { + super.updateTransformedTypes() + + addViewsTransformingToSimilar( + conversationIconView, + expandBtn, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 4244542f1f61..22b95efa5a95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -74,6 +74,8 @@ public abstract class NotificationViewWrapper implements TransformableView { return new NotificationCallTemplateViewWrapper(ctx, v, row); } else if ("compactHUN".equals((v.getTag()))) { return new NotificationCompactHeadsUpTemplateViewWrapper(ctx, v, row); + } else if ("compactMessagingHUN".equals((v.getTag()))) { + return new NotificationCompactMessagingTemplateViewWrapper(ctx, v, row); } if (row.getEntry().getSbn().getNotification().isStyle( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt index d4f8ea385667..d6c73a9dda9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt @@ -23,8 +23,8 @@ import com.android.systemui.flags.RefactorFlagUtils /** Helper for reading or using the heads-up cycling flag state. */ @Suppress("NOTHING_TO_INLINE") object NotificationHeadsUpCycling { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_NOTIFICATION_HEADS_UP_CYCLING + /** The aconfig flag name - enable this feature when FLAG_NOTIFICATION_THROTTLE_HUN is on. */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN /** A token used for dependency declaration */ val token: FlagToken @@ -33,7 +33,7 @@ object NotificationHeadsUpCycling { /** Is the heads-up cycling animation enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationHeadsUpCycling() + get() = Flags.notificationThrottleHun() /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */ @JvmStatic diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index abbe7d74733d..17b54c8f3970 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -674,7 +674,7 @@ public class NotificationStackScrollLayout void setOverExpansion(float margin) { mAmbientState.setOverExpansion(margin); if (notificationOverExpansionClippingFix() && !SceneContainerFlag.isEnabled()) { - setRoundingClippingYTranslation((int) margin); + setRoundingClippingYTranslation(mShouldUseSplitNotificationShade ? (int) margin : 0); } updateStackPosition(); requestChildrenUpdate(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 0ba7b3c214c6..3393321f887b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD @@ -64,6 +65,7 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor @@ -295,8 +297,7 @@ constructor( return combine( isOnLockscreenWithoutShade, keyguardTransitionInteractor.isInTransition( - from = LOCKSCREEN, - to = AOD, + edge = Edge.create(from = LOCKSCREEN, to = AOD) ), ::Pair ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 7d9742849a15..8fb552f167bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -283,12 +283,11 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner, CoreStartable void awakenDreams(); /** - * Handle a touch event while dreaming or on the glanceable hub when the touch was initiated - * within a prescribed swipeable area. This method is provided for cases where swiping in - * certain areas should be handled by CentralSurfaces instead (e.g. swiping hub open, opening - * the notification shade over dream or hub). + * Handle a touch event while dreaming when the touch was initiated within a prescribed + * swipeable area. This method is provided for cases where swiping in certain areas of a dream + * should be handled by CentralSurfaces instead (e.g. swiping communal hub open). */ - void handleExternalShadeWindowTouch(MotionEvent event); + void handleDreamTouch(MotionEvent event); boolean isBouncerShowing(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt index d5e66ff660c6..8af7ee8389e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -79,7 +79,7 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun updateScrimController() {} override fun shouldIgnoreTouch() = false override fun isDeviceInteractive() = false - override fun handleExternalShadeWindowTouch(event: MotionEvent?) {} + override fun handleDreamTouch(event: MotionEvent?) {} override fun awakenDreams() {} override fun isBouncerShowing() = false override fun isBouncerShowingScrimmed() = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index aa55f375b2eb..d3d2b1ebcb88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2805,7 +2805,16 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.setExpansionAffectsAlpha(!unlocking); if (mAlternateBouncerInteractor.isVisibleState()) { - if (!DeviceEntryUdfpsRefactor.isEnabled()) { + if (DeviceEntryUdfpsRefactor.isEnabled()) { + if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) + && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED + || mTransitionToFullShadeProgress > 0f)) { + // Assume scrim state for shade is already correct and do nothing + } else { + // Safeguard which prevents the scrim from being stuck in the wrong state + mScrimController.transitionTo(ScrimState.KEYGUARD); + } + } else { if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED || mTransitionToFullShadeProgress > 0f)) { @@ -2814,7 +2823,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); } } - // This will cancel the keyguardFadingAway animation if it is running. We need to do // this as otherwise it can remain pending and leave keyguard in a weird state. mUnlockScrimCallback.onCancelled(); @@ -2932,8 +2940,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }; @Override - public void handleExternalShadeWindowTouch(MotionEvent event) { - getNotificationShadeWindowViewController().handleExternalTouch(event); + public void handleDreamTouch(MotionEvent event) { + getNotificationShadeWindowViewController().handleDreamTouch(event); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index f219b9d3c185..2b26e3f12ef7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -54,7 +54,6 @@ import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.disableflags.DisableStateTracker; @@ -133,9 +132,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private View mSystemIconsContainer; private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory; - // TODO(b/273443374): remove - private NotificationMediaManager mNotificationMediaManager; - private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @Override @@ -302,7 +298,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat @Main Executor mainExecutor, @Background Executor backgroundExecutor, KeyguardLogger logger, - NotificationMediaManager notificationMediaManager, StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory ) { super(view); @@ -357,7 +352,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /* mask2= */ DISABLE2_SYSTEM_ICONS, this::updateViewState ); - mNotificationMediaManager = notificationMediaManager; mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 74182fc4d2c1..fe001b35e958 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -64,6 +64,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.ScrimAlpha; import com.android.systemui.keyguard.shared.model.TransitionState; @@ -71,6 +72,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; @@ -454,23 +456,32 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump }; // PRIMARY_BOUNCER->GONE - collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(PRIMARY_BOUNCER, GONE), + collectFlow(behindScrim, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(PRIMARY_BOUNCER, GONE)), mBouncerToGoneTransition, mMainDispatcher); collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(), mScrimAlphaConsumer, mMainDispatcher); // ALTERNATE_BOUNCER->GONE - collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, GONE), + collectFlow(behindScrim, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(ALTERNATE_BOUNCER, Scenes.Gone), + Edge.Companion.create(ALTERNATE_BOUNCER, GONE)), mBouncerToGoneTransition, mMainDispatcher); collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(), mScrimAlphaConsumer, mMainDispatcher); // LOCKSCREEN<->GLANCEABLE_HUB + collectFlow( + behindScrim, + mKeyguardTransitionInteractor.transition( + Edge.Companion.create(LOCKSCREEN, Scenes.Communal), + Edge.Companion.create(LOCKSCREEN, GLANCEABLE_HUB)), + mGlanceableHubConsumer, + mMainDispatcher); collectFlow(behindScrim, - mKeyguardTransitionInteractor.transition(LOCKSCREEN, GLANCEABLE_HUB), - mGlanceableHubConsumer, mMainDispatcher); - collectFlow(behindScrim, - mKeyguardTransitionInteractor.transition(GLANCEABLE_HUB, LOCKSCREEN), + mKeyguardTransitionInteractor.transition( + Edge.Companion.create(Scenes.Communal, LOCKSCREEN), + Edge.Companion.create(GLANCEABLE_HUB, LOCKSCREEN)), mGlanceableHubConsumer, mMainDispatcher); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index b71564627223..fa88be5b638b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -1549,7 +1549,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardTransitionInteractor.startDismissKeyguardTransition(); + mKeyguardTransitionInteractor.startDismissKeyguardTransition( + "SBKVM#keyguardAuthenticated"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java index a6284e3f62ab..4505a1d2c548 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java @@ -196,7 +196,22 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, // The group isn't expanded, let's make sure it's visible! mGroupExpansionManager.toggleGroupExpansion(row.getEntry()); } - row.setUserExpanded(true); + + if (android.app.Flags.compactHeadsUpNotificationReply() + && row.isCompactConversationHeadsUpOnScreen()) { + // Notification can be system expanded true and it is set user expanded in + // activateRemoteInput. notifyHeightChanged also doesn't work as visibleType doesn't + // change. To expand huning notification properly, we need set userExpanded false. + if (!row.isPinned() && row.isExpanded()) { + row.setUserExpanded(false); + } + // expand notification emits expanded information to HUN listener. + row.expandNotification(); + } else { + // Note: Since Normal HUN has remote input view in it, we don't expect to hit + // onMakeExpandedVisibleForRemoteInput from activateRemoteInput for Normal HUN. + row.setUserExpanded(true); + } row.getPrivateLayout().setOnExpandedVisibleListener(runnable); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 4fc11df5aaa5..a858fb079d72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -119,7 +119,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private MultiSourceMinAlphaController mEndSideAlphaController; private LinearLayout mEndSideContent; private View mClockView; - private View mOngoingCallChip; + private View mOngoingActivityChip; private View mNotificationIconAreaInner; // Visibilities come in from external system callers via disable flags, but we also sometimes // modify the visibilities internally. We need to store both so that we don't accidentally @@ -345,7 +345,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content); mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent); mClockView = mStatusBar.findViewById(R.id.clock); - mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip); + mOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip); showEndSideContent(false); showClock(false); initOperatorName(); @@ -594,9 +594,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue // so if the icons are disabled then the call chip should be, too.) boolean showOngoingCallChip = hasOngoingCall && !disableNotifications; if (showOngoingCallChip) { - showOngoingCallChip(animate); + showOngoingActivityChip(animate); } else { - hideOngoingCallChip(animate); + hideOngoingActivityChip(animate); } mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip); } @@ -688,14 +688,19 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue animateShow(mClockView, animate); } - /** Hides the ongoing call chip. */ - public void hideOngoingCallChip(boolean animate) { - animateHiddenState(mOngoingCallChip, View.GONE, animate); + /** Hides the ongoing activity chip. */ + private void hideOngoingActivityChip(boolean animate) { + animateHiddenState(mOngoingActivityChip, View.GONE, animate); } - /** Displays the ongoing call chip. */ - public void showOngoingCallChip(boolean animate) { - animateShow(mOngoingCallChip, animate); + /** + * Displays the ongoing activity chip. + * + * For now, this chip will only ever contain the ongoing call information, but after b/332662551 + * feature is implemented, it will support different kinds of ongoing activities. + */ + private void showOngoingActivityChip(boolean animate) { + animateShow(mOngoingActivityChip, animate); } /** @@ -803,7 +808,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private void initOngoingCallChip() { mOngoingCallController.addCallback(mOngoingCallListener); - mOngoingCallController.setChipView(mOngoingCallChip); + mOngoingCallController.setChipView(mOngoingActivityChip); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index ec88b6c56477..a7d4ce30a191 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -36,6 +36,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.chips.ui.view.ChipChronometer +import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -145,8 +147,8 @@ class OngoingCallController @Inject constructor( fun setChipView(chipView: View) { tearDownChipView() this.chipView = chipView - val backgroundView: OngoingCallBackgroundContainer? = - chipView.findViewById(R.id.ongoing_call_chip_background) + val backgroundView: ChipBackgroundContainer? = + chipView.findViewById(R.id.ongoing_activity_chip_background) backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight } if (hasOngoingCall()) { updateChip() @@ -226,7 +228,7 @@ class OngoingCallController @Inject constructor( if (callNotificationInfo == null) { return } val currentChipView = chipView val backgroundView = - currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background) + currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background) val intent = callNotificationInfo?.intent if (currentChipView != null && backgroundView != null && intent != null) { currentChipView.setOnClickListener { @@ -271,8 +273,8 @@ class OngoingCallController @Inject constructor( @VisibleForTesting fun tearDownChipView() = chipView?.getTimeView()?.stop() - private fun View.getTimeView(): OngoingCallChronometer? { - return this.findViewById(R.id.ongoing_call_chip_time) + private fun View.getTimeView(): ChipChronometer? { + return this.findViewById(R.id.ongoing_activity_chip_time) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt index 9c78ab42a14a..886481e64dbe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index d4b2dbff078b..2e54972c4950 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -53,6 +53,27 @@ constructor( ) } + fun logTopLevelServiceStateBroadcastEmergencyOnly(subId: Int, serviceState: ServiceState) { + buffer.log( + TAG, + LogLevel.INFO, + { + int1 = subId + bool1 = serviceState.isEmergencyOnly + }, + { "ACTION_SERVICE_STATE for subId=$int1. ServiceState.isEmergencyOnly=$bool1" } + ) + } + + fun logTopLevelServiceStateBroadcastMissingExtras(subId: Int) { + buffer.log( + TAG, + LogLevel.INFO, + { int1 = subId }, + { "ACTION_SERVICE_STATE for subId=$int1. Intent is missing extras. Ignoring" } + ) + } + fun logOnSignalStrengthsChanged(signalStrength: SignalStrength, subId: Int) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt new file mode 100644 index 000000000000..cce3eb02023b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.data.model + +import android.telephony.ServiceState + +/** + * Simplified representation of a [ServiceState] for use in SystemUI. Add any fields that we need to + * extract from service state here for consumption downstream + */ +data class ServiceStateModel(val isEmergencyOnly: Boolean) { + companion object { + fun fromServiceState(serviceState: ServiceState): ServiceStateModel { + return ServiceStateModel(isEmergencyOnly = serviceState.isEmergencyOnly) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt index 9471574fc755..5ad8bf1652b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt @@ -21,6 +21,7 @@ import android.telephony.SubscriptionManager import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings.Config +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -92,6 +93,19 @@ interface MobileConnectionsRepository { val defaultMobileIconGroup: Flow<MobileIconGroup> /** + * [deviceServiceState] is equivalent to the last [Intent.ACTION_SERVICE_STATE] broadcast with a + * subscriptionId of -1 (aka [SubscriptionManager.INVALID_SUBSCRIPTION_ID]). + * + * While each [MobileConnectionsRepository] listens for the service state of each subscription, + * there is potentially a service state associated with the device itself. This value can be + * used to calculate e.g., the emergency calling capability of the device (as opposed to the + * emergency calling capability of an individual mobile connection) + * + * Note: this is a [StateFlow] using an eager sharing strategy. + */ + val deviceServiceState: StateFlow<ServiceStateModel?> + + /** * If any active SIM on the device is in * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt index 8a8e33efbcef..b0681525a137 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt @@ -25,6 +25,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl @@ -151,6 +152,15 @@ constructor( override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> = activeRepo.flatMapLatest { it.defaultMobileIconGroup } + override val deviceServiceState: StateFlow<ServiceStateModel?> = + activeRepo + .flatMapLatest { it.deviceServiceState } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + realRepository.deviceServiceState.value + ) + override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure } override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index 2b3c6326032c..a944e9133a31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -27,6 +27,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository @@ -136,6 +137,9 @@ constructor( override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G) + // TODO(b/339023069): demo command for device-based connectivity state + override val deviceServiceState: StateFlow<ServiceStateModel?> = MutableStateFlow(null) + override val isAnySimSecure: Flow<Boolean> = flowOf(getIsAnySimSecure()) override fun getIsAnySimSecure(): Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 0073e9cd3dd8..c32f0e8090ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -18,8 +18,10 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod import android.annotation.SuppressLint import android.content.Context +import android.content.Intent import android.content.IntentFilter import android.telephony.CarrierConfigManager +import android.telephony.ServiceState import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID @@ -35,7 +37,6 @@ import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings.Config import com.android.systemui.Dumpable import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.Airplane import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy @@ -55,6 +57,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.util.kotlin.pairwise +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.io.PrintWriter import java.lang.ref.WeakReference import javax.inject.Inject @@ -68,6 +71,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest @@ -169,6 +173,35 @@ constructor( } .flowOn(bgDispatcher) + /** Note that this flow is eager, so we don't miss any state */ + override val deviceServiceState: StateFlow<ServiceStateModel?> = + broadcastDispatcher + .broadcastFlow(IntentFilter(Intent.ACTION_SERVICE_STATE)) { intent, _ -> + val subId = + intent.getIntExtra( + SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, + INVALID_SUBSCRIPTION_ID + ) + + val extras = intent.extras + if (extras == null) { + logger.logTopLevelServiceStateBroadcastMissingExtras(subId) + return@broadcastFlow null + } + + val serviceState = ServiceState.newFromBundle(extras) + logger.logTopLevelServiceStateBroadcastEmergencyOnly(subId, serviceState) + if (subId == INVALID_SUBSCRIPTION_ID) { + // Assume that -1 here is the device's service state. We don't care about + // other ones. + ServiceStateModel.fromServiceState(serviceState) + } else { + null + } + } + .filterNotNull() + .stateIn(scope, SharingStarted.Eagerly, null) + /** * State flow that emits the set of mobile data subscriptions, each represented by its own * [SubscriptionModel]. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index 91d7ca65b30d..cc4d5689c3fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -111,6 +111,13 @@ interface MobileIconsInteractor { val isForceHidden: Flow<Boolean> /** + * True if the device-level service state (with -1 subscription id) reports emergency calls + * only. This value is only useful when there are no other subscriptions OR all existing + * subscriptions report that they are not in service. + */ + val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> + + /** * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given * subId. */ @@ -377,6 +384,9 @@ constructor( .map { it.contains(ConnectivitySlot.MOBILE) } .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> = + mobileConnectionsRepo.deviceServiceState.map { it?.isEmergencyOnly ?: false } + /** Vends out new [MobileIconInteractor] for a particular subId */ override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt index 51c053ee284a..5b954b272044 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt @@ -19,6 +19,9 @@ package com.android.systemui.statusbar.pipeline.satellite.domain.interactor import com.android.internal.telephony.flags.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState @@ -45,6 +48,7 @@ constructor( deviceProvisioningInteractor: DeviceProvisioningInteractor, wifiInteractor: WifiInteractor, @Application scope: CoroutineScope, + @OemSatelliteInputLog private val logBuffer: LogBuffer, ) { /** Must be observed by any UI showing Satellite iconography */ val isSatelliteAllowed = @@ -79,25 +83,52 @@ constructor( val isWifiActive: Flow<Boolean> = wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active } + private val allConnectionsOos = + iconsInteractor.icons.aggregateOver( + selector = { intr -> + combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) { + isInService, + isEmergencyOnly, + isNtn -> + !isInService && !isEmergencyOnly && !isNtn + } + }, + defaultValue = true, // no connections == everything is OOS + ) { isOosAndNotEmergencyAndNotSatellite -> + isOosAndNotEmergencyAndNotSatellite.all { it } + } + /** When all connections are considered OOS, satellite connectivity is potentially valid */ val areAllConnectionsOutOfService = if (Flags.oemEnabledSatelliteFlag()) { - iconsInteractor.icons.aggregateOver( - selector = { intr -> - combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) { - isInService, - isEmergencyOnly, - isNtn -> - !isInService && !(isEmergencyOnly || isNtn) - } - } - ) { isOosAndNotEmergencyOnlyOrSatellite -> - isOosAndNotEmergencyOnlyOrSatellite.all { it } + combine( + allConnectionsOos, + iconsInteractor.isDeviceInEmergencyCallsOnlyMode, + ) { connectionsOos, deviceEmergencyOnly -> + logBuffer.log( + TAG, + LogLevel.INFO, + { + bool1 = connectionsOos + bool2 = deviceEmergencyOnly + }, + { + "Updating OOS status. allConnectionsOOs=$bool1 " + + "deviceEmergencyOnly=$bool2" + }, + ) + // If no connections exist, or all are OOS, then we look to the device-based + // service state to detect if any calls are possible + connectionsOos && !deviceEmergencyOnly } } else { flowOf(false) } .stateIn(scope, SharingStarted.WhileSubscribed(), true) + + companion object { + const val TAG = "DeviceBasedSatelliteInteractor" + } } /** @@ -106,12 +137,22 @@ constructor( * * Provides a way to connect the reactivity of the top-level flow with the reactivity of an * arbitrarily-defined relationship ([selector]) from R to the flow that R exposes. + * + * [defaultValue] allows for a default value to be used if there are no leaf nodes after applying + * [selector]. E.g., if there are no mobile connections, assume that there is no service. */ @OptIn(ExperimentalCoroutinesApi::class) private inline fun <R, reified S, T> Flow<List<R>>.aggregateOver( crossinline selector: (R) -> Flow<S>, - crossinline transform: (Array<S>) -> T + defaultValue: T, + crossinline transform: (Array<S>) -> T, ): Flow<T> { return map { list -> list.map { selector(it) } } - .flatMapLatest { newFlows -> combine(newFlows) { newVals -> transform(newVals) } } + .flatMapLatest { newFlows -> + if (newFlows.isEmpty()) { + flowOf(defaultValue) + } else { + combine(newFlows) { newVals -> transform(newVals) } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt index cc87e8a45d13..0a6e95eee127 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED @@ -81,12 +82,12 @@ constructor( ) : CollapsedStatusBarViewModel { override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> = keyguardTransitionInteractor - .isInTransition(LOCKSCREEN, OCCLUDED) + .isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED)) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false) override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> = keyguardTransitionInteractor - .transition(LOCKSCREEN, DREAMING) + .transition(Edge.create(from = LOCKSCREEN, to = DREAMING)) .filter { it.transitionState == TransitionState.STARTED } .map {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index fa8a7d8ae587..8b48bd32e089 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry +import com.android.systemui.util.Compile import java.io.PrintWriter import javax.inject.Inject @@ -30,12 +31,14 @@ import javax.inject.Inject * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager */ @SysUISingleton -class AvalancheController @Inject constructor( +class AvalancheController +@Inject +constructor( dumpManager: DumpManager, ) : Dumpable { private val tag = "AvalancheController" - private val debug = false + private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG) // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null @@ -79,7 +82,7 @@ class AvalancheController @Inject constructor( val fn = "[$label] => AvalancheController.update [${getKey(entry)}]" if (entry == null) { log { "Entry is NULL, stop update." } - return; + return } if (debug) { debugRunnableLabelMap[runnable] = label @@ -106,7 +109,10 @@ class AvalancheController @Inject constructor( if (isOnlyNextEntry) { // HeadsUpEntry.updateEntry recursively calls AvalancheController#update // and goes to the isShowing case above - headsUpEntryShowing!!.updateEntry(false, "avalanche duration update") + headsUpEntryShowing!!.updateEntry( + /* updatePostTime= */ false, + /* updateEarliestRemovalTime= */ false, + /* reason= */ "avalanche duration update") } } logState("after $fn") @@ -142,9 +148,12 @@ class AvalancheController @Inject constructor( } else if (isShowing(entry)) { log { "$fn => [remove showing ${getKey(entry)}]" } previousHunKey = getKey(headsUpEntryShowing) - + // Show the next HUN before removing this one, so that we don't tell listeners + // onHeadsUpPinnedModeChanged, which causes + // NotificationPanelViewController.updateTouchableRegion to hide the window while the + // HUN is animating out, resulting in a flicker. + showNext() runnable.run() - showNextAfterRemove() } else { log { "$fn => [removing untracked ${getKey(entry)}]" } } @@ -247,7 +256,7 @@ class AvalancheController @Inject constructor( } } - private fun showNextAfterRemove() { + private fun showNext() { log { "SHOW NEXT" } headsUpEntryShowing = null @@ -294,17 +303,21 @@ class AvalancheController @Inject constructor( private fun getStateStr(): String { return "SHOWING: [${getKey(headsUpEntryShowing)}]" + - "\nPREVIOUS: [$previousHunKey]" + - "\nNEXT LIST: $nextListStr" + - "\nNEXT MAP: $nextMapStr" + - "\nDROPPED: $dropSetStr" + "\nPREVIOUS: [$previousHunKey]" + + "\nNEXT LIST: $nextListStr" + + "\nNEXT MAP: $nextMapStr" + + "\nDROPPED: $dropSetStr" } private fun logState(reason: String) { - log { "\n================================================================================="} + log { + "\n=================================================================================" + } log { "STATE $reason" } log { getStateStr() } - log { "=================================================================================\n"} + log { + "=================================================================================\n" + } } private val dropSetStr: String diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index b8318a7dfb61..a7fe49b54602 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -39,6 +39,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.util.ListenerSet; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -114,7 +115,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { mUiEventLogger = uiEventLogger; mAvalancheController = avalancheController; Resources resources = context.getResources(); - mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); + mMinimumDisplayTime = NotificationThrottleHun.isEnabled() + ? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time); mStickyForSomeTimeAutoDismissTime = resources.getInteger( R.integer.sticky_heads_up_notification_time); mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay); @@ -765,11 +767,23 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * @param updatePostTime whether or not to refresh the post time */ public void updateEntry(boolean updatePostTime, @Nullable String reason) { + updateEntry(updatePostTime, /* updateEarliestRemovalTime= */ true, reason); + } + + /** + * Updates an entry's removal time. + * @param updatePostTime whether or not to refresh the post time + * @param updateEarliestRemovalTime whether this update should further delay removal + */ + public void updateEntry(boolean updatePostTime, boolean updateEarliestRemovalTime, + @Nullable String reason) { Runnable runnable = () -> { mLogger.logUpdateEntry(mEntry, updatePostTime, reason); final long now = mSystemClock.elapsedRealtime(); - mEarliestRemovalTime = now + mMinimumDisplayTime; + if (updateEarliestRemovalTime) { + mEarliestRemovalTime = now + mMinimumDisplayTime; + } if (updatePostTime) { mPostTime = Math.max(mPostTime, now); @@ -785,7 +799,9 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { FinishTimeUpdater finishTimeCalculator = () -> { final long finishTime = calculateFinishTime(); final long now = mSystemClock.elapsedRealtime(); - final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime); + final long timeLeft = NotificationThrottleHun.isEnabled() + ? Math.max(finishTime, mEarliestRemovalTime) - now + : Math.max(finishTime - now, mMinimumDisplayTime); return timeLeft; }; scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)"); @@ -818,13 +834,6 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } public int compareNonTimeFields(HeadsUpEntry headsUpEntry) { - boolean isPinned = mEntry.isRowPinned(); - boolean otherPinned = headsUpEntry.mEntry.isRowPinned(); - if (isPinned && !otherPinned) { - return -1; - } else if (!isPinned && otherPinned) { - return 1; - } boolean selfFullscreen = hasFullScreenIntent(mEntry); boolean otherFullscreen = hasFullScreenIntent(headsUpEntry.mEntry); if (selfFullscreen && !otherFullscreen) { @@ -851,6 +860,13 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } public int compareTo(@NonNull HeadsUpEntry headsUpEntry) { + boolean isPinned = mEntry.isRowPinned(); + boolean otherPinned = headsUpEntry.mEntry.isRowPinned(); + if (isPinned && !otherPinned) { + return -1; + } else if (!isPinned && otherPinned) { + return 1; + } int nonTimeCompareResult = compareNonTimeFields(headsUpEntry); if (nonTimeCompareResult != 0) { return nonTimeCompareResult; diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt index 93396516add7..516cb46ec6ee 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt @@ -533,7 +533,7 @@ constructor( targetUserId = targetUserId, ::showDialog, ::dismissDialog, - ::selectUser, + ::switchUser ) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt index 155102c9b9a7..369610882959 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt @@ -27,6 +27,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactoryImpl +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractorImpl import dagger.Binds import dagger.Module import dagger.Provides @@ -41,6 +43,11 @@ interface MediaDevicesModule { impl: LocalMediaRepositoryFactoryImpl ): LocalMediaRepositoryFactory + @Binds + fun bindMediaControllerInteractor( + impl: MediaControllerInteractorImpl + ): MediaControllerInteractor + companion object { @Provides diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt index 1f037c0280e3..4812765d4afe 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.volume.data.repository +package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor import android.media.MediaMetadata import android.media.session.MediaController @@ -22,79 +22,75 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import android.os.Bundle import android.os.Handler +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel +import javax.inject.Inject import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.launch -/** [MediaController.Callback] flow representation. */ -fun MediaController.stateChanges(handler: Handler): Flow<MediaControllerChange> { - return callbackFlow { - val callback = MediaControllerCallbackProducer(this) - registerCallback(callback, handler) - awaitClose { unregisterCallback(callback) } - } -} - -/** Models particular change event received by [MediaController.Callback]. */ -sealed interface MediaControllerChange { - - data object SessionDestroyed : MediaControllerChange - - data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChange - - data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChange +interface MediaControllerInteractor { - data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChange - - data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) : - MediaControllerChange - - data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChange - - data class ExtrasChanged(val extras: Bundle?) : MediaControllerChange + /** [MediaController.Callback] flow representation. */ + fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> +} - data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : MediaControllerChange +@SysUISingleton +class MediaControllerInteractorImpl +@Inject +constructor( + @Background private val backgroundHandler: Handler, +) : MediaControllerInteractor { + + override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> { + return conflatedCallbackFlow { + val callback = MediaControllerCallbackProducer(this) + mediaController.registerCallback(callback, backgroundHandler) + awaitClose { mediaController.unregisterCallback(callback) } + } + } } private class MediaControllerCallbackProducer( - private val producingScope: ProducerScope<MediaControllerChange> + private val producingScope: ProducerScope<MediaControllerChangeModel> ) : MediaController.Callback() { override fun onSessionDestroyed() { - send(MediaControllerChange.SessionDestroyed) + send(MediaControllerChangeModel.SessionDestroyed) } override fun onSessionEvent(event: String, extras: Bundle?) { - send(MediaControllerChange.SessionEvent(event, extras)) + send(MediaControllerChangeModel.SessionEvent(event, extras)) } override fun onPlaybackStateChanged(state: PlaybackState?) { - send(MediaControllerChange.PlaybackStateChanged(state)) + send(MediaControllerChangeModel.PlaybackStateChanged(state)) } override fun onMetadataChanged(metadata: MediaMetadata?) { - send(MediaControllerChange.MetadataChanged(metadata)) + send(MediaControllerChangeModel.MetadataChanged(metadata)) } override fun onQueueChanged(queue: MutableList<MediaSession.QueueItem>?) { - send(MediaControllerChange.QueueChanged(queue)) + send(MediaControllerChangeModel.QueueChanged(queue)) } override fun onQueueTitleChanged(title: CharSequence?) { - send(MediaControllerChange.QueueTitleChanged(title)) + send(MediaControllerChangeModel.QueueTitleChanged(title)) } override fun onExtrasChanged(extras: Bundle?) { - send(MediaControllerChange.ExtrasChanged(extras)) + send(MediaControllerChangeModel.ExtrasChanged(extras)) } override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) { - send(MediaControllerChange.AudioInfoChanged(info)) + send(MediaControllerChangeModel.AudioInfoChanged(info)) } - private fun send(change: MediaControllerChange) { + private fun send(change: MediaControllerChangeModel) { producingScope.launch { producingScope.send(change) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt index dc73344fafe6..599bd73abb69 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt @@ -18,11 +18,9 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.media.session.MediaController import android.media.session.PlaybackState -import android.os.Handler -import com.android.settingslib.volume.data.repository.MediaControllerChange import com.android.settingslib.volume.data.repository.MediaControllerRepository -import com.android.settingslib.volume.data.repository.stateChanges import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject @@ -45,38 +43,39 @@ class MediaDeviceSessionInteractor @Inject constructor( @Background private val backgroundCoroutineContext: CoroutineContext, - @Background private val backgroundHandler: Handler, + private val mediaControllerInteractor: MediaControllerInteractor, private val mediaControllerRepository: MediaControllerRepository, ) { /** [PlaybackState] changes for the [MediaDeviceSession]. */ fun playbackState(session: MediaDeviceSession): Flow<PlaybackState?> { return stateChanges(session) { - emit(MediaControllerChange.PlaybackStateChanged(it.playbackState)) + emit(MediaControllerChangeModel.PlaybackStateChanged(it.playbackState)) } - .filterIsInstance(MediaControllerChange.PlaybackStateChanged::class) + .filterIsInstance(MediaControllerChangeModel.PlaybackStateChanged::class) .map { it.state } } /** [MediaController.PlaybackInfo] changes for the [MediaDeviceSession]. */ fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo?> { return stateChanges(session) { - emit(MediaControllerChange.AudioInfoChanged(it.playbackInfo)) + emit(MediaControllerChangeModel.AudioInfoChanged(it.playbackInfo)) } - .filterIsInstance(MediaControllerChange.AudioInfoChanged::class) + .filterIsInstance(MediaControllerChangeModel.AudioInfoChanged::class) .map { it.info } } private fun stateChanges( session: MediaDeviceSession, - onStart: suspend FlowCollector<MediaControllerChange>.(controller: MediaController) -> Unit, - ): Flow<MediaControllerChange?> = + onStart: + suspend FlowCollector<MediaControllerChangeModel>.(controller: MediaController) -> Unit, + ): Flow<MediaControllerChangeModel?> = mediaControllerRepository.activeSessions .flatMapLatest { controllers -> val controller: MediaController = findControllerForSession(controllers, session) ?: return@flatMapLatest flowOf(null) - controller.stateChanges(backgroundHandler).onStart { onStart(controller) } + mediaControllerInteractor.stateChanges(controller).onStart { onStart(controller) } } .flowOn(backgroundCoroutineContext) diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index b00829e48404..9fbd79accf80 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -19,12 +19,10 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.content.pm.PackageManager import android.media.VolumeProvider import android.media.session.MediaController -import android.os.Handler import android.util.Log import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.repository.LocalMediaRepository import com.android.settingslib.volume.data.repository.MediaControllerRepository -import com.android.settingslib.volume.data.repository.stateChanges import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions @@ -61,7 +59,7 @@ constructor( @VolumePanelScope private val coroutineScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, mediaControllerRepository: MediaControllerRepository, - @Background private val backgroundHandler: Handler, + private val mediaControllerInteractor: MediaControllerInteractor, ) { private val activeMediaControllers: Flow<MediaControllers> = @@ -194,7 +192,10 @@ constructor( return flowOf(null) } - return stateChanges(backgroundHandler).map { this }.onStart { emit(this@stateChanges) } + return mediaControllerInteractor + .stateChanges(this) + .map { this } + .onStart { emit(this@stateChanges) } } private data class MediaControllers( diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt new file mode 100644 index 000000000000..ef5a44a7a2fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.mediaoutput.domain.model + +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession +import android.media.session.PlaybackState +import android.os.Bundle + +/** Models particular change event received by [MediaController.Callback]. */ +sealed interface MediaControllerChangeModel { + + data object SessionDestroyed : MediaControllerChangeModel + + data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChangeModel + + data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChangeModel + + data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChangeModel + + data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) : + MediaControllerChangeModel + + data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChangeModel + + data class ExtrasChanged(val extras: Bundle?) : MediaControllerChangeModel + + data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : + MediaControllerChangeModel +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index 1568e8c011a1..2e29bbd33f92 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java @@ -20,6 +20,7 @@ import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; import static android.app.WallpaperManager.SetWallpaperFlags; +import static com.android.systemui.Flags.fixImageWallpaperCrashSurfaceAlreadyReleased; import static com.android.window.flags.Flags.offloadColorExtraction; import android.annotation.Nullable; @@ -128,8 +129,17 @@ public class ImageWallpaper extends WallpaperService { * and if the count is 0, unload the bitmap */ private int mBitmapUsages = 0; + + /** + * Main lock for long operations (loading the bitmap or processing colors). + */ private final Object mLock = new Object(); + /** + * Lock for SurfaceHolder operations. Should only be acquired after the main lock. + */ + private final Object mSurfaceLock = new Object(); + CanvasEngine() { super(); setFixedSizeAllowed(true); @@ -223,6 +233,12 @@ public class ImageWallpaper extends WallpaperService { if (DEBUG) { Log.i(TAG, "onSurfaceDestroyed"); } + if (fixImageWallpaperCrashSurfaceAlreadyReleased()) { + synchronized (mSurfaceLock) { + mSurfaceHolder = null; + } + return; + } mLongExecutor.execute(this::onSurfaceDestroyedSynchronized); } @@ -259,7 +275,7 @@ public class ImageWallpaper extends WallpaperService { } private void drawFrameInternal() { - if (mSurfaceHolder == null) { + if (mSurfaceHolder == null && !fixImageWallpaperCrashSurfaceAlreadyReleased()) { Log.i(TAG, "attempt to draw a frame without a valid surface"); return; } @@ -268,6 +284,19 @@ public class ImageWallpaper extends WallpaperService { if (!isBitmapLoaded()) { loadWallpaperAndDrawFrameInternal(); } else { + if (fixImageWallpaperCrashSurfaceAlreadyReleased()) { + synchronized (mSurfaceLock) { + if (mSurfaceHolder == null) { + Log.i(TAG, "Surface released before the image could be drawn"); + return; + } + mBitmapUsages++; + drawFrameOnCanvas(mBitmap); + reportEngineShown(false); + unloadBitmapIfNotUsedInternal(); + return; + } + } mBitmapUsages++; drawFrameOnCanvas(mBitmap); reportEngineShown(false); @@ -328,9 +357,14 @@ public class ImageWallpaper extends WallpaperService { mBitmap.recycle(); } mBitmap = null; - - final Surface surface = getSurfaceHolder().getSurface(); - surface.hwuiDestroy(); + if (fixImageWallpaperCrashSurfaceAlreadyReleased()) { + synchronized (mSurfaceLock) { + if (mSurfaceHolder != null) mSurfaceHolder.getSurface().hwuiDestroy(); + } + } else { + final Surface surface = getSurfaceHolder().getSurface(); + surface.hwuiDestroy(); + } mWallpaperManager.forgetLoadedWallpaper(); Trace.endSection(); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 6f550ba70045..5702a8c61e7a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -21,14 +21,18 @@ import android.view.View import android.view.ViewTreeObserver import android.widget.FrameLayout import androidx.test.filters.SmallTest -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.core.LogLevel @@ -68,8 +72,9 @@ import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import com.android.systemui.Flags as AConfigFlags +import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @SmallTest @@ -319,26 +324,16 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForDozeAmountTransition_updatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever( - keyguardTransitionInteractor.transition( - KeyguardState.LOCKSCREEN, - KeyguardState.AOD - ) - ) + whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD))) .thenReturn(transitionStep) - whenever( - keyguardTransitionInteractor.transition( - KeyguardState.AOD, - KeyguardState.LOCKSCREEN - ) - ) + whenever(keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN))) .thenReturn(transitionStep) val job = underTest.listenForDozeAmountTransition(this) transitionStep.value = TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, + from = LOCKSCREEN, + to = AOD, value = 0.4f, transitionState = TransitionState.RUNNING, ) @@ -353,14 +348,14 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) + whenever(keyguardTransitionInteractor.transitionStepsToState(AOD)) .thenReturn(transitionStep) val job = underTest.listenForAnyStateToAodTransition(this) transitionStep.value = TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, + from = GONE, + to = AOD, transitionState = TransitionState.STARTED, ) yield() @@ -374,16 +369,16 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN)) - .thenReturn(transitionStep) + whenever(keyguardTransitionInteractor.transitionStepsToState(LOCKSCREEN)) + .thenReturn(transitionStep) val job = underTest.listenForAnyStateToLockscreenTransition(this) transitionStep.value = - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.LOCKSCREEN, - transitionState = TransitionState.STARTED, - ) + TransitionStep( + from = OCCLUDED, + to = LOCKSCREEN, + transitionState = TransitionState.STARTED, + ) yield() verify(animations, times(2)).doze(0f) @@ -395,37 +390,37 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) + whenever(keyguardTransitionInteractor.transitionStepsToState(AOD)) .thenReturn(transitionStep) val job = underTest.listenForAnyStateToAodTransition(this) transitionStep.value = TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, + from = LOCKSCREEN, + to = AOD, transitionState = TransitionState.STARTED, ) yield() verify(animations, never()).doze(1f) - job.cancel() - } + job.cancel() + } @Test fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN)) - .thenReturn(transitionStep) + whenever(keyguardTransitionInteractor.transitionStepsToState(LOCKSCREEN)) + .thenReturn(transitionStep) val job = underTest.listenForAnyStateToLockscreenTransition(this) transitionStep.value = - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - transitionState = TransitionState.STARTED, - ) + TransitionStep( + from = AOD, + to = LOCKSCREEN, + transitionState = TransitionState.STARTED, + ) yield() verify(animations, never()).doze(0f) @@ -437,16 +432,16 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.DOZING)) - .thenReturn(transitionStep) + whenever(keyguardTransitionInteractor.transitionStepsToState(DOZING)) + .thenReturn(transitionStep) val job = underTest.listenForAnyStateToDozingTransition(this) transitionStep.value = - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.DOZING, - transitionState = TransitionState.STARTED, - ) + TransitionStep( + from = LOCKSCREEN, + to = DOZING, + transitionState = TransitionState.STARTED, + ) yield() verify(animations, times(2)).doze(1f) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index 51ceda339778..f9fe5e7e2b62 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.os.SystemClock; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.KeyEvent; @@ -125,8 +126,8 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) public void withFeatureFlagOn_oldMessage_isHidden() { - mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES); KeyguardAbsKeyInputViewController underTest = createTestObject(); underTest.init(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index e0df1e0e5586..2d5e3a6788cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import android.graphics.PointF; -import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -40,7 +39,6 @@ import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; import androidx.test.filters.SmallTest; -import com.android.systemui.Flags; import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.utils.TestUtils; @@ -230,7 +228,6 @@ public class MenuAnimationControllerTest extends SysuiTestCase { } @Test - @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK) public void tuck_animates() { mMenuAnimationController.cancelAnimations(); mMenuAnimationController.moveToEdgeAndHide(); @@ -239,7 +236,6 @@ public class MenuAnimationControllerTest extends SysuiTestCase { } @Test - @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK) public void untuck_animates() { mMenuAnimationController.cancelAnimations(); mMenuAnimationController.moveOutEdgeAndShow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 97c3c4264855..fa78f0c6ec1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -37,6 +37,7 @@ import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.platform.test.annotations.EnableFlags import android.view.HapticFeedbackConstants import android.view.MotionEvent import androidx.test.filters.SmallTest @@ -1272,8 +1273,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_BP_TALKBACK) fun hint_for_talkback_guidance() = runGenericTest { - mSetFlagsRule.enableFlags(FLAG_BP_TALKBACK) val hint by collectLastValue(viewModel.accessibilityHint) // Touches should fall outside of sensor area @@ -1295,10 +1296,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun descriptionOverriddenByVerticalListContentView() = runGenericTest(contentView = promptContentView, description = "test description") { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) @@ -1307,13 +1307,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun descriptionOverriddenByContentViewWithMoreOptionsButton() = runGenericTest( contentView = promptContentViewWithMoreOptionsButton, description = "test description" ) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) @@ -1322,10 +1321,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun descriptionWithoutContentView() = runGenericTest(description = "test description") { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) @@ -1334,19 +1332,17 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logo_nullIfPkgNameNotFound() = runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat(logo).isNull() } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logo_defaultWithOverrides() = runGenericTest(packageName = packageNameForLogoWithOverrides) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return @@ -1357,71 +1353,63 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logo_defaultIsNull() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat(logo).isNull() } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logo_default() = runGenericTest { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat(logo).isEqualTo(defaultLogoIcon) } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logo_resSetByApp() = runGenericTest(logoRes = logoResFromApp) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat(logo).isEqualTo(logoFromApp) } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logo_bitmapSetByApp() = runGenericTest(logoBitmap = logoBitmapFromApp) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp) } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logoDescription_emptyIfPkgNameNotFound() = runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo("") } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logoDescription_defaultIsEmpty() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo("") } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logoDescription_default() = runGenericTest { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo(defaultLogoDescription) } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun logoDescription_setByApp() = runGenericTest(logoDescription = logoDescriptionFromApp) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo(logoDescriptionFromApp) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt index f62a55d02f61..11f74c0b98cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -63,6 +64,7 @@ import org.mockito.junit.MockitoRule @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) +@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE) class BluetoothTileDialogViewModelTest : SysuiTestCase() { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() @@ -113,7 +115,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Before fun setUp() { - mSetFlagsRule.enableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE) scheduler = TestCoroutineScheduler() dispatcher = UnconfinedTestDispatcher(scheduler) testScope = TestScope(dispatcher) diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt deleted file mode 100644 index ab034652e0cc..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.contrast - -import android.app.UiModeManager -import android.app.UiModeManager.ContrastUtils.fromContrastLevel -import android.os.Looper -import android.provider.Settings -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper.RunWithLooper -import android.view.LayoutInflater -import android.view.View -import android.widget.FrameLayout -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.DialogTransitionAnimator -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.model.SysUiState -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.settings.SecureSettings -import com.android.systemui.util.time.FakeSystemClock -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mock -import org.mockito.Mockito.eq -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -/** Test the behaviour of buttons of the [ContrastDialogDelegate]. */ -@SmallTest -@RunWith(AndroidTestingRunner::class) -@RunWithLooper -class ContrastDialogDelegateTest : SysuiTestCase() { - - private val mainExecutor = FakeExecutor(FakeSystemClock()) - private lateinit var mContrastDialogDelegate: ContrastDialogDelegate - @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory - @Mock private lateinit var sysuiDialog: SystemUIDialog - @Mock private lateinit var mockUiModeManager: UiModeManager - @Mock private lateinit var mockUserTracker: UserTracker - @Mock private lateinit var mockSecureSettings: SecureSettings - @Mock private lateinit var sysuiState: SysUiState - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - mDependency.injectTestDependency(FeatureFlags::class.java, FakeFeatureFlags()) - mDependency.injectTestDependency(SysUiState::class.java, sysuiState) - mDependency.injectMockDependency(DialogTransitionAnimator::class.java) - whenever(sysuiState.setFlag(any(), any())).thenReturn(sysuiState) - whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))) - .thenReturn(sysuiDialog) - whenever(sysuiDialog.layoutInflater).thenReturn(LayoutInflater.from(mContext)) - - whenever(mockUserTracker.userId).thenReturn(context.userId) - if (Looper.myLooper() == null) Looper.prepare() - - mContrastDialogDelegate = - ContrastDialogDelegate( - sysuiDialogFactory, - mainExecutor, - mockUiModeManager, - mockUserTracker, - mockSecureSettings - ) - - mContrastDialogDelegate.createDialog() - val viewCaptor = ArgumentCaptor.forClass(View::class.java) - verify(sysuiDialog).setView(viewCaptor.capture()) - whenever(sysuiDialog.requireViewById(anyInt()) as View?).then { - viewCaptor.value.requireViewById(it.getArgument(0)) - } - } - - @Test - fun testClickButtons_putsContrastInSettings() { - mContrastDialogDelegate.onCreate(sysuiDialog, null) - - mContrastDialogDelegate.contrastButtons.forEach { - (contrastLevel: Int, clickedButton: FrameLayout) -> - clickedButton.performClick() - mainExecutor.runAllReady() - verify(mockSecureSettings) - .putFloatForUser( - eq(Settings.Secure.CONTRAST_LEVEL), - eq(fromContrastLevel(contrastLevel)), - eq(context.userId) - ) - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt index 86533086f67a..44a8904f50da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt @@ -26,6 +26,8 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.model.sysUiState +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -47,7 +49,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val testHelper = kosmos.shortcutHelperTestHelper - + private val sysUiState = kosmos.sysUiState private val viewModel = kosmos.shortcutHelperViewModel @Test @@ -90,12 +92,12 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { } @Test - fun shouldShow_falseAfterViewDestroyed() = + fun shouldShow_falseAfterViewClosed() = testScope.runTest { val shouldShow by collectLastValue(viewModel.shouldShow) testHelper.toggle(deviceId = 567) - viewModel.onUserLeave() + viewModel.onViewClosed() assertThat(shouldShow).isFalse() } @@ -108,7 +110,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { testHelper.hideForSystem() testHelper.toggle(deviceId = 987) testHelper.showFromActivity() - viewModel.onUserLeave() + viewModel.onViewClosed() testHelper.hideFromActivity() testHelper.hideForSystem() testHelper.toggle(deviceId = 456) @@ -127,4 +129,27 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { val shouldShowNew by collectLastValue(viewModel.shouldShow) assertThat(shouldShowNew).isEqualTo(shouldShow) } + + @Test + fun sysUiStateFlag_disabledByDefault() = + testScope.runTest { + assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isFalse() + } + + @Test + fun sysUiStateFlag_trueAfterViewOpened() = + testScope.runTest { + viewModel.onViewOpened() + + assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isTrue() + } + + @Test + fun sysUiStateFlag_falseAfterViewClosed() = + testScope.runTest { + viewModel.onViewOpened() + viewModel.onViewClosed() + + assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index b50d248d6940..977116e812ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -8,6 +8,8 @@ import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -19,6 +21,10 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.utils.GlobalWindowManager @@ -69,12 +75,13 @@ class ResourceTrimmerTest : SysuiTestCase() { resourceTrimmer = ResourceTrimmer( keyguardInteractor, - powerInteractor, - kosmos.keyguardTransitionInteractor, - globalWindowManager, - testScope.backgroundScope, - kosmos.testDispatcher, - featureFlags + powerInteractor = powerInteractor, + keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, + globalWindowManager = globalWindowManager, + applicationScope = testScope.backgroundScope, + bgDispatcher = kosmos.testDispatcher, + featureFlags = featureFlags, + sceneInteractor = kosmos.sceneInteractor, ) resourceTrimmer.start() } @@ -203,6 +210,7 @@ class ResourceTrimmerTest : SysuiTestCase() { @Test @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) + @DisableSceneContainer fun keyguardTransitionsToGone_trimsFontCache() = testScope.runTest { keyguardTransitionRepository.sendTransitionSteps( @@ -218,6 +226,20 @@ class ResourceTrimmerTest : SysuiTestCase() { @Test @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) + @EnableSceneContainer + fun keyguardTransitionsToGone_trimsFontCache_scene_container() = + testScope.runTest { + kosmos.setSceneTransition(Idle(Scenes.Gone)) + + verify(globalWindowManager, times(1)) + .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT) + verifyNoMoreInteractions(globalWindowManager) + } + + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) + @DisableSceneContainer fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() = testScope.runTest { featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false) @@ -231,4 +253,18 @@ class ResourceTrimmerTest : SysuiTestCase() { .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) verify(globalWindowManager, times(0)).trimCaches(any()) } + + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) + @EnableSceneContainer + fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache_scene_container() = + testScope.runTest { + featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false) + kosmos.setSceneTransition(Idle(Scenes.Gone)) + + // Memory hidden should still be called. + verify(globalWindowManager, times(1)) + .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + verify(globalWindowManager, times(0)).trimCaches(any()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 53560d740575..48a5df91d47c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -25,16 +25,19 @@ import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.app.animation.Interpolators import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.util.KeyguardTransitionRunner +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -45,6 +48,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.dropWhile import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After @@ -372,6 +377,43 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { assertThat(wtfHandler.failed).isTrue() } + @Test + fun simulateRaceConditionIsProcessedInOrder() = + testScope.runTest { + val ktr = KeyguardTransitionRepositoryImpl(kosmos.testDispatcher) + val steps by collectValues(ktr.transitions.dropWhile { step -> step.from == OFF }) + + // Add a delay to the first transition in order to attempt to have the second transition + // be processed first + val info1 = TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null) + launch { + ktr.forceDelayForRaceConditionTest = true + ktr.startTransition(info1) + } + val info2 = TransitionInfo(OWNER_NAME, LOCKSCREEN, OCCLUDED, animator = null) + launch { + ktr.forceDelayForRaceConditionTest = false + ktr.startTransition(info2) + } + + runCurrent() + assertThat(steps.isEmpty()).isTrue() + + advanceTimeBy(60L) + assertThat(steps[0]) + .isEqualTo( + TransitionStep(info1.from, info1.to, 0f, TransitionState.STARTED, OWNER_NAME) + ) + assertThat(steps[1]) + .isEqualTo( + TransitionStep(info1.from, info1.to, 0f, TransitionState.CANCELED, OWNER_NAME) + ) + assertThat(steps[2]) + .isEqualTo( + TransitionStep(info2.from, info2.to, 0f, TransitionState.STARTED, OWNER_NAME) + ) + } + private fun listWithStep( step: BigDecimal, start: BigDecimal = BigDecimal.ZERO, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt index 62855d79c052..974e3bb133d8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt @@ -21,12 +21,18 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.DismissAction import com.android.systemui.keyguard.shared.model.KeyguardDone import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -65,10 +71,11 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { underTest = KeyguardDismissActionInteractor( - keyguardRepository, - kosmos.keyguardTransitionInteractor, - dismissInteractorWithDependencies.interactor, - testScope.backgroundScope, + repository = keyguardRepository, + transitionInteractor = kosmos.keyguardTransitionInteractor, + dismissInteractor = dismissInteractorWithDependencies.interactor, + applicationScope = testScope.backgroundScope, + sceneInteractor = kosmos.sceneInteractor, ) } @@ -153,6 +160,7 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() = testScope.runTest { val executeDismissAction by collectLastValue(underTest.executeDismissAction) @@ -179,6 +187,29 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction_scene_container() = + testScope.runTest { + val executeDismissAction by collectLastValue(underTest.executeDismissAction) + + // WHEN a keyguard action will run after the keyguard is gone + val onDismissAction = {} + keyguardRepository.setDismissAction( + DismissAction.RunAfterKeyguardGone( + dismissAction = onDismissAction, + onCancelAction = {}, + message = "message", + willAnimateOnLockscreen = true, + ) + ) + assertThat(executeDismissAction).isNull() + + kosmos.setSceneTransition(Idle(Scenes.Gone)) + + assertThat(executeDismissAction).isNotNull() + } + + @Test fun resetDismissAction() = testScope.runTest { val resetDismissAction by collectLastValue(underTest.resetDismissAction) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index ef15d21107b0..fa3fe5c80adb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -91,33 +91,40 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } private val testScope = kosmos.testScope - private val keyguardRepository = kosmos.fakeKeyguardRepository - private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository + private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } + private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository } private var commandQueue = kosmos.fakeCommandQueue private val shadeTestUtil by lazy { kosmos.shadeTestUtil } - private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } private lateinit var featureFlags: FakeFeatureFlags // Used to verify transition requests for test output @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel - private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor - private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor - private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor - private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor - private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor - private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor - private val fromAlternateBouncerTransitionInteractor = + private val fromLockscreenTransitionInteractor by lazy { + kosmos.fromLockscreenTransitionInteractor + } + private val fromDreamingTransitionInteractor by lazy { kosmos.fromDreamingTransitionInteractor } + private val fromDozingTransitionInteractor by lazy { kosmos.fromDozingTransitionInteractor } + private val fromOccludedTransitionInteractor by lazy { kosmos.fromOccludedTransitionInteractor } + private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor } + private val fromAodTransitionInteractor by lazy { kosmos.fromAodTransitionInteractor } + private val fromAlternateBouncerTransitionInteractor by lazy { kosmos.fromAlternateBouncerTransitionInteractor - private val fromPrimaryBouncerTransitionInteractor = + } + private val fromPrimaryBouncerTransitionInteractor by lazy { kosmos.fromPrimaryBouncerTransitionInteractor - private val fromDreamingLockscreenHostedTransitionInteractor = + } + private val fromDreamingLockscreenHostedTransitionInteractor by lazy { kosmos.fromDreamingLockscreenHostedTransitionInteractor - private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor + } + private val fromGlanceableHubTransitionInteractor by lazy { + kosmos.fromGlanceableHubTransitionInteractor + } - private val powerInteractor = kosmos.powerInteractor - private val communalInteractor = kosmos.communalInteractor - private val dockManager = kosmos.fakeDockManager + private val powerInteractor by lazy { kosmos.powerInteractor } + private val communalInteractor by lazy { kosmos.communalInteractor } + private val dockManager by lazy { kosmos.fakeDockManager } companion object { @JvmStatic @@ -633,7 +640,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // GIVEN a prior transition has run to DREAMING keyguardRepository.setDreamingWithOverlay(true) runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) - runCurrent() + advanceTimeBy(60L) // WHEN the device wakes up without a keyguard keyguardRepository.setKeyguardShowing(false) @@ -1662,7 +1669,9 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // THEN a transition from DOZING => OCCLUDED should occur assertThat(transitionRepository) .startedTransition( - ownerName = "FromDozingTransitionInteractor", + ownerName = + "FromDozingTransitionInteractor" + + "(keyguardInteractor.onCameraLaunchDetected)", from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED, animatorAssertion = { it.isNotNull() }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt index 9b2db3e5316c..1f132989b169 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -41,10 +42,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { private lateinit var executor: FakeExecutor @Mock private lateinit var activityTaskManagerService: IActivityTaskManager - @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier + @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Before fun setUp() { @@ -57,6 +57,7 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { activityTaskManagerService = activityTaskManagerService, keyguardStateController = keyguardStateController, keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator, + keyguardTransitionInteractor = keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt index 1396b20a800d..391831a61579 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt @@ -18,15 +18,19 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest +import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.policy.keyguardStateController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -34,6 +38,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.kotlin.whenever @ExperimentalCoroutinesApi @RunWith(JUnit4::class) @@ -49,13 +54,35 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { fun alternateBouncerTransition_alternateBouncerWindowRequiredTrue() = testScope.runTest { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + val canShowAlternateBouncer by collectLastValue(underTest.canShowAlternateBouncer) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) + givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsUdfps() transitionRepository.sendTransitionSteps( listOf( + stepToLockscreen(0f, TransitionState.STARTED), + stepToLockscreen(.4f), + stepToLockscreen(1f, TransitionState.FINISHED), + ), + testScope, + ) + assertThat(canShowAlternateBouncer).isTrue() + transitionRepository.sendTransitionSteps( + listOf( + stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED), + stepFromLockscreenToAlternateBouncer(.4f), + stepFromLockscreenToAlternateBouncer(.6f), + ), + testScope, + ) + assertThat(canShowAlternateBouncer).isTrue() + assertThat(alternateBouncerWindowRequired).isTrue() + + transitionRepository.sendTransitionSteps( + listOf( stepFromAlternateBouncer(0f, TransitionState.STARTED), - stepFromAlternateBouncer(.4f), + stepFromAlternateBouncer(.2f), stepFromAlternateBouncer(.6f), ), testScope, @@ -77,13 +104,21 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) + givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsUdfps() transitionRepository.sendTransitionSteps( listOf( - stepFromAlternateBouncer(0f, TransitionState.STARTED), - stepFromAlternateBouncer(.4f), - stepFromAlternateBouncer(.6f), - stepFromAlternateBouncer(1f), + stepToLockscreen(0f, TransitionState.STARTED), + stepToLockscreen(.4f), + stepToLockscreen(1f, TransitionState.FINISHED), + ), + testScope, + ) + transitionRepository.sendTransitionSteps( + listOf( + stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED), + stepFromLockscreenToAlternateBouncer(.4f), + stepFromLockscreenToAlternateBouncer(.6f), ), testScope, ) @@ -96,13 +131,23 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) + givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsUdfps() transitionRepository.sendTransitionSteps( listOf( + stepFromLockscreenToDozing(0f, TransitionState.STARTED), + stepFromLockscreenToDozing(.4f), + stepFromLockscreenToDozing(.6f), + stepFromLockscreenToDozing(1f, TransitionState.FINISHED), + ), + testScope, + ) + assertThat(alternateBouncerWindowRequired).isFalse() + transitionRepository.sendTransitionSteps( + listOf( stepFromDozingToLockscreen(0f, TransitionState.STARTED), stepFromDozingToLockscreen(.4f), stepFromDozingToLockscreen(.6f), - stepFromDozingToLockscreen(1f), ), testScope, ) @@ -115,19 +160,39 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) + givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsRearFps() transitionRepository.sendTransitionSteps( listOf( - stepFromAlternateBouncer(0f, TransitionState.STARTED), - stepFromAlternateBouncer(.4f), - stepFromAlternateBouncer(.6f), - stepFromAlternateBouncer(1f), + stepToLockscreen(0f, TransitionState.STARTED), + stepToLockscreen(.4f), + stepToLockscreen(1f, TransitionState.FINISHED), + ), + testScope, + ) + transitionRepository.sendTransitionSteps( + listOf( + stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED), + stepFromLockscreenToAlternateBouncer(.4f), + stepFromLockscreenToAlternateBouncer(.6f), ), testScope, ) assertThat(alternateBouncerWindowRequired).isFalse() } + private fun stepToLockscreen( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return step( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ) + } + private fun stepFromAlternateBouncer( value: Float, state: TransitionState = TransitionState.RUNNING @@ -140,6 +205,18 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { ) } + private fun stepFromLockscreenToAlternateBouncer( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return step( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = value, + transitionState = state, + ) + } + private fun stepFromDozingToLockscreen( value: Float, state: TransitionState = TransitionState.RUNNING @@ -152,6 +229,18 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { ) } + private fun stepFromLockscreenToDozing( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return step( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DOZING, + value = value, + transitionState = state, + ) + } + private fun step( from: KeyguardState, to: KeyguardState, @@ -166,4 +255,16 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { ownerName = "AlternateBouncerViewModelTest" ) } + + /** + * Given the alternate bouncer parameters are set so that the alternate bouncer can show, aside + * from the fingerprint modality. + */ + private fun givenCanShowAlternateBouncer() { + kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(false) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) + whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) + whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt index 768d4468de4f..40663ceb2ad2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt @@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.keyguardClockRepository @@ -56,7 +57,7 @@ import platform.test.runner.parameterized.Parameters class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope - val underTest = kosmos.keyguardClockViewModel + val underTest by lazy { kosmos.keyguardClockViewModel } val res = context.resources @Mock lateinit var clockController: ClockController @@ -96,6 +97,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test + @BrokenWithSceneContainer(339465026) fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() = testScope.runTest { val currentClockLayout by collectLastValue(underTest.currentClockLayout) @@ -110,6 +112,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test + @BrokenWithSceneContainer(339465026) fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() = testScope.runTest { val currentClockLayout by collectLastValue(underTest.currentClockLayout) @@ -124,6 +127,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test + @BrokenWithSceneContainer(339465026) fun currentClockLayout_singleShade_smallClock_smallClock() = testScope.runTest { val currentClockLayout by collectLastValue(underTest.currentClockLayout) @@ -193,6 +197,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test + @BrokenWithSceneContainer(339465026) fun testClockSize_dynamicClockSize() = testScope.runTest { with(kosmos) { @@ -216,6 +221,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test + @BrokenWithSceneContainer(339465026) fun isLargeClockVisible_whenSmallClockSize_isFalse() = testScope.runTest { val value by collectLastValue(underTest.isLargeClockVisible) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt index 265ade3fac0f..5986f4a9a9aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt @@ -33,9 +33,6 @@ import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor @@ -50,6 +47,9 @@ import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever private const val KEY = "TEST_KEY" private const val KEY_ALT = "TEST_KEY_2" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt index 99bf2db2ff98..3372f06dec22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt @@ -66,9 +66,6 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After @@ -90,6 +87,9 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoSession import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.capture +import org.mockito.kotlin.eq import org.mockito.quality.Strictness private const val KEY = "KEY" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt index 35eefd91bc8e..caaa42fc364c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt @@ -40,9 +40,6 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor @@ -60,6 +57,9 @@ import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever private const val KEY = "TEST_KEY" private const val KEY_ALT = "TEST_KEY_2" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt index 5791826ab1f9..3bf4173cd7c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt @@ -71,10 +71,6 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.utils.os.FakeHandler @@ -101,6 +97,10 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoSession import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.capture +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever import org.mockito.quality.Strictness private const val KEY = "KEY" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt index befe64c1f411..d2701dd0d3a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt @@ -51,7 +51,6 @@ import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFacto import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After @@ -72,6 +71,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.eq private const val KEY = "TEST_KEY" private const val KEY_OLD = "TEST_KEY_OLD" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt index 030bca25c518..31a243591b60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import org.junit.After import org.junit.Before @@ -38,12 +37,13 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.any import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.eq private const val PACKAGE = "PKG" private const val KEY = "TEST_KEY" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt index cdbf9d757ec1..6ca0bef17404 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt @@ -31,10 +31,6 @@ import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -52,6 +48,10 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.capture +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever private const val KEY = "KEY" private const val PACKAGE = "PKG" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt index 3bb8b8fcd775..7856f9bce5cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt @@ -33,6 +33,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -54,15 +56,16 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PageIndicator import com.android.systemui.res.R +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SecureSettings @@ -92,6 +95,9 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.capture +import org.mockito.kotlin.eq private val DATA = MediaTestUtils.emptyMediaData @@ -152,29 +158,30 @@ class MediaCarouselControllerTest : SysuiTestCase() { testDispatcher = UnconfinedTestDispatcher() mediaCarouselController = MediaCarouselController( - context, - mediaControlPanelFactory, - visualStabilityProvider, - mediaHostStatesManager, - activityStarter, - clock, - kosmos.testDispatcher, - executor, - bgExecutor, - testDispatcher, - mediaDataManager, - configurationController, - falsingManager, - dumpManager, - logger, - debugLogger, - mediaFlags, - keyguardUpdateMonitor, - kosmos.keyguardTransitionInteractor, - globalSettings, - secureSettings, - kosmos.mediaCarouselViewModel, - mediaViewControllerFactory, + context = context, + mediaControlPanelFactory = mediaControlPanelFactory, + visualStabilityProvider = visualStabilityProvider, + mediaHostStatesManager = mediaHostStatesManager, + activityStarter = activityStarter, + systemClock = clock, + mainDispatcher = kosmos.testDispatcher, + executor = executor, + bgExecutor = bgExecutor, + backgroundDispatcher = testDispatcher, + mediaManager = mediaDataManager, + configurationController = configurationController, + falsingManager = falsingManager, + dumpManager = dumpManager, + logger = logger, + debugLogger = debugLogger, + mediaFlags = mediaFlags, + keyguardUpdateMonitor = keyguardUpdateMonitor, + keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, + globalSettings = globalSettings, + secureSettings = secureSettings, + mediaCarouselViewModel = kosmos.mediaCarouselViewModel, + mediaViewControllerFactory = mediaViewControllerFactory, + sceneInteractor = kosmos.sceneInteractor, ) verify(configurationController).addCallback(capture(configListener)) verify(mediaDataManager).addListener(capture(listener)) @@ -834,6 +841,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(mediaCarousel).visibility = View.VISIBLE } + @DisableSceneContainer @ExperimentalCoroutinesApi @Test fun testKeyguardGone_showMediaCarousel() = @@ -857,6 +865,25 @@ class MediaCarouselControllerTest : SysuiTestCase() { job.cancel() } + @EnableSceneContainer + @ExperimentalCoroutinesApi + @Test + fun testKeyguardGone_showMediaCarousel_scene_container() = + kosmos.testScope.runTest { + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) + var updatedVisibility = false + mediaCarouselController.updateHostVisibility = { updatedVisibility = true } + mediaCarouselController.mediaCarousel = mediaCarousel + + val job = mediaCarouselController.listenForAnyStateToGoneKeyguardTransition(this) + kosmos.setSceneTransition(Idle(Scenes.Gone)) + + verify(mediaCarousel).visibility = View.VISIBLE + assertEquals(true, updatedVisibility) + + job.cancel() + } + @ExperimentalCoroutinesApi @Test fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt index 0c9fee9e2741..6d7976e6e51d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt @@ -98,11 +98,6 @@ import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimat import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.KotlinArgumentCaptor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -125,6 +120,9 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq private const val KEY = "TEST_KEY" private const val PACKAGE = "PKG" @@ -247,8 +245,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Set up package manager mocks val icon = context.getDrawable(R.drawable.ic_android) whenever(packageManager.getApplicationIcon(anyString())).thenReturn(icon) - whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) - .thenReturn(icon) + whenever(packageManager.getApplicationIcon(any<ApplicationInfo>())).thenReturn(icon) whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) .thenReturn(applicationInfo) whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE) @@ -644,7 +641,7 @@ public class MediaControlPanelTest : SysuiTestCase() { bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView).setImageDrawable(any(Drawable::class.java)) + verify(albumView).setImageDrawable(any<Drawable>()) } @Test @@ -657,7 +654,7 @@ public class MediaControlPanelTest : SysuiTestCase() { bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView).setImageDrawable(any(Drawable::class.java)) + verify(albumView).setImageDrawable(any<Drawable>()) } @Test @@ -675,12 +672,12 @@ public class MediaControlPanelTest : SysuiTestCase() { player.bindPlayer(state0, PACKAGE) bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView).setImageDrawable(any(Drawable::class.java)) + verify(albumView).setImageDrawable(any<Drawable>()) // Run Metadata update so that later states don't update val captor = argumentCaptor<Animator.AnimatorListener>() verify(mockAnimator, times(2)).addListener(captor.capture()) - captor.value.onAnimationEnd(mockAnimator) + captor.lastValue.onAnimationEnd(mockAnimator) assertThat(titleText.getText()).isEqualTo(TITLE) assertThat(artistText.getText()).isEqualTo(ARTIST) @@ -696,13 +693,13 @@ public class MediaControlPanelTest : SysuiTestCase() { player.bindPlayer(state2, PACKAGE) bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView, times(2)).setImageDrawable(any(Drawable::class.java)) + verify(albumView, times(2)).setImageDrawable(any<Drawable>()) // Fourth binding to new image runs transition due to color scheme change player.bindPlayer(state3, PACKAGE) bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView, times(3)).setImageDrawable(any(Drawable::class.java)) + verify(albumView, times(3)).setImageDrawable(any<Drawable>()) } @Test @@ -974,7 +971,7 @@ public class MediaControlPanelTest : SysuiTestCase() { val captor = argumentCaptor<SeekBarObserver>() verify(seekBarData).observeForever(captor.capture()) - val seekBarObserver = captor.value!! + val seekBarObserver = captor.lastValue // Then the seekbar is set to animate assertThat(seekBarObserver.animationEnabled).isTrue() @@ -1086,27 +1083,19 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(mockAvd0.isRunning()).thenReturn(false) val captor = ArgumentCaptor.forClass(Animatable2.AnimationCallback::class.java) verify(mockAvd0, times(1)).registerAnimationCallback(captor.capture()) - verify(mockAvd1, never()) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd2, never()) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) + verify(mockAvd1, never()).registerAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd2, never()).registerAnimationCallback(any<Animatable2.AnimationCallback>()) captor.getValue().onAnimationEnd(mockAvd0) // Validate correct state was bound assertThat(actionPlayPause.contentDescription).isEqualTo("loading") assertThat(actionPlayPause.getBackground()).isNull() - verify(mockAvd0, times(1)) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd1, times(1)) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd2, times(1)) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd0, times(1)) - .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd1, times(1)) - .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd2, never()) - .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java)) + verify(mockAvd0, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd1, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd2, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd0, times(1)).unregisterAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd1, times(1)).unregisterAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd2, never()).unregisterAnimationCallback(any<Animatable2.AnimationCallback>()) } @Test @@ -1118,7 +1107,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Capture animation handler val captor = argumentCaptor<Animator.AnimatorListener>() verify(mockAnimator, times(2)).addListener(captor.capture()) - val handler = captor.value + val handler = captor.lastValue // Validate text views unchanged but animation started assertThat(titleText.getText()).isEqualTo("") @@ -1147,7 +1136,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Capture animation handler val captor = argumentCaptor<Animator.AnimatorListener>() verify(mockAnimator, times(2)).addListener(captor.capture()) - val handler = captor.value + val handler = captor.lastValue // Validate text views unchanged but animation started assertThat(titleText.getText()).isEqualTo("") @@ -1179,7 +1168,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Capture animation handler val captor = argumentCaptor<Animator.AnimatorListener>() verify(mockAnimator, times(2)).addListener(captor.capture()) - val handler = captor.value + val handler = captor.lastValue handler.onAnimationEnd(mockAnimator) assertThat(artistText.getText()).isEqualTo("ARTIST_0") @@ -1775,10 +1764,9 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attachPlayer(viewHolder) player.bindPlayer(mediaData, KEY) - val callback: () -> Unit = {} - val captor = KotlinArgumentCaptor(callback::class.java) + val captor = argumentCaptor<() -> Unit>() verify(seekBarViewModel).logSeek = captor.capture() - captor.value.invoke() + captor.lastValue.invoke() verify(logger).logSeek(anyInt(), eq(PACKAGE), eq(instanceId)) } @@ -1801,7 +1789,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // THEN it sends the PendingIntent without dismissing keyguard first, // and does not use the Intent directly (see b/271845008) captor.value.onClick(viewHolder.player) - verify(pendingIntent).send(any(Bundle::class.java)) + verify(pendingIntent).send(any<Bundle>()) verify(pendingIntent, never()).getIntent() verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any()) } @@ -2219,8 +2207,8 @@ public class MediaControlPanelTest : SysuiTestCase() { mainExecutor.runAllReady() verify(recCardTitle).setTextColor(any<Int>()) - verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java)) - verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java)) + verify(recAppIconItem, times(3)).setImageDrawable(any<Drawable>()) + verify(coverItem, times(3)).setImageDrawable(any<Drawable>()) verify(coverItem, times(3)).imageMatrix = any() } @@ -2547,7 +2535,7 @@ public class MediaControlPanelTest : SysuiTestCase() { seamless.callOnClick() // Then we send the pending intent as is, without modifying the original intent - verify(pendingIntent).send(any(Bundle::class.java)) + verify(pendingIntent).send(any<Bundle>()) verify(pendingIntent, never()).getIntent() } @@ -2579,13 +2567,16 @@ public class MediaControlPanelTest : SysuiTestCase() { return Icon.createWithBitmap(bmp) } - private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener = - withArgCaptor { - verify(seekBarViewModel).setScrubbingChangeListener(capture()) - } + private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener { + val captor = argumentCaptor<SeekBarViewModel.ScrubbingChangeListener>() + verify(seekBarViewModel).setScrubbingChangeListener(captor.capture()) + return captor.lastValue + } - private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor { - verify(seekBarViewModel).setEnabledChangeListener(capture()) + private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener { + val captor = argumentCaptor<SeekBarViewModel.EnabledChangeListener>() + verify(seekBarViewModel).setEnabledChangeListener(captor.capture()) + return captor.lastValue } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java index 3b6a88af1ee0..5dbfe475fedc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Intent; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -91,8 +93,8 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { } @Test + @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) public void launchMediaOutputBroadcastDialog_flagOff_broadcastDialogFactoryNotCalled() { - mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING); Intent intent = new Intent( MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName()); @@ -105,8 +107,8 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) public void launchMediaOutputBroadcastDialog_ExtraPackageName_BroadcastDialogFactoryCalled() { - mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING); Intent intent = new Intent( MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName()); @@ -119,8 +121,8 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) public void launchMediaOutputBroadcastDialog_WrongExtraKey_DialogBroadcastFactoryNotCalled() { - mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING); Intent intent = new Intent( MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); intent.putExtra("Wrong Package Name Key", getContext().getPackageName()); @@ -133,8 +135,8 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) public void launchMediaOutputBroadcastDialog_NoExtra_BroadcastDialogFactoryNotCalled() { - mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING); Intent intent = new Intent( MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); mMediaOutputDialogReceiver.onReceive(getContext(), intent); diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt index db275ec190ac..db36131b825e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt @@ -52,7 +52,7 @@ class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() { val taskId = 123 val isLowResolution = false val snapshot = createTaskSnapshot() - val thumbnailData = ThumbnailData(snapshot) + val thumbnailData = ThumbnailData.fromSnapshot(snapshot) whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) .thenReturn(thumbnailData) @@ -74,7 +74,7 @@ class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() { fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() = testScope.runTest { val taskId = 321 - val thumbnailData = ThumbnailData(createTaskSnapshot()) + val thumbnailData = ThumbnailData.fromSnapshot(createTaskSnapshot()) whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData) diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index 890e1e011f22..0998c0c3d32c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -94,6 +94,8 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; @@ -1576,17 +1578,19 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @DisableFlags({ + android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS, + android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL + }) public void testUpdateGeneratedPreview_flagDisabled() { - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any()); } @Test + @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS) + @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL) public void testUpdateGeneratedPreview_userLocked() { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false); mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); @@ -1594,9 +1598,9 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS) + @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL) public void testUpdateGeneratedPreview_userUnlocked() { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); @@ -1605,9 +1609,9 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS) + @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL) public void testUpdateGeneratedPreview_doesNotSetTwice() { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); @@ -1617,9 +1621,11 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags({ + android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS, + android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL + }) public void testUpdateGeneratedPreviewWithDataParcel_userLocked() throws InterruptedException { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false); mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); @@ -1628,10 +1634,12 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags({ + android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS, + android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL + }) public void testUpdateGeneratedPreviewWithDataParcel_userUnlocked() throws InterruptedException { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); @@ -1641,10 +1649,12 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags({ + android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS, + android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL + }) public void testUpdateGeneratedPreviewWithDataParcel_doesNotSetTwice() throws InterruptedException { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt index 629c663943db..bc947fb910e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt @@ -3,6 +3,7 @@ package com.android.systemui.qs import android.content.ComponentName import android.service.quicksettings.Tile import android.testing.AndroidTestingRunner +import android.widget.Switch import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.qs.QSTile @@ -66,15 +67,19 @@ class TileStateToProtoTest : SysuiTestCase() { assertThat(proto?.hasBooleanState()).isFalse() } + /** + * The [QSTile.AdapterState.expandedAccessibilityClassName] setting to [Switch] results in the + * proto having a booleanState. The value of that boolean is true iff the tile is active. + */ @Test - fun booleanState_ACTIVE() { + fun adapterState_ACTIVE() { val state = - QSTile.BooleanState().apply { + QSTile.AdapterState().apply { spec = TEST_SPEC label = TEST_LABEL secondaryLabel = TEST_SUBTITLE state = Tile.STATE_ACTIVE - value = true + expandedAccessibilityClassName = Switch::class.java.name } val proto = state.toProto() @@ -89,6 +94,33 @@ class TileStateToProtoTest : SysuiTestCase() { assertThat(proto?.booleanState).isTrue() } + /** + * Similar to [adapterState_ACTIVE], the use of + * [QSTile.AdapterState.expandedAccessibilityClassName] signals that the tile is toggleable. + */ + @Test + fun adapterState_INACTIVE() { + val state = + QSTile.AdapterState().apply { + spec = TEST_SPEC + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_INACTIVE + expandedAccessibilityClassName = Switch::class.java.name + } + val proto = state.toProto() + + assertThat(proto).isNotNull() + assertThat(proto?.hasSpec()).isTrue() + assertThat(proto?.spec).isEqualTo(TEST_SPEC) + assertThat(proto?.hasComponentName()).isFalse() + assertThat(proto?.label).isEqualTo(TEST_LABEL) + assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE) + assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE) + assertThat(proto?.hasBooleanState()).isTrue() + assertThat(proto?.booleanState).isFalse() + } + @Test fun noSpec_returnsNull() { val state = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt index db752dd64997..d15cfbf537a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt @@ -20,7 +20,6 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.testScope -import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository import com.android.systemui.qs.panels.data.repository.IconTilesRepository import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository import com.android.systemui.qs.panels.data.repository.iconTilesRepository @@ -48,9 +47,6 @@ class GridConsistencyInteractorTest : SysuiTestCase() { data object TestGridLayoutType : GridLayoutType - private val gridLayout: MutableStateFlow<GridLayoutType> = - MutableStateFlow(InfiniteGridLayoutType) - private val iconOnlyTiles = MutableStateFlow( setOf( @@ -74,17 +70,13 @@ class GridConsistencyInteractorTest : SysuiTestCase() { Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor), Pair(TestGridLayoutType, noopGridConsistencyInteractor) ) - gridLayoutTypeRepository = - object : GridLayoutTypeRepository { - override val layout: StateFlow<GridLayoutType> = gridLayout.asStateFlow() - } } private val underTest = with(kosmos) { gridConsistencyInteractor } @Before fun setUp() { - gridLayout.value = InfiniteGridLayoutType + with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) } underTest.start() } @@ -94,7 +86,7 @@ class GridConsistencyInteractorTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Using the no-op grid consistency interactor - gridLayout.value = TestGridLayoutType + gridLayoutTypeRepository.setLayout(TestGridLayoutType) // Setting an invalid layout with holes // [ Large A ] [ sa ] diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt index e2a3fac60ee8..ad87315c34f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt @@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.systemui.res.R +import com.android.internal.telephony.flags.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.classifier.FalsingManagerFake @@ -33,10 +33,12 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.util.settings.GlobalSettings import com.google.common.truth.Truth.assertThat import dagger.Lazy +import kotlinx.coroutines.Job import org.junit.After import org.junit.Before import org.junit.Test @@ -44,11 +46,15 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.times +import org.mockito.kotlin.verify @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class AirplaneModeTileTest : SysuiTestCase() { + @Mock private lateinit var mHost: QSHost @Mock @@ -62,7 +68,9 @@ class AirplaneModeTileTest : SysuiTestCase() { @Mock private lateinit var mBroadcastDispatcher: BroadcastDispatcher @Mock - private lateinit var mConnectivityManager: Lazy<ConnectivityManager> + private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager> + @Mock + private lateinit var mConnectivityManager: ConnectivityManager @Mock private lateinit var mGlobalSettings: GlobalSettings @Mock @@ -72,13 +80,15 @@ class AirplaneModeTileTest : SysuiTestCase() { private lateinit var mTestableLooper: TestableLooper private lateinit var mTile: AirplaneModeTile + @Mock + private lateinit var mClickJob: Job @Before fun setUp() { MockitoAnnotations.initMocks(this) mTestableLooper = TestableLooper.get(this) Mockito.`when`(mHost.context).thenReturn(mContext) Mockito.`when`(mHost.userContext).thenReturn(mContext) - + Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager) mTile = AirplaneModeTile( mHost, mUiEventLogger, @@ -90,7 +100,7 @@ class AirplaneModeTileTest : SysuiTestCase() { mActivityStarter, mQsLogger, mBroadcastDispatcher, - mConnectivityManager, + mLazyConnectivityManager, mGlobalSettings, mUserTracker) } @@ -120,4 +130,24 @@ class AirplaneModeTileTest : SysuiTestCase() { assertThat(state.icon) .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on)) } + + @Test + fun handleClick_noSatelliteFeature_directSetAirplaneMode() { + mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + + mTile.handleClick(null) + + verify(mConnectivityManager).setAirplaneMode(any()) + } + + @Test + fun handleClick_hasSatelliteFeatureButClickIsProcessing_doNothing() { + mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + Mockito.`when`(mClickJob.isCompleted).thenReturn(false) + mTile.mClickJob = mClickJob + + mTile.handleClick(null) + + verify(mConnectivityManager, times(0)).setAirplaneMode(any()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index 830f08a0c445..1ffbb7be49fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -9,10 +9,11 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger +import com.android.internal.telephony.flags.Flags import com.android.settingslib.Utils import com.android.settingslib.bluetooth.CachedBluetoothDevice -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.plugins.ActivityStarter @@ -23,13 +24,14 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.BluetoothController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Job import org.junit.After import org.junit.Before import org.junit.Test @@ -37,6 +39,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -54,7 +57,7 @@ class BluetoothTileTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: QsEventLogger @Mock private lateinit var featureFlags: FeatureFlagsClassic @Mock private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel - + @Mock private lateinit var clickJob: Job private lateinit var testableLooper: TestableLooper private lateinit var tile: FakeBluetoothTile @@ -191,6 +194,41 @@ class BluetoothTileTest : SysuiTestCase() { } @Test + fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() { + mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) + .thenReturn(false) + `when`(clickJob.isCompleted).thenReturn(false) + tile.mClickJob = clickJob + + tile.handleClick(null) + + verify(bluetoothController, times(0)).setBluetoothEnabled(any()) + } + + @Test + fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() { + mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) + .thenReturn(false) + + tile.handleClick(null) + + verify(bluetoothController).setBluetoothEnabled(any()) + } + + @Test + fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() { + mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) + .thenReturn(true) + + tile.handleClick(null) + + verify(bluetoothTileDialogViewModel).showDialog(null) + } + + @Test fun testMetadataListener_whenDisconnected_isUnregistered() { val state = QSTile.BooleanState() val cachedDevice = mock<CachedBluetoothDevice>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt index 5e7d8fb5df02..a10d81f86d8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt @@ -21,7 +21,6 @@ import android.content.Intent import android.os.Bundle import android.os.UserHandle import android.testing.AndroidTestingRunner -import android.view.View import android.view.Window import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -49,7 +48,7 @@ class ActionExecutorTest : SysuiTestCase() { private val intentExecutor = mock<ActionIntentExecutor>() private val window = mock<Window>() - private val view = mock<View>() + private val viewProxy = mock<ScreenshotShelfViewProxy>() private val onDismiss = mock<(() -> Unit)>() private val pendingIntent = mock<PendingIntent>() @@ -70,16 +69,16 @@ class ActionExecutorTest : SysuiTestCase() { } @Test - fun sendPendingIntent_dismisses() = runTest { + fun sendPendingIntent_requestsDismissal() = runTest { actionExecutor = createActionExecutor() actionExecutor.sendPendingIntent(pendingIntent) verify(pendingIntent).send(any(Bundle::class.java)) - verify(onDismiss).invoke() + verify(viewProxy).requestDismissal(null) } private fun createActionExecutor(): ActionExecutor { - return ActionExecutor(intentExecutor, testScope, window, view, onDismiss) + return ActionExecutor(intentExecutor, testScope, window, viewProxy, onDismiss) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 766113f09308..8e3290748039 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -547,6 +547,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class)); // Dreaming->Lockscreen + when(mKeyguardTransitionInteractor.transition(any())) + .thenReturn(emptyFlow()); when(mKeyguardTransitionInteractor.transition(any(), any())) .thenReturn(emptyFlow()); when(mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 45d0102f2dd1..4a867a8ecf22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -46,6 +46,7 @@ import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionStep @@ -173,7 +174,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : .thenReturn(keyguardBouncerComponent) whenever(keyguardBouncerComponent.securityContainerController) .thenReturn(keyguardSecurityContainerController) - whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING)) + whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING))) .thenReturn(emptyFlow<TransitionStep>()) featureFlagsClassic = FakeFeatureFlagsClassic() @@ -518,46 +519,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - fun handleExternalTouch_intercepted_sendsOnTouch() { - // Accept dispatch and also intercept. - whenever(view.dispatchTouchEvent(any())).thenReturn(true) - whenever(view.onInterceptTouchEvent(any())).thenReturn(true) - - underTest.handleExternalTouch(DOWN_EVENT) - underTest.handleExternalTouch(MOVE_EVENT) - - // Once intercepted, both events are sent to the view. - verify(view).onTouchEvent(DOWN_EVENT) - verify(view).onTouchEvent(MOVE_EVENT) - } - - @Test - fun handleExternalTouch_notDispatched_interceptNotCalled() { - // Don't accept dispatch - whenever(view.dispatchTouchEvent(any())).thenReturn(false) - - underTest.handleExternalTouch(DOWN_EVENT) - - // Interception is not offered. - verify(view, never()).onInterceptTouchEvent(any()) - } - - @Test - fun handleExternalTouch_notIntercepted_onTouchNotSent() { - // Accept dispatch, but don't dispatch - whenever(view.dispatchTouchEvent(any())).thenReturn(true) - whenever(view.onInterceptTouchEvent(any())).thenReturn(false) - - underTest.handleExternalTouch(DOWN_EVENT) - underTest.handleExternalTouch(MOVE_EVENT) - - // Interception offered for both events, but onTouchEvent is never called. - verify(view).onInterceptTouchEvent(DOWN_EVENT) - verify(view).onInterceptTouchEvent(MOVE_EVENT) - verify(view, never()).onTouchEvent(any()) - } - - @Test fun testGetKeyguardMessageArea() = testScope.runTest { underTest.keyguardMessageArea diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index f380b6c700cd..e83a46bb56a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.res.R @@ -151,7 +152,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { whenever(statusBarStateController.isDozing).thenReturn(false) mDependency.injectTestDependency(ShadeController::class.java, shadeController) whenever(dockManager.isDocked).thenReturn(false) - whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING)) + whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING))) .thenReturn(emptyFlow()) val featureFlags = FakeFeatureFlags() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index 81d0e06df1fc..2c2fcbe75e1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.shade +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -26,8 +28,8 @@ import androidx.annotation.IdRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService @@ -164,10 +166,10 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() { val headerResourceHeight = 20 val headerHelperHeight = 30 - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) .thenReturn(headerHelperHeight) overrideResource(R.bool.config_use_large_screen_shade_header, true) @@ -187,10 +189,10 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() { val headerResourceHeight = 20 val headerHelperHeight = 30 - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) .thenReturn(headerHelperHeight) overrideResource(R.bool.config_use_large_screen_shade_header, true) @@ -400,8 +402,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSplitShadeLayout_isAlignedToGuideline() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) enableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline) @@ -410,8 +412,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSinglePaneLayout_childrenHaveEqualMargins() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) disableSplitShade() underTest.updateResources() val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin @@ -427,8 +429,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) enableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0) @@ -445,9 +447,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderResourceHeight = 100 val largeScreenHeaderHelperHeight = 200 @@ -468,9 +469,9 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderResourceHeight = 100 val largeScreenHeaderHelperHeight = 200 @@ -491,8 +492,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) setSmallScreen() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0) @@ -512,8 +513,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSinglePaneShadeLayout_isAlignedToParent() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) disableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt index 4ae751b4e7eb..f21def361e40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.shade +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -27,6 +29,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService @@ -67,6 +70,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) class NotificationsQSContainerControllerTest : SysuiTestCase() { private val view = mock<NotificationsQuickSettingsContainer>() @@ -99,7 +103,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) fakeSystemClock = FakeSystemClock() delayableExecutor = FakeExecutor(fakeSystemClock) - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) mContext.ensureTestableResources() whenever(view.context).thenReturn(mContext) whenever(view.resources).thenReturn(mContext.resources) @@ -161,8 +164,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val helperHeight = 30 val resourceHeight = 20 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight) @@ -182,8 +185,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val helperHeight = 30 val resourceHeight = 20 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight) @@ -424,8 +427,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderHelperHeight = 200 val largeScreenHeaderResourceHeight = 100 @@ -444,8 +447,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderHelperHeight = 200 val largeScreenHeaderResourceHeight = 100 diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt index 2c453a711c87..dfd7a715fcdf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -36,6 +37,8 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isFalse() + + coroutineContext.cancelChildren() } @Test @@ -45,6 +48,8 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isTrue() + + coroutineContext.cancelChildren() } @Test @@ -58,6 +63,8 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isFalse() + + coroutineContext.cancelChildren() } @Test @@ -71,6 +78,8 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isFalse() + + coroutineContext.cancelChildren() } @Test @@ -81,5 +90,7 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isTrue() + + coroutineContext.cancelChildren() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt index 9ec9b69d44c0..05d9495db091 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.ExpandHelper import com.android.systemui.SysuiTestCase @@ -39,7 +39,7 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DragDownHelperTest : SysuiTestCase() { private lateinit var dragDownHelper: DragDownHelper diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 1504d4c1f033..995b5383c3c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -65,9 +65,9 @@ import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricSourceType; import android.os.BatteryManager; import android.os.RemoteException; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; @@ -88,7 +88,7 @@ import java.util.List; import java.util.Set; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardIndicationControllerTest extends KeyguardIndicationControllerBaseTest { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt index cdc752098aa7..4a14f8853904 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED import kotlinx.coroutines.Dispatchers @@ -28,7 +28,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.times import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) class KeyguardIndicationControllerWithCoroutinesTest : KeyguardIndicationControllerBaseTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt index 8cb530c355bd..948a73208d10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt @@ -1,7 +1,7 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.util.DisplayMetrics +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer @@ -16,7 +16,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LSShadeTransitionLoggerTest : SysuiTestCase() { lateinit var logger: LSShadeTransitionLogger @@ -41,4 +41,4 @@ class LSShadeTransitionLoggerTest : SysuiTestCase() { // log a non-null, non row, ensure no crash logger.logDragDownStarted(view) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt index d3befb4ad4cd..fe2dd6d78a82 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith import java.util.function.Consumer -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LightRevealScrimTest : SysuiTestCase() { @@ -85,4 +85,4 @@ class LightRevealScrimTest : SysuiTestCase() { private const val DEFAULT_WIDTH = 42 private const val DEFAULT_HEIGHT = 24 } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt index 402d9aab66bd..e48242a3e003 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -36,7 +36,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class LockscreenShadeQsTransitionControllerTest : SysuiTestCase() { private val configurationController = FakeConfigurationController() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index a92cf8c96339..69e8f4737a5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.statusbar import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.ExpandHelper import com.android.systemui.SysUITestModule @@ -74,7 +74,7 @@ private fun <T> anyObject(): T { @SmallTest @RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class LockscreenShadeTransitionControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index d3febf55117b..ef1c927f22d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -32,8 +32,8 @@ import android.os.UserHandle; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -52,7 +52,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationListenerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = 0; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index d3850be7c192..c9d910c530ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -27,9 +27,9 @@ import android.app.Notification; import android.content.Context; import android.os.SystemClock; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -52,7 +52,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationRemoteInputManagerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index fc0c85e30d5a..9f94cff4ead4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -17,11 +17,11 @@ package com.android.systemui.statusbar import android.os.IBinder -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Choreographer import android.view.View import android.view.ViewRootImpl +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation @@ -59,7 +59,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @SmallTest class NotificationShadeDepthControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt index 49e5c456e645..9907740672ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -43,7 +43,7 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class PulseExpansionHandlerTest : SysuiTestCase() { private lateinit var pulseExpansionHandler: PulseExpansionHandler diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java index ce11d6a62a8c..58943ea3b4ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java @@ -27,9 +27,9 @@ import android.app.RemoteInputHistoryItem; import android.net.Uri; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -44,7 +44,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class RemoteInputNotificationRebuilderTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt index 2606be5fabad..6b9a19a92fc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt @@ -1,7 +1,7 @@ package com.android.systemui.statusbar import org.mockito.Mockito.`when` as whenever -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController @@ -14,7 +14,7 @@ import org.mockito.Mockito.intThat import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class SingleShadeLockScreenOverScrollerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java index 775dc3c95746..3346e19b4ce9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java @@ -29,9 +29,9 @@ import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -52,7 +52,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class SmartReplyControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt index 700fb1ec332c..58473c4e07a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt @@ -1,7 +1,7 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -23,7 +23,7 @@ import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt index 79a2008e7542..26692c908c5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Assert.assertEquals @@ -24,7 +24,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class StatusBarStateEventTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt index b90582575970..78c1887f7c0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt @@ -5,9 +5,9 @@ import android.os.UserHandle import android.os.VibrationAttributes import android.os.VibrationEffect import android.os.Vibrator -import android.testing.AndroidTestingRunner import android.view.HapticFeedbackConstants import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.eq @@ -26,7 +26,7 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import java.util.concurrent.Executor -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class VibratorHelperTest : SysuiTestCase() { @@ -120,4 +120,4 @@ class VibratorHelperTest : SysuiTestCase() { return verify(vibrator) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt index f0a457e2c19f..7d2b463afab7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone.ongoingcall +package com.android.systemui.statusbar.chips.ui.view import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater import android.view.View import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -31,16 +31,17 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper -class OngoingCallBackgroundContainerTest : SysuiTestCase() { +class ChipBackgroundContainerTest : SysuiTestCase() { - private lateinit var underTest: OngoingCallBackgroundContainer + private lateinit var underTest: ChipBackgroundContainer @Before fun setUp() { allowTestableLooperAsMainThread() TestableLooper.get(this).runWithLooper { - val chipView = LayoutInflater.from(context).inflate(R.layout.ongoing_call_chip, null) - underTest = chipView.requireViewById(R.id.ongoing_call_chip_background) + val chipView = + LayoutInflater.from(context).inflate(R.layout.ongoing_activity_chip, null) + underTest = chipView.requireViewById(R.id.ongoing_activity_chip_background) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt index 7e25aa373097..b8d4e47544dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone.ongoingcall +package com.android.systemui.statusbar.chips.ui.view import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater import android.view.View import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -38,17 +38,18 @@ private const val XL_TEXT = "00:0000" @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper -class OngoingCallChronometerTest : SysuiTestCase() { +class ChipChronometerTest : SysuiTestCase() { - private lateinit var textView: OngoingCallChronometer + private lateinit var textView: ChipChronometer private lateinit var doesNotFitText: String @Before fun setUp() { allowTestableLooperAsMainThread() TestableLooper.get(this).runWithLooper { - val chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null) - textView = chipView.findViewById(R.id.ongoing_call_chip_time)!! + val chipView = + LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null) + textView = chipView.findViewById(R.id.ongoing_activity_chip_time)!! measureTextView() calculateDoesNotFixText() } @@ -159,8 +160,8 @@ class OngoingCallChronometerTest : SysuiTestCase() { private fun measureTextView() { textView.measure( - View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt index 7e88ae080178..643acdbb9277 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.connectivity import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.lifecycle.Lifecycle import com.android.systemui.SysuiTestCase @@ -42,7 +42,7 @@ import org.mockito.MockitoAnnotations import java.util.concurrent.Executor @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class AccessPointControllerImplTest : SysuiTestCase() { @@ -244,4 +244,4 @@ class AccessPointControllerImplTest : SysuiTestCase() { verify(wifiEntryOther).connect(any()) verify(callback, never()).onSettingsActivityTriggered(any()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt index 7aed4f7e250b..40f81e2bae5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.statusbar.connectivity -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase @@ -26,7 +26,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MobileStateTest : SysuiTestCase() { private val state = MobileState() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index 461d80412cb5..4241254e1fb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -39,10 +39,10 @@ import android.os.Looper; import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.SignalIcon.MobileIconGroup; @@ -60,7 +60,7 @@ import org.junit.runner.RunWith; import java.util.HashMap; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class NetworkControllerDataTest extends NetworkControllerBaseTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java index 3bbf06dbc69c..521cb4f4c42d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java @@ -19,9 +19,9 @@ package com.android.systemui.statusbar.connectivity; import static junit.framework.Assert.assertEquals; import android.net.NetworkCapabilities; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Test; @@ -30,7 +30,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class NetworkControllerEthernetTest extends NetworkControllerBaseTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java index 35609a5faf00..22f0e9d20c41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java @@ -33,10 +33,10 @@ import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.graph.SignalDrawable; @@ -59,7 +59,7 @@ import java.util.Collections; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class NetworkControllerSignalTest extends NetworkControllerBaseTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index 44a1c50e5a58..6c80a97625a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -34,9 +34,9 @@ import android.net.NetworkInfo; import android.net.vcn.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.mobile.TelephonyIcons; @@ -50,7 +50,7 @@ import org.mockito.Mockito; import java.util.Collections; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class NetworkControllerWifiTest extends NetworkControllerBaseTest { // These match the constants in WifiManager and need to be kept up to date. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt index 5bf0a94935cf..3eeed73a539c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.connectivity -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.systemui.SysuiTestCase @@ -26,7 +26,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NetworkTypeResIdCacheTest : SysuiTestCase() { private lateinit var cache: NetworkTypeResIdCache private var overrides = MobileIconCarrierIdOverridesFake() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt index 452302d4db8a..984bda1c0d21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt @@ -19,11 +19,11 @@ package com.android.systemui.statusbar.events import android.content.Context import android.graphics.Insets import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.Gravity import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule @@ -46,7 +46,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class SystemEventChipAnimationControllerTest : SysuiTestCase() { private lateinit var controller: SystemEventChipAnimationController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt index ae84df55e113..742494b769de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.events -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor @@ -40,7 +40,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest @OptIn(ExperimentalCoroutinesApi::class) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index cacfa8dc0be4..376873d19624 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.statusbar.events import android.graphics.Insets import android.graphics.Rect import android.os.Process -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule @@ -54,7 +54,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @OptIn(ExperimentalCoroutinesApi::class) @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt index d3f5adeb05bb..0f58990d4310 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.statusbar.gesture -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.InputEvent import android.view.MotionEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeDisplayTracker @@ -13,7 +13,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class GenericGestureDetectorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java index 6b2ee76a75ac..01a0fd020bda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java @@ -19,11 +19,11 @@ package com.android.systemui.statusbar.notification; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.widget.FrameLayout; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -36,7 +36,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class AboveShelfObserverTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java index fc4702c209e1..d66b010daefd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java @@ -42,9 +42,9 @@ import android.os.Handler; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; @@ -58,7 +58,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class AssistantFeedbackControllerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java index 0103564088e0..77fd06757595 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java @@ -25,12 +25,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -56,7 +56,7 @@ import java.util.List; import java.util.Map; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class DynamicChildBindControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java index 5b72ca07edbe..d879fcecffab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java @@ -25,9 +25,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -38,9 +38,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; @SmallTest -@org.junit.runner.RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class DynamicPrivacyControllerTest extends SysuiTestCase { @@ -127,4 +128,4 @@ public class DynamicPrivacyControllerTest extends SysuiTestCase { mDynamicPrivacyController.onUnlockedChanged(); verifyNoMoreInteractions(mListener); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt index 1cce3b5b9e77..8e8a3513f3be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.notification import android.provider.DeviceConfig import android.provider.Settings -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito @@ -39,7 +39,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoSession import org.mockito.quality.Strictness -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class NotificationSectionsFeatureManagerTest : SysuiTestCase() { var manager: NotificationSectionsFeatureManager? = null diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt index 3b3f05d25c16..3abdf6212f22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.statusbar.notification import android.app.Notification.GROUP_ALERT_SUMMARY -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -35,7 +35,7 @@ import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { @Mock lateinit var notificationListContainer: NotificationListContainer diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt index 1aac515f538b..a5206f52b6f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer @@ -28,7 +28,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 67b540cd762e..0906d8eadf44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase @@ -60,7 +60,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) class NotificationWakeUpCoordinatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt index 7d8cf3657ba1..382b307fb9de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification import android.platform.test.annotations.EnableFlags import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation @@ -10,13 +11,12 @@ import com.android.systemui.util.mockito.whenever import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.times import org.mockito.Mockito.verify @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class RoundableTest : SysuiTestCase() { private val targetView: View = mock() private val roundable = FakeRoundable(targetView = targetView) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java index 2d044fec1eb6..8e95ac599ce1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java @@ -30,8 +30,8 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -51,7 +51,7 @@ import java.util.Arrays; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class HighPriorityProviderTest extends SysuiTestCase { @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier; @Mock private GroupMembershipManager mGroupMembershipManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt index 892575ab6c71..2a587511eaec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.collection -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.lifecycle.Observer +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -34,7 +34,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotifLiveDataImplTest : SysuiTestCase() { @@ -164,4 +164,4 @@ class NotifLiveDataImplTest : SysuiTestCase() { assertThat(executor.runAllReady()).isEqualTo(2) verifyNoMoreInteractions(syncObserver, asyncObserver) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt index 9c8ac5cded9e..d87f827de776 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.collection -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -30,7 +30,7 @@ import org.junit.runner.RunWith import java.lang.UnsupportedOperationException @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotifLiveDataStoreImplTest : SysuiTestCase() { @@ -102,4 +102,4 @@ class NotifLiveDataStoreImplTest : SysuiTestCase() { liveDataStoreImpl.setActiveNotifList(mutableListOf(entry1, entry2)) executor.runAllReady() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt index 3b908b4175f9..f1da22f08e75 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.collection -import android.testing.AndroidTestingRunner import android.view.Choreographer +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.SysUISingleton @@ -36,7 +36,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotifPipelineChoreographerTest : SysuiTestCase() { val viewChoreographer: Choreographer = mock() @@ -118,4 +118,4 @@ interface NotifPipelineChoreographerTestComponent { @BindsInstance @Main executor: DelayableExecutor ): NotifPipelineChoreographerTestComponent } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 8a48fe10d7fc..72d1db3affe8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -46,8 +46,8 @@ import android.os.UserHandle; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -64,7 +64,7 @@ import org.mockito.Mockito; import java.util.ArrayList; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationEntryTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = 0; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt index ab55a7d650c6..1fd6b042ad67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt @@ -20,7 +20,7 @@ import android.os.UserHandle import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection @@ -47,7 +47,7 @@ import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SectionStyleProviderTest : SysuiTestCase() { @Rule @JvmField public val setFlagsRule = SetFlagsRule() @@ -118,4 +118,4 @@ class SectionStyleProviderTest : SysuiTestCase() { override fun getSection(): NotifSection? = NotifSection(inputSectioner, 1) } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt index 4708350c1c0a..2ad3c9e1c21a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt @@ -25,7 +25,7 @@ import android.os.Bundle import android.os.UserHandle import android.service.notification.NotificationListenerService.Ranking import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection @@ -48,7 +48,7 @@ private const val PACKAGE = "pkg" private const val USER_ID = -1 @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class TargetSdkResolverTest : SysuiTestCase() { private val packageManager: PackageManager = mock() private val applicationInfo = ApplicationInfo().apply { targetSdkVersion = SDK_VERSION } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java index b1180aebe9bd..f029a2ca6099 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java @@ -30,8 +30,8 @@ import static java.util.Objects.requireNonNull; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -57,7 +57,7 @@ import java.util.Arrays; import java.util.Collections; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class GroupCoalescerTest extends SysuiTestCase { private GroupCoalescer mCoalescer; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java index f2207afeda10..1f2925528077 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java @@ -30,9 +30,9 @@ import android.app.Person; import android.content.Intent; import android.graphics.Color; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -47,7 +47,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ColorizedFgsCoordinatorTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt index 59fc591e4d06..e72109d4d8e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder @@ -39,7 +39,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class DataStoreCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: DataStoreCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt index f91e5a8cf626..543f0c72a8ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -38,7 +38,7 @@ import org.mockito.Mockito import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DismissibilityCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: DismissibilityCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt index a544cad03b77..4d5ea92aa0e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -49,7 +49,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DreamCoordinatorTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController @Mock private lateinit var notifPipeline: NotifPipeline diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt index 929c3d4288d4..7b688d4f91fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder @@ -36,7 +36,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class GroupCountCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: GroupCountCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt index eac0e296c51f..3f140265e5f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.SbnBuilder @@ -45,7 +45,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class GroupWhenCoordinatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt index a652ad64ea8d..7fe97d27f273 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -42,7 +42,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class GutsCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: GutsCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index cd75e0811fff..8e9323fead92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification.GROUP_ALERT_ALL import android.app.Notification.GROUP_ALERT_SUMMARY -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer @@ -70,7 +70,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class HeadsUpCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: HeadsUpCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java index 27542a462d36..5dcad4bb9e62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java @@ -24,9 +24,9 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.util.SparseArray; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -47,7 +47,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class HideNotifsForOtherUsersCoordinatorTest extends SysuiTestCase { @Mock private NotificationLockscreenUserManager mLockscreenUserManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index 5ff73538b359..25533d82608b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -20,7 +20,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification import android.os.UserHandle import android.provider.Settings -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -67,7 +67,7 @@ import kotlin.time.Duration.Companion.seconds import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class KeyguardCoordinatorTest : SysuiTestCase() { private val headsUpManager: HeadsUpManager = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java index e90a3ac8bfdc..07c29a024a6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java @@ -33,8 +33,8 @@ import android.media.session.MediaSession; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.service.notification.NotificationListenerService; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -58,7 +58,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public final class MediaCoordinatorTest extends SysuiTestCase { private MediaSession mMediaSession; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt index c29ff416feb9..501bca248c76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.platform.test.annotations.EnableFlags import android.service.notification.NotificationListenerService.REASON_CANCEL -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -36,7 +36,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) class NotificationStatsLoggerCoordinatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index cceaaea672c4..80127682be02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -39,10 +39,10 @@ import static java.util.Objects.requireNonNull; import android.database.ContentObserver; import android.os.Handler; import android.os.RemoteException; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -88,7 +88,7 @@ import java.util.List; import java.util.Map; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class PreparationCoordinatorTest extends SysuiTestCase { private NotifCollectionListener mCollectionListener; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index 3d1253e2b05d..c05b13163d32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -35,9 +35,9 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; -import android.testing.AndroidTestingRunner; import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -67,7 +67,7 @@ import java.util.ArrayList; import java.util.Arrays; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt index d3df48e9ef02..deb3fc1224ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt @@ -23,8 +23,8 @@ import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -54,7 +54,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class RemoteInputCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: RemoteInputCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt index 7daadb07f89a..1b7ec5381713 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder @@ -37,7 +37,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class RowAlertTimeCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: RowAlertTimeCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt index a66f8ce1a92c..5b231e21e700 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.AssistantFeedbackController @@ -41,7 +41,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class RowAppearanceCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: RowAppearanceCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt index 56f16f32ec15..ccf7cdd70675 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.service.notification.NotificationListenerService.REASON_APP_CANCEL import android.service.notification.NotificationListenerService.REASON_CANCEL -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer @@ -40,7 +40,7 @@ import java.util.concurrent.Executor import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class ShadeEventCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: ShadeEventCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index ea4f692ef4f1..c7513de7a41a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX @@ -51,7 +51,7 @@ import org.mockito.MockitoAnnotations.initMocks import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class StackCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: StackCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt index b1d2ea21f7fc..c8fbe61fa799 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback @@ -40,7 +40,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class ViewConfigCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: ViewConfigCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt index 8e6ceccbc14e..7943872558c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt @@ -20,8 +20,8 @@ import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING import com.android.systemui.SysuiTestCase @@ -54,7 +54,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotifUiAdjustmentProviderTest : SysuiTestCase() { private val lockscreenUserManager: NotificationLockscreenUserManager = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt index 1cdd023dd01c..d2057703d560 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt @@ -15,9 +15,9 @@ */ package com.android.systemui.statusbar.notification.collection.listbuilder -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Assert.assertFalse @@ -27,7 +27,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class SemiStableSortTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt index 20369546d68a..49f836fe9f81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.listbuilder -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper.getContiguousSubLists @@ -25,7 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class ShadeListBuilderHelperTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt index 22f6bdc54b85..341a51e32a46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection import android.service.notification.NotificationListenerService.RankingMap -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer @@ -34,7 +34,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotifCollectionInconsistencyTrackerTest : SysuiTestCase() { private val logger = spy(NotifCollectionLogger(logcatLogBuffer())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt index a09f3a35c308..99e55a85344a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection import android.os.Handler -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -41,7 +41,7 @@ import java.util.function.Consumer import java.util.function.Predicate @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { private lateinit var extender: TestableSelfTrackingLifetimeExtender diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt index b56f8e9364c4..586b947d5299 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.statusbar.notification.collection.provider -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock @@ -29,7 +29,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class VisualStabilityProviderTest : SysuiTestCase() { private val visualStabilityProvider = VisualStabilityProvider() private val listener: OnReorderingAllowedListener = mock() @@ -148,4 +148,4 @@ class VisualStabilityProviderTest : SysuiTestCase() { visualStabilityProvider.isReorderingAllowed = true verify(selfAddingListener, times(2)).onReorderingAllowed() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt index eeabc744987b..9d3e2e8dfcb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.notification.collection.render import android.content.Context -import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer @@ -34,7 +34,7 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ShadeViewDifferTest : SysuiTestCase() { private lateinit var differ: ShadeViewDiffer private val rootController = FakeController(mContext, "RootController") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt index 2a3c1a53559e..39085295fbac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt @@ -15,7 +15,7 @@ package com.android.systemui.statusbar.notification.domain.interactor -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -25,7 +25,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class SeenNotificationsInteractorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt index 4ac9dc2be161..bfa816e65eb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt @@ -30,8 +30,8 @@ import android.graphics.drawable.Icon import android.os.Bundle import android.os.SystemClock import android.os.UserHandle -import android.testing.AndroidTestingRunner import androidx.test.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any @@ -53,7 +53,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class IconManagerTest : SysuiTestCase() { companion object { private const val TEST_PACKAGE_NAME = "test" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt index b410b33b97d9..c9f2addfd39b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt @@ -15,7 +15,7 @@ package com.android.systemui.statusbar.notification.icon.domain.interactor -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule @@ -52,7 +52,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationIconsInteractorTest : SysuiTestCase() { private val bubbles: Bubbles = mock() @@ -151,7 +151,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() { } @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { private val bubbles: Bubbles = mock() @@ -256,7 +256,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { } @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class StatusBarNotificationIconsInteractorTest : SysuiTestCase() { private val bubbles: Bubbles = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java index 60eea9beb2e0..af2789b8401a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java @@ -26,9 +26,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import androidx.core.os.CancellationSignal; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.NotificationMessagingUtil; @@ -47,7 +47,7 @@ import org.mockito.MockitoAnnotations; import java.util.concurrent.atomic.AtomicReference; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class HeadsUpViewBinderTest extends SysuiTestCase { private HeadsUpViewBinder mViewBinder; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index 86620480fa7b..19214fb831eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -42,9 +42,9 @@ import android.content.Context; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; @@ -86,7 +86,7 @@ import java.util.Map; import java.util.function.Consumer; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { private static final int NOTIF_USER_ID = 0; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 7ade053720e9..3e8461a225b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -60,8 +60,8 @@ import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.testing.UiEventLoggerFake; @@ -96,7 +96,7 @@ import java.util.Set; * Tests for the interruption state provider which understands whether the system & notification * is in a state allowing a particular notification to hun, pulse, or bubble. */ -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index 7ed33126a54f..a6177e8feb1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.interruption import android.platform.test.annotations.DisableFlags -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision @@ -34,7 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() { override val provider by lazy { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index edab9d9f7fdf..1870194f21f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -20,7 +20,7 @@ import android.app.Notification.CATEGORY_EVENT import android.app.Notification.CATEGORY_REMINDER import android.app.NotificationManager import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE @@ -30,7 +30,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() { override val provider by lazy { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java index 2662c80dce1d..59741718476f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java @@ -22,9 +22,9 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.os.RemoteException; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -44,7 +44,7 @@ import org.mockito.MockitoAnnotations; import java.util.Collections; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ExpansionStateLoggerTest extends SysuiTestCase { private static final String NOTIFICATION_KEY = "notin_key"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 111309112f61..a8929a63a812 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -35,9 +35,9 @@ import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; @@ -87,7 +87,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) public class NotificationLoggerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt index 4b0b4b89fad4..3ea7732bdf15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt @@ -21,8 +21,8 @@ import android.app.StatsManager import android.graphics.Bitmap import android.graphics.drawable.Icon import android.stats.sysui.NotificationEnums -import android.testing.AndroidTestingRunner import android.util.StatsEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.assertLogsWtf @@ -46,7 +46,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationMemoryLoggerTest : SysuiTestCase() { @Rule @JvmField val expect = Expect.create() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt index 072a497f1a65..f10a52a73586 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt @@ -24,8 +24,8 @@ import android.content.Intent import android.graphics.Bitmap import android.graphics.drawable.Icon import android.stats.sysui.NotificationEnums -import android.testing.AndroidTestingRunner import android.widget.RemoteViews +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.NotificationUtils @@ -36,7 +36,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationMemoryMeterTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt index 4bb28ae46211..d7dde96baf32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt @@ -3,9 +3,9 @@ package com.android.systemui.statusbar.notification.logging import android.app.Notification import android.graphics.Bitmap import android.graphics.drawable.Icon -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.widget.RemoteViews +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder @@ -17,7 +17,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotificationMemoryViewWalkerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt index 9b9cb8213c91..9f98fd4c8508 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.statusbar.notification.row import android.annotation.ColorInt import android.graphics.Color -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.Utils import com.android.systemui.res.R @@ -34,7 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class ActivatableNotificationViewTest : SysuiTestCase() { private val mContentView: View = mock() @@ -98,4 +98,4 @@ class ActivatableNotificationViewTest : SysuiTestCase() { assertThat(mView.topRoundness).isEqualTo(1f) assertThat(mView.roundableState.hashCode()).isEqualTo(roundableState.hashCode()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt index 0eae5fc209b4..fda5cd286074 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt @@ -20,8 +20,8 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.net.Uri -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.NotificationDrawableConsumer import com.android.systemui.SysuiTestCase @@ -50,7 +50,7 @@ private const val MAX_IMAGE_SIZE = 512 // size of the test drawables in pixels @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class BigPictureIconManagerTest : SysuiTestCase() { private val testDispatcher = StandardTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt index 7dcbd8084594..c5b19ab5862c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt @@ -25,8 +25,8 @@ import android.content.pm.ParceledListSlice import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -47,7 +47,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class ChannelEditorDialogControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index 210b1a7f22f4..e738b616d227 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -21,8 +21,8 @@ import android.app.Notification import android.net.Uri import android.os.UserHandle import android.os.UserHandle.USER_ALL -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.statusbar.IStatusBarService @@ -71,7 +71,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class ExpandableNotificationRowControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java index 9d2f32d77028..1c5f37cc60c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java @@ -31,10 +31,10 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -48,7 +48,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index aa79c23bdf74..7304bd62293d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -46,13 +46,13 @@ import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.DisplayMetrics; import android.view.View; import android.widget.ImageView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -87,7 +87,7 @@ import java.util.List; import java.util.function.Consumer; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class ExpandableNotificationRowTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java index ffb8646942b5..d04d6fc5f593 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java @@ -45,13 +45,13 @@ import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.UiThreadTest; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -72,7 +72,7 @@ import org.mockito.MockitoAnnotations; import java.util.Locale; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @UiThreadTest public class FeedbackInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt index 5e50af39203f..c325791b1f05 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt @@ -20,7 +20,7 @@ import android.app.Flags.FLAG_COMPACT_HEADS_UP_NOTIFICATION import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository @@ -31,7 +31,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class HeadsUpStyleProviderImplTest : SysuiTestCase() { @Rule @JvmField val setFlagsRule = SetFlagsRule() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java index 25172deb67a7..18fd42da78ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -22,11 +22,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.annotation.NonNull; import androidx.core.os.CancellationSignal; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -48,7 +48,7 @@ import java.util.ArrayList; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotifBindPipelineTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt index e38adeb0fcd9..29252b27f6d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.row -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -33,7 +33,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotifInflationErrorManagerTest : SysuiTestCase() { private lateinit var manager: NotifInflationErrorManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java index 20cc01accbc3..8b1c95babe38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java @@ -26,9 +26,9 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.widget.RemoteViews; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -45,7 +45,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotifRemoteViewCacheImplTest extends SysuiTestCase { private NotifRemoteViewCacheImpl mNotifRemoteViewCache; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index 03a84036b4b5..a355cd16ee15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -40,7 +40,6 @@ import android.os.AsyncTask; import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.TypedValue; @@ -49,6 +48,7 @@ import android.view.ViewGroup; import android.widget.RemoteViews; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; @@ -79,7 +79,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @Suppress public class NotificationContentInflaterTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 7332bc34b030..2bb610ac4449 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -20,7 +20,6 @@ import android.annotation.DimenRes import android.content.res.Resources import android.os.UserHandle import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils import android.view.NotificationHeaderView @@ -29,6 +28,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.internal.widget.NotificationActionListLayout @@ -60,7 +60,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotificationContentViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index 97cb11e2f107..be89ab8c5cf8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -65,13 +65,13 @@ import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -103,7 +103,7 @@ import java.util.Optional; import java.util.concurrent.CountDownLatch; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationConversationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 907649b90956..625963f5ec7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -57,12 +57,12 @@ import android.os.Handler; import android.os.UserManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArraySet; import android.view.View; import android.view.accessibility.AccessibilityManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -115,7 +115,7 @@ import java.util.Optional; * Tests for {@link NotificationGutsManager}. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationGutsManagerTest extends SysuiTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index 1b85dfa5a087..0b5f8d5e948c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -31,11 +31,11 @@ import android.os.fakeExecutorHandler import android.os.userManager import android.provider.Settings import android.service.notification.NotificationListenerService.Ranking -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.util.ArraySet import android.view.View import android.view.accessibility.accessibilityManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.MetricsLogger @@ -91,7 +91,7 @@ import org.mockito.invocation.InvocationOnMock /** Tests for [NotificationGutsManager] with the scene container enabled. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @EnableSceneContainer class NotificationGutsManagerWithScenesTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt index 7f9471e5271f..350f90dcbe91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.notification.row -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils import android.view.LayoutInflater import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -34,7 +34,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationGutsTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index 13ced928175e..245a6a0b130c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -55,13 +55,13 @@ import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -85,7 +85,7 @@ import org.mockito.junit.MockitoRule; import java.util.concurrent.CountDownLatch; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index e9290289f7f2..027e899e20df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -29,13 +29,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; import android.view.View; import android.view.ViewGroup; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -49,7 +49,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class NotificationMenuRowTest extends LeakCheckedTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt index 8261c1c4b42e..352b79f50b90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt @@ -22,8 +22,8 @@ import android.database.ContentObserver import android.net.Uri import android.os.Handler import android.provider.Settings.Secure -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -54,7 +54,7 @@ import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotificationSettingsControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java index 4a91cd239d87..22f1e4604bbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java @@ -23,11 +23,11 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableResources; import android.testing.UiThreadTest; import android.util.KeyValueListParser; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -41,7 +41,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @UiThreadTest public class NotificationSnoozeTest extends SysuiTestCase { private static final int RES_DEFAULT = 2; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java index 51665d987888..57b0f3f8d8c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java @@ -41,7 +41,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.SpannableString; import android.view.LayoutInflater; @@ -49,6 +48,7 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -69,7 +69,7 @@ import org.mockito.junit.MockitoRule; import java.util.concurrent.CountDownLatch; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class PartialConversationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index 1534c84fd99a..841cb4a3669b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -34,10 +34,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Log; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -52,7 +52,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class RowContentBindStageTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt index 1c959af6ec3f..53a11989cca0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.notification.row import android.app.Notification import android.app.Person import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -34,7 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class SingleLineConversationViewBinderTest : SysuiTestCase() { private lateinit var notificationBuilder: Notification.Builder diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt index f0fc349777b2..ee819c4df0ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.row import android.app.Notification import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -33,7 +33,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class SingleLineViewBinderTest : SysuiTestCase() { private lateinit var notificationBuilder: Notification.Builder diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt index b67153a842ac..e025d3d36ab1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt @@ -27,9 +27,9 @@ import android.graphics.PorterDuffXfermode import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.core.graphics.drawable.toBitmap +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R @@ -48,7 +48,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper @EnableFlags(AsyncHybridViewInflation.FLAG_NAME) class SingleLineViewInflaterTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt index d46763df8a75..f8533a532d32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.notification.row -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.text.PrecomputedText import android.text.TextPaint import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -29,7 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class TextPrecomputerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt index c9602307216d..0dc871a523ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.row.ui.viewmodel -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepository @@ -31,7 +31,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ActivatableNotificationViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java index a15b4cd37184..ec280a1d6d01 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java @@ -24,10 +24,10 @@ import android.app.Notification; import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.Icon; import android.os.Bundle; -import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -40,7 +40,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class NotificationBigPictureTemplateViewWrapperTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt index fe2971c46c32..9d990b1d7edf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.graphics.drawable.AnimatedImageDrawable -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.internal.widget.CachingIconView @@ -38,7 +38,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() { private lateinit var mRow: ExpandableNotificationRow diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index 2d72c7e0b714..f9a9704334a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.notification.row.wrapper; -import android.testing.AndroidTestingRunner; import android.view.View; import android.widget.RemoteViews; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -33,7 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationCustomViewWrapperTest extends SysuiTestCase { private ExpandableNotificationRow mRow; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt index f26c18b1d197..fc829d53a6b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.graphics.drawable.AnimatedImageDrawable -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.MessagingGroup import com.android.internal.widget.MessagingImageMessage @@ -36,7 +36,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() { private lateinit var mRow: ExpandableNotificationRow diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt index 54eed26adaf3..1ce3bada4609 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.app.PendingIntent import android.app.PendingIntent.CancelListener import android.content.Intent -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils @@ -27,6 +26,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -46,7 +46,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationTemplateViewWrapperTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java index fad85f53a091..d17c8dbcf38d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java @@ -20,11 +20,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; -import android.testing.AndroidTestingRunner; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class NotificationViewWrapperTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt index 59d98c233f99..4c6e25a530a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.shelf.domain.interactor import android.os.PowerManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -41,7 +41,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.isNull import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class NotificationShelfInteractorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt index 917569ca787b..e2fb3ba11a02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.shelf.ui.viewmodel import android.os.PowerManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule @@ -44,7 +44,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class NotificationShelfViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt index fb1594898f24..2349c252369c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -33,7 +33,7 @@ import org.junit.runner.RunWith private const val MAX_PULSE_HEIGHT = 100000f -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class AmbientStateTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt index f4e236e5bbf9..3a77d822eb7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.stack -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R @@ -15,7 +15,7 @@ import org.junit.runner.RunWith * Tests for {@link MediaContainView}. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class MediaContainerViewTest : SysuiTestCase() { @@ -35,4 +35,4 @@ class MediaContainerViewTest : SysuiTestCase() { mediaContainerView.updateClipping() assertTrue(mediaContainerView.clipHeight == 10) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index 3b16f1416935..14bbd38ece2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -21,13 +21,13 @@ import static org.junit.Assert.assertNull; import android.app.Notification; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.NotificationHeaderView; import android.view.View; import android.widget.RemoteViews; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -45,7 +45,7 @@ import org.junit.runner.RunWith; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper //@DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME) public class NotificationChildrenContainerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 745d20dd686b..48e8f88a15c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -1,10 +1,11 @@ package com.android.systemui.statusbar.notification.stack +import android.platform.test.annotations.DisableFlags import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.systemui.SysuiTestCase @@ -34,7 +35,7 @@ import org.mockito.Mockito.`when` as whenever /** Tests for {@link NotificationShelf}. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper open class NotificationShelfTest : SysuiTestCase() { @@ -69,8 +70,8 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME) fun testShadeWidth_BasedOnFractionToShade() { - mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME) setFractionToShade(0f) setOnLockscreen(true) @@ -85,8 +86,8 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME) fun testShelfIsLong_WhenNotOnLockscreen() { - mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME) setFractionToShade(0f) setOnLockscreen(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index f262df1d875a..ce2491bb098e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -42,12 +42,12 @@ import static org.mockito.Mockito.when; import android.metrics.LogMaker; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -126,7 +126,7 @@ import javax.inject.Provider; */ @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 0c0a2a59d9bc..f461e2f67d20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -54,7 +54,6 @@ import android.graphics.Rect; import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; import android.util.MathUtils; @@ -65,6 +64,7 @@ import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; @@ -114,7 +114,7 @@ import java.util.function.Consumer; * Tests for {@link NotificationStackScrollLayout}. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationStackScrollLayoutTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index 6fec9ad7bd66..dae5542123ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.DimenRes import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.view.View.VISIBLE +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -44,7 +44,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationStackSizeCalculatorTest : SysuiTestCase() { @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 85a2bdd21073..2d119174efff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -37,12 +37,12 @@ import android.animation.Animator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.os.Handler; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -70,7 +70,7 @@ import java.util.stream.Collectors; * Tests for {@link NotificationSwipeHelper}. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper() public class NotificationSwipeHelperTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt index e30947ce84bd..660eb308fdf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.stack -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags @@ -15,7 +15,7 @@ import org.junit.runner.RunWith /** Tests for {@link NotificationTargetsHelper}. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationTargetsHelperTest : SysuiTestCase() { private val featureFlags = FakeFeatureFlags() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt index 926c35f32967..798465e7b165 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.stack import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule @@ -48,7 +48,7 @@ private const val FULL_SHADE_APPEAR_TRANSLATION = 300 private const val HEADS_UP_ABOVE_SCREEN = 80 @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class StackStateAnimatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt index cd6bb5f4966a..e493420b64a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.assertDoesNotLogWtf @@ -27,7 +27,7 @@ import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ViewStateTest : SysuiTestCase() { private val viewState = ViewState() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt index e2ac2038be32..e46906fb5192 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt @@ -17,10 +17,10 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import android.content.res.Configuration import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.view.Surface import android.view.Surface.ROTATION_0 import android.view.Surface.ROTATION_90 +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl @@ -52,7 +52,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) open class HideNotificationsInteractorTest : SysuiTestCase() { private val testScope = TestScope() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 84cd518cf85a..f0bc655a554d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -45,10 +45,10 @@ import android.hardware.display.ColorDisplayManager; import android.hardware.display.NightDisplayListener; import android.os.Handler; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -89,7 +89,7 @@ import java.util.List; import javax.inject.Named; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class AutoTileManagerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index dc7525c8e256..285949a41fcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -37,11 +37,11 @@ import android.hardware.biometrics.BiometricSourceType; import android.os.Handler; import android.os.PowerManager; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableResources; import android.view.ViewRootImpl; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -77,7 +77,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class BiometricsUnlockControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index fe6a88d00374..5675915506a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -30,9 +30,9 @@ import android.content.ComponentName; import android.os.PowerManager; import android.os.UserHandle; import android.os.Vibrator; -import android.testing.AndroidTestingRunner; import android.view.HapticFeedbackConstants; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -71,7 +71,7 @@ import org.mockito.stubbing.Answer; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private CentralSurfaces mCentralSurfaces; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index b9312d3ce2be..f3f16f7d59f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -21,6 +21,7 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED; import static android.provider.Settings.Global.HEADS_UP_ON; +import static com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR; import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE; import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; @@ -70,9 +71,10 @@ import android.os.IThermalService; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.service.dreams.IDreamManager; import android.support.test.metricshelper.MetricsAsserts; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.DisplayMetrics; @@ -82,6 +84,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowMetrics; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.compose.animation.scene.ObservableTransitionState; @@ -105,8 +108,6 @@ import com.android.systemui.charging.WiredChargingRippleController; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.communal.data.repository.CommunalRepository; -import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.communal.shared.model.CommunalScenes; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; @@ -222,8 +223,9 @@ import java.util.Optional; import javax.inject.Provider; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) +@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION) public class CentralSurfacesImplTest extends SysuiTestCase { private static final int FOLD_STATE_FOLDED = 0; @@ -238,8 +240,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private final TestScope mTestScope = mKosmos.getTestScope(); - private final CommunalInteractor mCommunalInteractor = mKosmos.getCommunalInteractor(); - private final CommunalRepository mCommunalRepository = mKosmos.getCommunalRepository(); @Mock private NotificationsController mNotificationsController; @Mock private LightBarController mLightBarController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -362,13 +362,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // Set default value to avoid IllegalStateException. mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION); // Turn AOD on and toggle feature flag for jank fixes mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true); when(mDozeParameters.getAlwaysOn()).thenReturn(true); - if (!SceneContainerFlag.isEnabled()) { - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); - } IThermalService thermalService = mock(IThermalService.class); mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService, @@ -528,7 +524,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mScreenLifecycle, mWakefulnessLifecycle, mPowerInteractor, - mCommunalInteractor, + mKosmos.getCommunalInteractor(), mStatusBarStateController, Optional.of(mBubbles), () -> mNoteTaskController, @@ -837,6 +833,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testSetDozingNotUnlocking_transitionToAuthScrimmed_cancelKeyguardFadingAway() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true); @@ -848,7 +845,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - public void testOccludingQSNotExpanded_transitionToAuthScrimmed() { + @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + public void testOccludingQSNotExpanded_flagOff_transitionToAuthScrimmed() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); // GIVEN device occluded and panel is NOT expanded @@ -862,6 +860,39 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + public void testNotOccluding_QSNotExpanded_flagOn_doesNotTransitionScrimState() { + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // GIVEN device occluded and panel is NOT expanded + mCentralSurfaces.setBarStateForTest(SHADE); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false); + + mCentralSurfaces.updateScrimController(); + + // Tests the safeguard to reset the scrimstate + verify(mScrimController, never()).transitionTo(any()); + } + + @Test + @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + public void testNotOccluding_QSExpanded_flagOn_doesTransitionScrimStateToKeyguard() { + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // GIVEN device occluded and panel is NOT expanded + mCentralSurfaces.setBarStateForTest(SHADE); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true); + + mCentralSurfaces.updateScrimController(); + + // Tests the safeguard to reset the scrimstate + verify(mScrimController, never()).transitionTo(eq(ScrimState.KEYGUARD)); + } + + @Test + @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testOccludingQSExpanded_transitionToAuthScrimmedShade() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); @@ -878,16 +909,18 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Test public void testEnteringGlanceableHub_updatesScrim() { // Transition to the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( - CommunalScenes.Communal))); + mKosmos.getCommunalRepository() + .setTransitionState( + flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal))); mTestScope.getTestScheduler().runCurrent(); // ScrimState also transitions. verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB); // Transition away from the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( - CommunalScenes.Blank))); + mKosmos.getCommunalRepository() + .setTransitionState( + flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank))); mTestScope.getTestScheduler().runCurrent(); // ScrimState goes back to UNLOCKED. @@ -901,16 +934,18 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true); // Transition to the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( - CommunalScenes.Communal))); + mKosmos.getCommunalRepository() + .setTransitionState( + flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal))); mTestScope.getTestScheduler().runCurrent(); // ScrimState also transitions. verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); // Transition away from the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( - CommunalScenes.Blank))); + mKosmos.getCommunalRepository() + .setTransitionState( + flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank))); mTestScope.getTestScheduler().runCurrent(); // ScrimState goes back to UNLOCKED. @@ -1105,18 +1140,16 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX) public void updateResources_flagEnabled_doesNotUpdateStatusBarWindowHeight() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX); - mCentralSurfaces.updateResources(); verify(mStatusBarWindowController, never()).refreshStatusBarHeight(); } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX) public void updateResources_flagDisabled_updatesStatusBarWindowHeight() { - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX); - mCentralSurfaces.updateResources(); verify(mStatusBarWindowController).refreshStatusBarHeight(); @@ -1151,10 +1184,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotDismissAny() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1163,10 +1196,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_largeScreen_newFlagsDisabled_dismissesTabletVersion() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1175,10 +1208,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_largeScreen_bothFlagsDisabled_dismissesPhoneVersion() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1188,10 +1221,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotDismissAny() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1200,10 +1233,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_smallScreen_newFlagsDisabled_dismissesPhoneVersion() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1213,10 +1246,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void dismissKeyboardShortcuts_smallScreen_bothFlagsDisabled_dismissesPhoneVersion() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); dismissKeyboardShortcuts(); @@ -1226,10 +1259,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotTogglesAny() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 321; @@ -1239,10 +1272,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_largeScreen_newFlagsDisabled_togglesTabletVersion() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 654; @@ -1253,10 +1286,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_largeScreen_bothFlagsDisabled_togglesPhoneVersion() { switchToLargeScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 987; @@ -1267,10 +1300,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotToggleAny() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 789; @@ -1280,10 +1313,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_smallScreen_newFlagsDisabled_togglesPhoneVersion() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 456; @@ -1294,10 +1327,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) public void toggleKeyboardShortcuts_smallScreen_bothFlagsDisabled_togglesPhoneVersion() { switchToSmallScreen(); mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); createCentralSurfaces(); int deviceId = 123; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt index 56d23978a5c7..942ea65ec49e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt @@ -21,7 +21,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_TYPE_CAR import android.os.LocaleList -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener @@ -36,7 +36,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import java.util.Locale -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ConfigurationControllerImplTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java index 34c43ef52a00..3b3ec263145a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java @@ -20,9 +20,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -36,7 +36,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class DozeScrimControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt index 5d42d5167c27..a3e2d1949a29 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.phone import android.hardware.devicestate.DeviceState -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -30,7 +30,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class FoldStateListenerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index 3e9006e5268c..0d06b6431e1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -26,12 +26,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -62,7 +62,7 @@ import org.junit.runner.RunWith; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class HeadsUpAppearanceControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index fd295b57379c..cf87afbbff34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -26,8 +26,10 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; import android.content.res.Resources; -import android.testing.AndroidTestingRunner; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.Flags; @@ -48,7 +50,7 @@ import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { private static final int SCREEN_HEIGHT = 2000; private static final int EMPTY_HEIGHT = 0; @@ -297,8 +299,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { } @Test + @DisableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() { - mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX); int keyguardSplitShadeTopMargin = 100; int largeScreenHeaderHeightResource = 70; when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) @@ -316,8 +318,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() { - mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX); int keyguardSplitShadeTopMargin = 100; int largeScreenHeaderHeightHelper = 50; int largeScreenHeaderHeightResource = 70; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java index b0aa2d3934cc..d880becaa4bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java @@ -20,8 +20,8 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class KeyguardDismissUtilTest extends SysuiTestCase { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java index 5cea931e2070..109cd948b5b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java @@ -21,10 +21,10 @@ import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE; import static com.google.common.truth.Truth.assertThat; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardIndicationTextViewTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index dfee7374c104..71f09a5d3f04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -42,11 +42,11 @@ import android.os.UserManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.CarrierTextController; @@ -71,7 +71,6 @@ import com.android.systemui.res.R; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository; import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor; @@ -100,7 +99,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Mock @@ -144,8 +143,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Mock private SecureSettings mSecureSettings; @Mock private CommandQueue mCommandQueue; @Mock private KeyguardLogger mLogger; - - @Mock private NotificationMediaManager mNotificationMediaManager; @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory; private TestShadeViewStateProvider mShadeViewStateProvider; @@ -225,7 +222,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mFakeExecutor, mBackgroundExecutor, mLogger, - mNotificationMediaManager, mStatusOverlayHoverListenerFactory ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java index c44f979fa971..0932a0c9307c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java @@ -18,11 +18,11 @@ package com.android.systemui.statusbar.phone; import static com.google.common.truth.Truth.assertThat; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -33,7 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardStatusBarViewTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java index f91064b49e95..782ca91bc8fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.Display; import android.view.View; @@ -35,6 +34,7 @@ import android.view.WindowInsets; import android.view.WindowManager; import androidx.lifecycle.Observer; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -54,7 +54,7 @@ import org.mockito.MockitoAnnotations; import java.util.Objects; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) public class LegacyLightsOutNotifControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java index 9d53b9c66b33..fea0e72fe577 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java @@ -21,9 +21,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.Flags; @@ -49,7 +49,7 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME) public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt index e7b287c5bdc8..518b327036cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.phone import android.graphics.Color import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.view.WindowInsetsController import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion @@ -36,7 +36,7 @@ import org.mockito.Mock import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LetterboxAppearanceCalculatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt index 1cc0bd3cb36c..788c2cb2a485 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt @@ -21,8 +21,8 @@ import android.app.WallpaperManager.OnColorsChangedListener import android.graphics.Color import android.os.Handler import android.os.Looper -import android.testing.AndroidTestingRunner import android.view.IWindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -40,7 +40,7 @@ import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LetterboxBackgroundProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index 7271a5efc377..a27073c77eb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -35,10 +35,10 @@ import static org.mockito.Mockito.when; import android.graphics.Color; import android.graphics.Rect; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.annotation.ColorInt; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor.GradientColors; @@ -67,7 +67,7 @@ import java.util.Arrays; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class LightBarControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java index f71114d92aa3..43c19b833646 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java @@ -28,9 +28,9 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.policy.GestureNavigationSettingsObserver; @@ -48,7 +48,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class LightBarTransitionsControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt index 9f4e1dd8cc4d..9d97e5a5686e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.phone import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.StatusBarIconView @@ -34,7 +34,7 @@ import org.mockito.Mockito.`when` as whenever /** Tests for {@link NotificationIconContainer}. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationIconContainerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java index ccd1a8c7a9b2..9522e1f65a4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java @@ -22,11 +22,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Resources; -import android.testing.AndroidTestingRunner; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -42,7 +42,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationTapHelperTest extends SysuiTestCase { private NotificationTapHelper mNotificationTapHelper; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index 8d2c1588fb62..f2f336c45d7f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt @@ -22,9 +22,9 @@ import android.app.admin.DevicePolicyResourcesManager import android.content.SharedPreferences import android.os.UserManager import android.telecom.TelecomManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -79,7 +79,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @OptIn(ExperimentalCoroutinesApi::class) @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 1000329cb276..416a869bf2f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -49,12 +49,12 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Color; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.ViewUtils; import android.util.MathUtils; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor.GradientColors; @@ -107,7 +107,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ScrimControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt index 61da701ce971..b9cfe21dcad3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt @@ -17,10 +17,10 @@ package com.android.systemui.statusbar.phone import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener @@ -37,7 +37,7 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class StatusBarBoundsProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 6b3c0053e738..3ca4c594cede 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -41,7 +41,6 @@ import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.service.trust.TrustAgentService; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; import android.view.View; @@ -55,6 +54,7 @@ import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.LatencyTracker; @@ -119,7 +119,7 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 269510e0b4b2..9fa392f3a337 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -51,9 +51,9 @@ import android.os.RemoteException; import android.os.UserHandle; import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.jank.InteractionJankMonitor; @@ -119,7 +119,7 @@ import java.util.List; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index a8c5fc357c7c..95472cad4b90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -34,10 +34,10 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.InitController; @@ -85,7 +85,7 @@ import java.util.List; import java.util.Set; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper() public class StatusBarNotificationPresenterTest extends SysuiTestCase { private StatusBarNotificationPresenter mStatusBarNotificationPresenter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java index 929099a8f1f7..35888a5fa734 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java @@ -24,10 +24,10 @@ import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; import android.content.Intent; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -52,7 +52,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { @Mock private DeviceProvisionedController mDeviceProvisionedController; @@ -103,4 +103,4 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).showBouncer(true); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt index 1455693fc54b..11dd587a04ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt @@ -21,7 +21,6 @@ import android.graphics.Color import android.graphics.drawable.Drawable import android.graphics.drawable.PaintDrawable import android.os.SystemClock -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils @@ -30,6 +29,7 @@ import android.view.View import android.view.ViewGroupOverlay import android.widget.LinearLayout import androidx.annotation.ColorInt +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -45,7 +45,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class StatusOverlayHoverListenerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt index dedd0afb6127..b560c591af1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt @@ -15,9 +15,9 @@ package com.android.systemui.statusbar.phone import android.app.Dialog import android.content.res.Configuration -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.WindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.testScope @@ -39,7 +39,7 @@ import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class SystemUIBottomSheetDialogTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index c8ff20b31aae..624c070e95e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.phone import android.os.Handler import android.os.PowerManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase @@ -51,7 +51,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 66211c922abb..fdf77ae87011 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -426,7 +426,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.GONE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); } @Test @@ -438,7 +438,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.VISIBLE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } @@ -452,7 +452,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); assertEquals(View.GONE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); } @Test @@ -465,7 +465,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.GONE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); } @Test @@ -477,21 +477,21 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.VISIBLE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); // Ongoing call ended when(mOngoingCallController.hasOngoingCall()).thenReturn(false); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.GONE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); // Ongoing call started when(mOngoingCallController.hasOngoingCall()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.VISIBLE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index 05464f3b715a..4d6798be9211 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -106,7 +106,7 @@ class OngoingCallControllerTest : SysuiTestCase() { fun setUp() { allowTestableLooperAsMainThread() TestableLooper.get(this).runWithLooper { - chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null) + chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null) } MockitoAnnotations.initMocks(this) @@ -206,7 +206,7 @@ class OngoingCallControllerTest : SysuiTestCase() { View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth) + assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) .isEqualTo(0) } @@ -222,7 +222,7 @@ class OngoingCallControllerTest : SysuiTestCase() { View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth) + assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) .isGreaterThan(0) } @@ -237,7 +237,7 @@ class OngoingCallControllerTest : SysuiTestCase() { View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth) + assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) .isGreaterThan(0) } @@ -472,7 +472,10 @@ class OngoingCallControllerTest : SysuiTestCase() { lateinit var newChipView: View TestableLooper.get(this).runWithLooper { - newChipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null) + newChipView = LayoutInflater.from(mContext).inflate( + R.layout.ongoing_activity_chip, + null + ) } // Change the chip view associated with the controller. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 598b12ccdc38..eb2538ec032e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -20,6 +20,7 @@ import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.demomode.DemoMode @@ -60,7 +61,6 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -73,7 +73,7 @@ import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class MobileRepositorySwitcherTest : SysuiTestCase() { private lateinit var underTest: MobileRepositorySwitcher private lateinit var realRepo: MobileConnectionsRepositoryImpl diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt index 265440154d77..237aabccfbd9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod import android.telephony.TelephonyManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -44,7 +44,7 @@ import org.mockito.MockitoAnnotations @SmallTest @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: CarrierMergedConnectionRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 36df61d287a1..96e599f8f4d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.annotation.SuppressLint import android.content.Intent import android.net.ConnectivityManager import android.net.Network @@ -27,8 +28,10 @@ import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.net.wifi.WifiManager +import android.os.Bundle import android.os.ParcelUuid import android.telephony.CarrierConfigManager +import android.telephony.ServiceState import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID @@ -53,6 +56,7 @@ import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository @@ -595,6 +599,51 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(mobileRepo.getIsCarrierMerged()).isFalse() } + @SuppressLint("UnspecifiedRegisterReceiverFlag") + @Test + fun testDeviceServiceStateFromBroadcast_eagerlyWatchesBroadcast() = + testScope.runTest { + // Value starts out empty (null) + assertThat(underTest.deviceServiceState.value).isNull() + + // WHEN an appropriate intent gets sent out + val intent = serviceStateIntent(subId = -1, emergencyOnly = false) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + intent, + ) + runCurrent() + + // THEN the repo's state is updated + val expected = ServiceStateModel(isEmergencyOnly = false) + assertThat(underTest.deviceServiceState.value).isEqualTo(expected) + } + + @Test + fun testDeviceServiceStateFromBroadcast_followsSubIdNegativeOne() = + testScope.runTest { + // device based state tracks -1 + val intent = serviceStateIntent(subId = -1, emergencyOnly = false) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + intent, + ) + runCurrent() + + val deviceBasedState = ServiceStateModel(isEmergencyOnly = false) + assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState) + + // ... and ignores any other subId + val intent2 = serviceStateIntent(subId = 1, emergencyOnly = true) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + intent2, + ) + runCurrent() + + assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState) + } + @Test @Ignore("b/333912012") fun testConnectionCache_clearsInvalidSubscriptions() = @@ -1491,5 +1540,24 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE) whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true) } + + /** + * To properly mimic telephony manager, create a service state, and then turn it into an + * intent + */ + private fun serviceStateIntent( + subId: Int, + emergencyOnly: Boolean = false, + ): Intent { + val serviceState = ServiceState().apply { isEmergencyOnly = emergencyOnly } + + val bundle = Bundle() + serviceState.fillInNotifierBundle(bundle) + + return Intent(Intent.ACTION_SERVICE_STATE).apply { + putExtras(bundle) + putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId) + } + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 0f9cbfa66b5b..58d9ee3935fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository @@ -888,6 +889,22 @@ class MobileIconsInteractorTest : SysuiTestCase() { assertThat(interactor1).isSameInstanceAs(interactor2) } + @Test + fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() = + testScope.runTest { + val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode) + + connectionsRepository.deviceServiceState.value = + ServiceStateModel(isEmergencyOnly = true) + + assertThat(latest).isTrue() + + connectionsRepository.deviceServiceState.value = + ServiceStateModel(isEmergencyOnly = false) + + assertThat(latest).isFalse() + } + /** * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions * flow. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt index 405e3ed807d5..d303976612c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt @@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest import com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.log.core.FakeLogBuffer import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository @@ -71,6 +72,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) } @@ -114,6 +116,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) val latest by collectLastValue(underTest.isSatelliteAllowed) @@ -162,6 +165,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) val latest by collectLastValue(underTest.connectionState) @@ -218,6 +222,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) val latest by collectLastValue(underTest.signalStrength) @@ -238,25 +243,97 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_noConnections_yes() = + fun areAllConnectionsOutOfService_noConnections_noDeviceEmergencyCalls_yes() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) // GIVEN, 0 connections + // GIVEN, device is not in emergency calls only mode + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false + // THEN the value is propagated to this interactor assertThat(latest).isTrue() } @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_yes() = + fun areAllConnectionsOutOfService_noConnections_deviceEmergencyCalls_yes() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 0 connections + + // GIVEN, device is in emergency calls only mode + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true + + // THEN the value is propagated to this interactor + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_noDeviceEmergencyCalls_yes() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 1 connections + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + // GIVEN, no device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false + + // WHEN connection is in service + i1.isInService.value = true + i1.isEmergencyOnly.value = false + i1.isNonTerrestrial.value = false + + // THEN we are considered NOT to be OOS + assertThat(latest).isFalse() + + // WHEN the connection disappears + iconsInteractor.icons.value = listOf() + + // THEN we are back to OOS + assertThat(latest).isTrue() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_deviceEmergencyCalls_no() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 1 connections + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + // GIVEN, device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true + + // WHEN one connection is in service + i1.isInService.value = true + i1.isEmergencyOnly.value = false + i1.isNonTerrestrial.value = false + + // THEN we are considered NOT to be OOS + assertThat(latest).isFalse() + + // WHEN the connection disappears + iconsInteractor.icons.value = listOf() + + // THEN we are still NOT in OOS, due to device-based emergency calls + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_noDeviceEmergencyCalls_yes() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) // GIVEN, 2 connections val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) + // GIVEN, no device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false // WHEN all of the connections are OOS and none are NTN i1.isInService.value = false @@ -272,13 +349,39 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_twoConnectionsOos_oneNtn_no() = + fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_deviceEmergencyCalls_no() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 2 connections + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) + // GIVEN, device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true + + // WHEN all of the connections are OOS and none are NTN + i1.isInService.value = false + i1.isEmergencyOnly.value = false + i1.isNonTerrestrial.value = false + i2.isInService.value = false + i2.isEmergencyOnly.value = false + i2.isNonTerrestrial.value = false + + // THEN we are not considered OOS due to device based emergency calling + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_twoConnectionsOos_noDeviceEmergencyCalls_oneNtn_no() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) // GIVEN, 2 connections val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) + // GIVEN, no device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false // WHEN all of the connections are OOS and one is NTN i1.isInService.value = false @@ -296,12 +399,14 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_yes() = + fun areAllConnectionsOutOfService_oneConnectionOos_noDeviceEmergencyCalls_nonNtn_yes() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) // GIVEN, 1 connection val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + // GIVEN, no device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false // WHEN all of the connections are OOS i1.isInService.value = false @@ -314,7 +419,27 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_oneConnectionOos_ntn_yes() = + fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_no() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 1 connection + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + // GIVEN, device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true + + // WHEN all of the connections are OOS + i1.isInService.value = false + i1.isEmergencyOnly.value = false + i1.isNonTerrestrial.value = false + + // THEN the value is propagated to this interactor + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_oneConnectionOos_ntn_no() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) @@ -416,6 +541,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) val latest by collectLastValue(underTest.areAllConnectionsOutOfService) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt index ceaae9e02e87..43b95688729c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt @@ -75,6 +75,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt index d1c38f6cbea7..0a5e63085b9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt @@ -22,6 +22,7 @@ import android.graphics.Bitmap import android.os.UserHandle import android.view.View import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.qs.user.UserSwitchDialogController @@ -33,14 +34,13 @@ import java.lang.ref.WeakReference import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class BaseUserSwitcherAdapterTest : SysuiTestCase() { @Mock private lateinit var controller: UserSwitcherController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt index fb4ccb52929a..c22c62825d04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt @@ -20,8 +20,8 @@ import android.content.ComponentName import android.content.Context import android.content.pm.ServiceInfo import android.provider.Settings -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R @@ -60,7 +60,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyObject @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DeviceControlsControllerImplTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index 2955162f80c2..f6e07d3d621e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -29,10 +29,10 @@ import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.os.UserHandle; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableContentResolver; import android.testing.TestableResources; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -51,7 +51,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt index 1c54263cb0ce..80cc6eca8405 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt @@ -20,8 +20,8 @@ import android.content.pm.PackageManager import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraManager import android.hardware.camera2.impl.CameraMetadataNative +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dump.DumpManager @@ -46,7 +46,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class FlashlightControllerImplTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt index 0bd6a685708b..9f74915b4d1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.statusbar.policy import android.content.Context import android.content.pm.UserInfo import android.graphics.Bitmap -import android.testing.AndroidTestingRunner import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.UserIcons import com.android.systemui.res.R @@ -44,7 +44,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt index b03edaf8ebd5..4b14e642063a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt @@ -23,7 +23,7 @@ import android.content.pm.PackageManager import android.net.Uri import android.os.Handler import android.safetycenter.SafetyCenterManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any @@ -44,7 +44,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SafetyControllerTest : SysuiTestCase() { private val TEST_PC_PKG = "testPermissionControllerPackageName" @@ -188,4 +188,4 @@ class SafetyControllerTest : SysuiTestCase() { assertThat(called).isTrue() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt index 3e20f689569e..81f095041fbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt @@ -22,7 +22,7 @@ import android.media.projection.MediaProjectionManager import android.os.Handler import android.platform.test.annotations.DisableFlags import android.telephony.TelephonyManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.server.notification.Flags import com.android.systemui.SysuiTestCase @@ -38,7 +38,7 @@ import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @DisableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING) class SensitiveNotificationProtectionControllerFlagDisabledTest : SysuiTestCase() { private val logger = SensitiveNotificationProtectionControllerLogger(logcatLogBuffer()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt index dbc2e3471c28..0249ab8fc9eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.policy import android.service.quickaccesswallet.QuickAccessWalletClient -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -35,7 +35,7 @@ import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class WalletControllerImplTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt index 3dee093bd594..96c6eb8107df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt @@ -887,6 +887,46 @@ class UserSwitcherInteractorTest : SysuiTestCase() { } @Test + fun removeGuestUser_shouldNotShowExitGuestDialog() { + createUserInteractor() + testScope.runTest { + val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true) + userRepository.setUserInfos(listOf(userInfo, guestUserInfo)) + userRepository.setSelectedUserInfo(guestUserInfo) + + whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true) + underTest.removeGuestUser(guestUserInfo.id, userInfo.id) + runCurrent() + + verify(manager).markGuestForDeletion(guestUserInfo.id) + verify(activityManager).switchUser(userInfo.id) + assertThat(collectLastValue(underTest.dialogShowRequests)()).isNull() + } + } + + @Test + fun resetGuestUser_shouldNotShowExitGuestDialog() { + createUserInteractor() + testScope.runTest { + val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true) + val otherGuestUserInfo = createUserInfos(count = 1, includeGuest = true)[0] + userRepository.setUserInfos(listOf(userInfo, guestUserInfo)) + userRepository.setSelectedUserInfo(guestUserInfo) + + whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true) + whenever(manager.createGuest(any())).thenReturn(otherGuestUserInfo) + underTest.removeGuestUser(guestUserInfo.id, UserHandle.USER_NULL) + runCurrent() + + verify(manager).markGuestForDeletion(guestUserInfo.id) + verify(manager).createGuest(any()) + verify(activityManager).switchUser(otherGuestUserInfo.id) + assertThat(collectLastValue(underTest.dialogShowRequests)()) + .isEqualTo(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true)) + } + } + + @Test fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() { createUserInteractor() testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt index 31848a67698c..e1dcb145f20b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.util import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.wm.shell.common.FloatingContentCoordinator @@ -14,7 +14,7 @@ import org.junit.Test import org.junit.runner.RunWith @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class FloatingContentCoordinatorTest : SysuiTestCase() { @@ -198,12 +198,11 @@ class FloatingContentCoordinatorTest : SysuiTestCase() { } /** - * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a - * Rect when needed. + * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a Rect when + * needed. */ - inner class FloatingRect( - private val underlyingRect: Rect - ) : FloatingContentCoordinator.FloatingContent { + inner class FloatingRect(private val underlyingRect: Rect) : + FloatingContentCoordinator.FloatingContent { override fun moveToBounds(bounds: Rect) { underlyingRect.set(bounds) } @@ -216,4 +215,4 @@ class FloatingContentCoordinatorTest : SysuiTestCase() { return underlyingRect } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt index 436f5b827ec6..457f2bb5d826 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt @@ -19,12 +19,13 @@ package com.android.systemui.util import android.content.BroadcastReceiver import android.content.IntentFilter import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.lifecycle.Observer +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import java.util.concurrent.Executor import org.junit.After import org.junit.Assert.assertTrue import org.junit.Before @@ -38,10 +39,9 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -import java.util.concurrent.Executor @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class RingerModeLiveDataTest : SysuiTestCase() { @@ -52,16 +52,11 @@ class RingerModeLiveDataTest : SysuiTestCase() { private val INTENT = "INTENT" } - @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock - private lateinit var valueSupplier: () -> Int - @Mock - private lateinit var observer: Observer<Int> - @Captor - private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> - @Captor - private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter> + @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var valueSupplier: () -> Int + @Mock private lateinit var observer: Observer<Int> + @Captor private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> + @Captor private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter> // Run everything immediately private val executor = Executor { it.run() } @@ -88,14 +83,14 @@ class RingerModeLiveDataTest : SysuiTestCase() { fun testOnActive_broadcastRegistered() { liveData.observeForever(observer) verify(broadcastDispatcher) - .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any()) + .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any()) } @Test fun testOnActive_intentFilterHasIntent() { liveData.observeForever(observer) - verify(broadcastDispatcher).registerReceiver(any(), capture(intentFilterCaptor), any(), - any(), anyInt(), any()) + verify(broadcastDispatcher) + .registerReceiver(any(), capture(intentFilterCaptor), any(), any(), anyInt(), any()) assertTrue(intentFilterCaptor.value.hasAction(INTENT)) } @@ -111,4 +106,4 @@ class RingerModeLiveDataTest : SysuiTestCase() { liveData.removeObserver(observer) verify(broadcastDispatcher).unregisterReceiver(any()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt index b13cb72dc944..6271904b2f04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.util import android.app.WallpaperInfo import android.app.WallpaperManager import android.os.IBinder -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.view.ViewRootImpl +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.eq @@ -32,36 +32,30 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.anyFloat import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.doThrow -import org.mockito.Mockito.times -import org.mockito.Mockito.verify import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever +import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @SmallTest class WallpaperControllerTest : SysuiTestCase() { - @Mock - private lateinit var wallpaperManager: WallpaperManager - @Mock - private lateinit var root: View - @Mock - private lateinit var viewRootImpl: ViewRootImpl - @Mock - private lateinit var windowToken: IBinder + @Mock private lateinit var wallpaperManager: WallpaperManager + @Mock private lateinit var root: View + @Mock private lateinit var viewRootImpl: ViewRootImpl + @Mock private lateinit var windowToken: IBinder private val wallpaperRepository = FakeWallpaperRepository() - @JvmField - @Rule - val mockitoRule = MockitoJUnit.rule() + @JvmField @Rule val mockitoRule = MockitoJUnit.rule() private lateinit var wallaperController: WallpaperController @@ -92,9 +86,7 @@ class WallpaperControllerTest : SysuiTestCase() { @Test fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() { - wallpaperRepository.wallpaperInfo.value = createWallpaperInfo( - useDefaultTransition = false - ) + wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(useDefaultTransition = false) wallaperController.setUnfoldTransitionZoom(0.5f) @@ -130,7 +122,8 @@ class WallpaperControllerTest : SysuiTestCase() { @Test fun setNotificationZoom_exceptionWhenUpdatingZoom_doesNotFail() { - doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager) + doThrow(IllegalArgumentException("test exception")) + .`when`(wallpaperManager) .setWallpaperZoomOut(any(), anyFloat()) wallaperController.setNotificationShadeZoom(0.5f) @@ -140,8 +133,7 @@ class WallpaperControllerTest : SysuiTestCase() { private fun createWallpaperInfo(useDefaultTransition: Boolean = true): WallpaperInfo { val info = mock(WallpaperInfo::class.java) - whenever(info.shouldUseDefaultUnfoldTransition()) - .thenReturn(useDefaultTransition) + whenever(info.shouldUseDefaultUnfoldTransition()).thenReturn(useDefaultTransition) return info } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt index 92afb038b321..b26598c80478 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt @@ -16,13 +16,16 @@ package com.android.systemui.util.animation +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat -import org.junit.Test import java.lang.IllegalArgumentException +import org.junit.runner.RunWith +import org.junit.Test @SmallTest +@RunWith(AndroidJUnit4::class) class AnimationUtilTest : SysuiTestCase() { @Test fun getMsForFrames_5frames_returns83() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java index 9dfa14dd0784..7dfac0ab2650 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java @@ -22,8 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -39,7 +38,7 @@ import java.util.ArrayList; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class FakeExecutorTest extends SysuiTestCase { @Before public void setUp() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java index 78fc6803ea7e..48fb74514b01 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java @@ -24,8 +24,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -39,7 +38,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class MessageRouterImplTest extends SysuiTestCase { private static final int MESSAGE_A = 0; private static final int MESSAGE_B = 1; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt index 15032dc182d6..7ec420f0b6da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.util.concurrency -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.time.FakeSystemClock @@ -26,7 +26,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MockExecutorHandlerTest : SysuiTestCase() { /** Test FakeExecutor that receives non-delayed items to execute. */ @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java index 00f37ae6f6cb..13fff29d89ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java @@ -18,8 +18,7 @@ package com.android.systemui.util.concurrency; import static com.google.common.truth.Truth.assertThat; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -30,7 +29,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class RepeatableExecutorTest extends SysuiTestCase { private static final int DELAY = 100; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java index b367a603ec67..37015e30781e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java @@ -23,8 +23,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.CoreStartable; @@ -44,7 +43,7 @@ import java.util.HashSet; import java.util.Set; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ConditionalCoreStartableTest extends SysuiTestCase { public static class FakeConditionalCoreStartable extends ConditionalCoreStartable { interface Callback { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt index ac357ea34be0..b8f581574848 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt @@ -4,7 +4,7 @@ import android.content.res.Resources import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ShapeDrawable -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -12,7 +12,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class DrawableSizeTest : SysuiTestCase() { @@ -32,14 +32,11 @@ class DrawableSizeTest : SysuiTestCase() { @Test fun testDownscaleToSize_drawableSmallerThanRequirement_unchanged() { - val drawable = BitmapDrawable(resources, - Bitmap.createBitmap( - resources.displayMetrics, - 150, - 150, - Bitmap.Config.ARGB_8888 - ) - ) + val drawable = + BitmapDrawable( + resources, + Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888) + ) val result = DrawableSize.downscaleToSize(resources, drawable, 300, 300) assertThat(result).isSameInstanceAs(drawable) } @@ -48,14 +45,11 @@ class DrawableSizeTest : SysuiTestCase() { fun testDownscaleToSize_drawableLargerThanRequirementWithDensity_resized() { // This bitmap would actually fail to resize if the method doesn't check for // bitmap dimensions inside drawable. - val drawable = BitmapDrawable(resources, - Bitmap.createBitmap( - resources.displayMetrics, - 150, - 75, - Bitmap.Config.ARGB_8888 - ) - ) + val drawable = + BitmapDrawable( + resources, + Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888) + ) val result = DrawableSize.downscaleToSize(resources, drawable, 75, 75) assertThat(result).isNotSameInstanceAs(drawable) @@ -65,9 +59,9 @@ class DrawableSizeTest : SysuiTestCase() { @Test fun testDownscaleToSize_drawableAnimated_unchanged() { - val drawable = resources.getDrawable(android.R.drawable.stat_sys_download, - resources.newTheme()) + val drawable = + resources.getDrawable(android.R.drawable.stat_sys_download, resources.newTheme()) val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) assertThat(result).isSameInstanceAs(drawable) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt index 7d0d57b4037a..e2ce50ccb6da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.time.FakeSystemClock @@ -47,7 +47,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class PairwiseFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { @@ -89,7 +89,9 @@ class PairwiseFlowTest : SysuiTestCase() { initRun = true "initial" } - ) { prev: String, next: String -> "$prev|$next" } + ) { prev: String, next: String -> + "$prev|$next" + } ) .emitsExactly("initial|val1", "val1|val2") assertThat(initRun).isTrue() @@ -104,7 +106,9 @@ class PairwiseFlowTest : SysuiTestCase() { initRun = true "initial" } - ) { prev: String, next: String -> "$prev|$next" } + ) { prev: String, next: String -> + "$prev|$next" + } ) .emitsNothing() // Even though the flow will not emit anything, the initial value function should still get @@ -120,7 +124,9 @@ class PairwiseFlowTest : SysuiTestCase() { initRun = true "initial" } - ) { prev: String, next: String -> "$prev|$next" } + ) { prev: String, next: String -> + "$prev|$next" + } // Since the flow isn't collected, ensure [initialValueFun] isn't run. assertThat(initRun).isFalse() @@ -146,7 +152,7 @@ class PairwiseFlowTest : SysuiTestCase() { } @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SetChangesFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { @@ -198,7 +204,7 @@ class SetChangesFlowTest : SysuiTestCase() { } @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SampleFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { @@ -240,7 +246,7 @@ class SampleFlowTest : SysuiTestCase() { @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ThrottleFlowTest : SysuiTestCase() { @Test @@ -248,13 +254,16 @@ class ThrottleFlowTest : SysuiTestCase() { // Arrange val choreographer = createChoreographer(this) val output = mutableListOf<Int>() - val collectJob = backgroundScope.launch { - flow { - emit(1) - delay(1000) - emit(2) - }.throttle(1000, choreographer.fakeClock).toList(output) - } + val collectJob = + backgroundScope.launch { + flow { + emit(1) + delay(1000) + emit(2) + } + .throttle(1000, choreographer.fakeClock) + .toList(output) + } // Act choreographer.advanceAndRun(0) @@ -283,13 +292,16 @@ class ThrottleFlowTest : SysuiTestCase() { // Arrange val choreographer = createChoreographer(this) val output = mutableListOf<Int>() - val collectJob = backgroundScope.launch { - flow { - emit(1) - delay(500) - emit(2) - }.throttle(1000, choreographer.fakeClock).toList(output) - } + val collectJob = + backgroundScope.launch { + flow { + emit(1) + delay(500) + emit(2) + } + .throttle(1000, choreographer.fakeClock) + .toList(output) + } // Act choreographer.advanceAndRun(0) @@ -319,15 +331,18 @@ class ThrottleFlowTest : SysuiTestCase() { // Arrange val choreographer = createChoreographer(this) val output = mutableListOf<Int>() - val collectJob = backgroundScope.launch { - flow { - emit(1) - delay(500) - emit(2) - delay(500) - emit(3) - }.throttle(1000, choreographer.fakeClock).toList(output) - } + val collectJob = + backgroundScope.launch { + flow { + emit(1) + delay(500) + emit(2) + delay(500) + emit(3) + } + .throttle(1000, choreographer.fakeClock) + .toList(output) + } // Act choreographer.advanceAndRun(0) @@ -357,15 +372,18 @@ class ThrottleFlowTest : SysuiTestCase() { // Arrange val choreographer = createChoreographer(this) val output = mutableListOf<Int>() - val collectJob = backgroundScope.launch { - flow { - emit(1) - delay(500) - emit(2) - delay(250) - emit(3) - }.throttle(1000, choreographer.fakeClock).toList(output) - } + val collectJob = + backgroundScope.launch { + flow { + emit(1) + delay(500) + emit(2) + delay(250) + emit(3) + } + .throttle(1000, choreographer.fakeClock) + .toList(output) + } // Act choreographer.advanceAndRun(0) @@ -391,15 +409,16 @@ class ThrottleFlowTest : SysuiTestCase() { collectJob.cancel() } - private fun createChoreographer(testScope: TestScope) = object { - val fakeClock = FakeSystemClock() + private fun createChoreographer(testScope: TestScope) = + object { + val fakeClock = FakeSystemClock() - fun advanceAndRun(millis: Long) { - fakeClock.advanceTime(millis) - testScope.advanceTimeBy(millis) - testScope.runCurrent() + fun advanceAndRun(millis: Long) { + fakeClock.advanceTime(millis) + testScope.advanceTimeBy(millis) + testScope.runCurrent() + } } - } } private fun <T> assertThatFlow(flow: Flow<T>) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt index 4ca1fd39682d..c31b287fddce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import java.util.concurrent.atomic.AtomicLong @@ -31,43 +31,42 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class IpcSerializerTest : SysuiTestCase() { private val serializer = IpcSerializer() @Ignore("b/253046405") @Test - fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) { - val processor = launch(start = CoroutineStart.LAZY) { serializer.process() } - withContext(Dispatchers.IO) { - val lastEvaluatedTime = AtomicLong(System.currentTimeMillis()) - // First, launch many serialization requests in parallel - repeat(100_000) { - launch(Dispatchers.Unconfined) { - val enqueuedTime = System.currentTimeMillis() - serializer.runSerialized { - val last = lastEvaluatedTime.getAndSet(enqueuedTime) - assertTrue( - "expected $last less than or equal to $enqueuedTime ", - last <= enqueuedTime, - ) + fun serializeManyIncomingIpcs(): Unit = + runBlocking(Dispatchers.Main.immediate) { + val processor = launch(start = CoroutineStart.LAZY) { serializer.process() } + withContext(Dispatchers.IO) { + val lastEvaluatedTime = AtomicLong(System.currentTimeMillis()) + // First, launch many serialization requests in parallel + repeat(100_000) { + launch(Dispatchers.Unconfined) { + val enqueuedTime = System.currentTimeMillis() + serializer.runSerialized { + val last = lastEvaluatedTime.getAndSet(enqueuedTime) + assertTrue( + "expected $last less than or equal to $enqueuedTime ", + last <= enqueuedTime, + ) + } } } + // Then, process them all in the order they came in. + processor.start() } - // Then, process them all in the order they came in. - processor.start() + // All done, stop processing + processor.cancel() } - // All done, stop processing - processor.cancel() - } @Test(timeout = 5000) fun serializeOnOneThread_doesNotDeadlock() = runBlocking { val job = launch { serializer.process() } - repeat(100) { - serializer.runSerializedBlocking { } - } + repeat(100) { serializer.runSerializedBlocking {} } job.cancel() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt index 2013bb0a547e..8bfff9c209e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt @@ -27,13 +27,13 @@ import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameters import org.mockito.Mock import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) internal class PackageManagerExtComponentEnabledTest(private val testCase: TestCase) : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt index 6848b836a348..b2f7c1aa0384 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class RaceSuspendTest : SysuiTestCase() { @Test fun raceSimple() = runBlocking { @@ -46,10 +46,11 @@ class RaceSuspendTest : SysuiTestCase() { @Test fun raceImmediate() = runBlocking { assertThat( - race<Int>( - { 1 }, - { 2 }, + race<Int>( + { 1 }, + { 2 }, + ) ) - ).isEqualTo(1) + .isEqualTo(1) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java index 84129beea92a..300c29852ead 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java @@ -26,9 +26,9 @@ import static org.mockito.Mockito.verify; import android.content.res.Resources; import android.hardware.Sensor; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -46,7 +46,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class PostureDependentProximitySensorTest extends SysuiTestCase { @Mock private Resources mResources; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java index 19dbf9aa3c13..5dd008ac10f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java @@ -23,9 +23,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.junit.runner.RunWith; import java.util.function.Consumer; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ProximityCheckTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java index 5e7557896145..0eab74eb3cfb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java @@ -24,9 +24,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ProximitySensorImplDualTest extends SysuiTestCase { private ProximitySensor mProximitySensor; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java index 752cd3211161..f44c842ad2eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java @@ -21,9 +21,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.mockito.MockitoAnnotations; * Tests for ProximitySensor that rely on a single hardware sensor. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ProximitySensorImplSingleTest extends SysuiTestCase { private ProximitySensor mProximitySensor; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java index 8d26c877f4cf..a54afad617bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java @@ -30,8 +30,8 @@ import android.content.Intent; import android.content.pm.UserInfo; import android.os.IBinder; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -49,7 +49,7 @@ import java.util.List; import java.util.Objects; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ObservableServiceConnectionTest extends SysuiTestCase { static class Foo { int mValue; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java index a2fd288ef33e..a70b00c8972d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java @@ -24,8 +24,8 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -38,7 +38,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class PackageObserverTest extends SysuiTestCase { @Mock Context mContext; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java index 55c49ee4360d..ef10fdf63741 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java @@ -19,8 +19,7 @@ package com.android.systemui.util.service; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -37,7 +36,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class PersistentConnectionManagerTest extends SysuiTestCase { private static final int MAX_RETRIES = 5; private static final int RETRY_DELAY_MS = 1000; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java index f65caee24e34..99f6303ad73d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java @@ -28,8 +28,8 @@ import static org.mockito.Mockito.verify; import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -44,7 +44,7 @@ import java.util.Collection; import java.util.Map; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class FakeSettingsTest extends SysuiTestCase { @Mock ContentObserver mContentObserver; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt index 913759f77013..88b2630bd78f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.util.settings.repository import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -33,11 +34,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() { private val dispatcher = StandardTestDispatcher() @@ -48,11 +48,12 @@ class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() { @Before fun setup() { - repository = UserAwareSecureSettingsRepositoryImpl( - secureSettings, - userRepository, - dispatcher, - ) + repository = + UserAwareSecureSettingsRepositoryImpl( + secureSettings, + userRepository, + dispatcher, + ) userRepository.setUserInfos(USER_INFOS) setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER) setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER) @@ -105,4 +106,4 @@ class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() { val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0) val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt index 94100fe7f4c4..6637d5f8de92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.util.ui -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -30,7 +30,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class AnimatedValueTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt index e3cd9b2d6eaf..3dcb82811408 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt @@ -19,17 +19,20 @@ package com.android.systemui.util.view import android.graphics.Rect import android.view.View import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.doAnswer import org.mockito.Mockito.spy import org.mockito.Mockito.`when` @SmallTest +@RunWith(AndroidJUnit4::class) class ViewUtilTest : SysuiTestCase() { private val viewUtil = ViewUtil() private lateinit var view: View @@ -45,11 +48,13 @@ class ViewUtilTest : SysuiTestCase() { location[1] = VIEW_TOP `when`(view.locationOnScreen).thenReturn(location) doAnswer { invocation -> - val pos = invocation.arguments[0] as IntArray - pos[0] = VIEW_LEFT - pos[1] = VIEW_TOP - null - }.`when`(view).getLocationInWindow(any()) + val pos = invocation.arguments[0] as IntArray + pos[0] = VIEW_LEFT + pos[1] = VIEW_TOP + null + } + .`when`(view) + .getLocationInWindow(any()) } @Test @@ -59,9 +64,8 @@ class ViewUtilTest : SysuiTestCase() { @Test fun touchIsWithinView_onTopLeftCorner_returnsTrue() { - assertThat(viewUtil.touchIsWithinView( - view, VIEW_LEFT.toFloat(), VIEW_TOP.toFloat()) - ).isTrue() + assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT.toFloat(), VIEW_TOP.toFloat())) + .isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java index ed07ad2a0976..207c35da1892 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java @@ -35,15 +35,18 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import java.util.List; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + + @SmallTest -@RunWith(Parameterized.class) +@RunWith(ParameterizedAndroidJunit4.class) public class WakeLockTest extends SysuiTestCase { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") public static List<FlagsParameterization> getFlags() { return FlagsParameterization.allCombinationsOf( Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD); @@ -114,4 +117,4 @@ public class WakeLockTest extends SysuiTestCase { // shouldn't throw an exception on production builds mWakeLock.release(WHY); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index aac36405e9b6..df781108cf79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -27,6 +27,7 @@ import static android.service.notification.NotificationListenerService.REASON_GR import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING; +import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR; import static com.google.common.truth.Truth.assertThat; @@ -2141,6 +2142,112 @@ public class BubblesTest extends SysuiTestCase { assertThat(mBubbleController.getLayerView().isExpanded()).isFalse(); } + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void dragBubbleBarBubble_selectedBubble_expandedViewCollapsesDuringDrag() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + // Add 2 bubbles + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.updateBubble(mBubbleEntry2); + + // Select first bubble + mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect()); + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + + // Drag first bubble, bubble should collapse + mBubbleController.startBubbleDrag(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isFalse(); + + // Stop dragging, first bubble should be expanded + mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT); + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + } + + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void dragBubbleBarBubble_unselectedBubble_expandedViewCollapsesDuringDrag() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + // Add 2 bubbles + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.updateBubble(mBubbleEntry2); + + // Select first bubble + mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect()); + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + + // Drag second bubble, bubble should collapse + mBubbleController.startBubbleDrag(mBubbleEntry2.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isFalse(); + + // Stop dragging, first bubble should be expanded + mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT); + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + } + + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void dismissBubbleBarBubble_selected_selectsAndExpandsNext() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + // Add 2 bubbles + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.updateBubble(mBubbleEntry2); + + // Select first bubble + mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect()); + // Drag first bubble to dismiss + mBubbleController.startBubbleDrag(mBubbleEntry.getKey()); + mBubbleController.dragBubbleToDismiss(mBubbleEntry.getKey()); + // Second bubble is selected and expanded + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry2.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + } + + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void dismissBubbleBarBubble_unselected_selectionDoesNotChange() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + // Add 2 bubbles + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.updateBubble(mBubbleEntry2); + + // Select first bubble + mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect()); + // Drag second bubble to dismiss + mBubbleController.startBubbleDrag(mBubbleEntry2.getKey()); + mBubbleController.dragBubbleToDismiss(mBubbleEntry2.getKey()); + // First bubble remains selected and expanded + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + } + @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) @Test public void doesNotRegisterSensitiveStateListener() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 9dcd94687668..8eef930cbb0c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -15,8 +15,6 @@ */ package com.android.systemui; -import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; - import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -99,8 +97,22 @@ public abstract class SysuiTestCase { .setProvideMainThread(true) .build(); - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + @ClassRule + public static final SetFlagsRule.ClassRule mSetFlagsClassRule = + new SetFlagsRule.ClassRule( + android.app.Flags.class, + android.hardware.biometrics.Flags.class, + android.multiuser.Flags.class, + android.net.platform.flags.Flags.class, + android.os.Flags.class, + android.service.controls.flags.Flags.class, + com.android.internal.telephony.flags.Flags.class, + com.android.server.notification.Flags.class, + com.android.systemui.Flags.class); + + // TODO(b/339471826): Fix Robolectric to execute the @ClassRule correctly + @Rule public final SetFlagsRule mSetFlagsRule = + isRobolectricTest() ? new SetFlagsRule() : mSetFlagsClassRule.createSetFlagsRule(); @Rule(order = 10) public final SceneContainerRule mSceneContainerRule = new SceneContainerRule(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt index d3ceb15ca5a2..f5d02f328336 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt @@ -38,4 +38,8 @@ class FakeBrightnessPolicyRepository : BrightnessPolicyRepository { ) ) } + + fun setBaseUserRestriction() { + _restrictionPolicy.value = PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin()) + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 38f2a56d317f..3401cc4be231 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -27,6 +27,9 @@ import com.android.systemui.keyguard.data.repository.fakeCommandQueue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.model.sysUiState +import com.android.systemui.settings.displayTracker val Kosmos.shortcutHelperRepository by Kosmos.Fixture { ShortcutHelperRepository(fakeCommandQueue, broadcastDispatcher) } @@ -42,7 +45,9 @@ val Kosmos.shortcutHelperTestHelper by } val Kosmos.shortcutHelperInteractor by - Kosmos.Fixture { ShortcutHelperInteractor(shortcutHelperRepository) } + Kosmos.Fixture { + ShortcutHelperInteractor(displayTracker, testScope, sysUiState, shortcutHelperRepository) + } val Kosmos.shortcutHelperViewModel by Kosmos.Fixture { ShortcutHelperViewModel(testDispatcher, shortcutHelperInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt index 2c6d44f10152..03e5a90c9f7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi @@ -29,5 +30,6 @@ val Kosmos.keyguardDismissActionInteractor by transitionInteractor = keyguardTransitionInteractor, dismissInteractor = keyguardDismissInteractor, applicationScope = testScope.backgroundScope, + sceneInteractor = sceneInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt index 6cc1e8eba73d..c90642d93939 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneInteractor val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by Kosmos.Fixture { @@ -32,5 +33,6 @@ val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by fromAodTransitionInteractor = { fromAodTransitionInteractor }, fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor }, fromDozingTransitionInteractor = { fromDozingTransitionInteractor }, + sceneInteractor = { sceneInteractor } ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt index 460913f75eb4..b8fcec648393 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -27,7 +26,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.aodToLockscreenTransitionViewModel by Fixture { AodToLockscreenTransitionViewModel( - deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, shadeInteractor = shadeInteractor, animationFlow = keyguardTransitionAnimationFlow, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt new file mode 100644 index 000000000000..8ad6087cfe82 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qrcodescanner + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController +import com.android.systemui.util.mockito.mock + +val Kosmos.qrCodeScannerController by Kosmos.Fixture { mock<QRCodeScannerController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt index c4bf8ff12817..f50443ec4e86 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt @@ -31,7 +31,11 @@ class FakeQSTileIntentUserInputHandler : QSTileIntentUserInputHandler { private val mutableInputs = mutableListOf<Input>() - override fun handle(expandable: Expandable?, intent: Intent) { + override fun handle( + expandable: Expandable?, + intent: Intent, + handleDismissShadeShowOverLockScreenWhenLocked: Boolean + ) { mutableInputs.add(Input.Intent(expandable, intent)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt new file mode 100644 index 000000000000..ccfb6092a2e3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.actions + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.qsTileIntentUserInputHandler by Kosmos.Fixture { FakeQSTileIntentUserInputHandler() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt new file mode 100644 index 000000000000..146c1ad6ab70 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.analytics + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.qsTileAnalytics by Kosmos.Fixture { mock<QSTileAnalytics>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt new file mode 100644 index 000000000000..9ad49f052c9e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.interactor + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeDisabledByPolicyInteractor by Kosmos.Fixture { FakeDisabledByPolicyInteractor() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt new file mode 100644 index 000000000000..dcfcce77942e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr + +import android.content.res.mainResources +import com.android.systemui.classifier.fakeFalsingManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.backgroundCoroutineContext +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule +import com.android.systemui.qrcodescanner.qrCodeScannerController +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.analytics.qsTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.fakeDisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl +import com.android.systemui.qs.tiles.impl.custom.qsTileLogger +import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor +import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.time.systemClock + +val Kosmos.qsQRCodeScannerTileConfig by + Kosmos.Fixture { QRCodeScannerModule.provideQRCodeScannerTileConfig(qsEventLogger) } + +val Kosmos.qrCodeScannerTileDataInteractor by + Kosmos.Fixture { + QRCodeScannerTileDataInteractor( + backgroundCoroutineContext, + applicationCoroutineScope, + qrCodeScannerController + ) + } + +val Kosmos.qrCodeScannerTileUserActionInteractor by + Kosmos.Fixture { QRCodeScannerTileUserActionInteractor(qsTileIntentUserInputHandler) } + +val Kosmos.qrCodeScannerTileMapper by + Kosmos.Fixture { QRCodeScannerTileMapper(mainResources, mainResources.newTheme()) } + +val Kosmos.qsQRCodeScannerViewModel by + Kosmos.Fixture { + QSTileViewModelImpl( + qsQRCodeScannerTileConfig, + { qrCodeScannerTileUserActionInteractor }, + { qrCodeScannerTileDataInteractor }, + { qrCodeScannerTileMapper }, + fakeDisabledByPolicyInteractor, + fakeUserRepository, + fakeFalsingManager, + qsTileAnalytics, + qsTileLogger, + systemClock, + testDispatcher, + testScope.backgroundScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt new file mode 100644 index 000000000000..641a75716ea0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.data.repository + +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.model.Scenes +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent + +private val mutableTransitionState = + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(Scenes.Lockscreen)) + +fun Kosmos.setSceneTransition( + transition: ObservableTransitionState, + scope: TestScope = testScope, + repository: SceneContainerRepository = sceneContainerRepository +) { + repository.setTransitionState(mutableTransitionState) + mutableTransitionState.value = transition + scope.runCurrent() +} + +fun Transition( + from: SceneKey, + to: SceneKey, + currentScene: Flow<SceneKey> = flowOf(to), + progress: Flow<Float> = flowOf(0f), + isInitiatedByUserInput: Boolean = false, + isUserInputOngoing: Flow<Boolean> = flowOf(false), +): ObservableTransitionState.Transition { + return ObservableTransitionState.Transition( + fromScene = from, + toScene = to, + currentScene = currentScene, + progress = progress, + isInitiatedByUserInput = isInitiatedByUserInput, + isUserInputOngoing = isUserInputOngoing + ) +} + +fun Idle(currentScene: SceneKey): ObservableTransitionState.Idle { + return ObservableTransitionState.Idle(currentScene) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index cce038f4ffc1..8229575a128f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -23,6 +23,7 @@ import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy @@ -93,6 +94,8 @@ class FakeMobileConnectionsRepository( private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON) override val defaultMobileIconGroup = _defaultMobileIconGroup + override val deviceServiceState = MutableStateFlow<ServiceStateModel?>(null) + override val isAnySimSecure = MutableStateFlow(false) override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index de6c87c2b515..3a4bf8e48d33 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -81,6 +81,8 @@ class FakeMobileIconsInteractor( override val isForceHidden = MutableStateFlow(false) + override val isDeviceInEmergencyCallsOnlyMode = MutableStateFlow(false) + /** Always returns a new fake interactor */ override fun getMobileConnectionInteractorForSubId(subId: Int): FakeMobileIconInteractor { return FakeMobileIconInteractor(tableLogBuffer).also { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt index 59bbff171725..1b58582a806f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt @@ -18,8 +18,6 @@ package com.android.systemui.volume import android.content.packageManager import android.content.pm.ApplicationInfo -import android.os.Handler -import android.os.looper import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.media.mediaOutputDialogManager @@ -32,6 +30,7 @@ import com.android.systemui.volume.panel.component.mediaoutput.data.repository.F import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor val Kosmos.localMediaRepository by Kosmos.Fixture { FakeLocalMediaRepository() } val Kosmos.localMediaRepositoryFactory by @@ -53,7 +52,7 @@ val Kosmos.mediaOutputInteractor by testScope.backgroundScope, testScope.testScheduler, mediaControllerRepository, - Handler(looper), + mediaControllerInteractor, ) } @@ -61,7 +60,7 @@ val Kosmos.mediaDeviceSessionInteractor by Kosmos.Fixture { MediaDeviceSessionInteractor( testScope.testScheduler, - Handler(looper), + mediaControllerInteractor, mediaControllerRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt index 617fc5258ec7..6b27079cb648 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.data.repository import android.media.AudioDeviceInfo +import android.media.AudioManager import com.android.settingslib.volume.data.repository.AudioRepository import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel @@ -29,10 +30,10 @@ import kotlinx.coroutines.flow.update class FakeAudioRepository : AudioRepository { - private val mutableMode = MutableStateFlow(0) + private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL) override val mode: StateFlow<Int> = mutableMode.asStateFlow() - private val mutableRingerMode = MutableStateFlow(RingerMode(0)) + private val mutableRingerMode = MutableStateFlow(RingerMode(AudioManager.RINGER_MODE_NORMAL)) override val ringerMode: StateFlow<RingerMode> = mutableRingerMode.asStateFlow() private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null) @@ -53,7 +54,7 @@ class FakeAudioRepository : AudioRepository { audioStream = audioStream, volume = 0, minVolume = 0, - maxVolume = 0, + maxVolume = 10, isAffectedByRingerMode = false, isMuted = false, ) @@ -67,8 +68,14 @@ class FakeAudioRepository : AudioRepository { getAudioStreamModelState(audioStream).update { it.copy(volume = volume) } } - override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) { - getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) } + override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean { + val modelState = getAudioStreamModelState(audioStream) + return if (modelState.value.isMuted == isMuted) { + false + } else { + modelState.update { it.copy(isMuted = isMuted) } + true + } } override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt new file mode 100644 index 000000000000..f03ec01bd36f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor + +import android.media.session.MediaController +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow + +class FakeMediaControllerInteractor : MediaControllerInteractor { + + private val stateChanges = MutableSharedFlow<MediaControllerChangeModel>(replay = 1) + + override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> = + stateChanges + + fun updateState(change: MediaControllerChangeModel) { + stateChanges.tryEmit(change) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt new file mode 100644 index 000000000000..652b3ea984e7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor + +import android.os.Handler +import android.os.looper +import com.android.systemui.kosmos.Kosmos + +var Kosmos.mediaControllerInteractor: MediaControllerInteractor by + Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) } diff --git a/proto/src/am_capabilities.proto b/proto/src/am_capabilities.proto index fc9f7a4590bd..c2b3ac2aaa78 100644 --- a/proto/src/am_capabilities.proto +++ b/proto/src/am_capabilities.proto @@ -15,8 +15,16 @@ message FrameworkCapability { string name = 1; } +message VMInfo { + // The value of the "java.vm.name" system property + string name = 1; + // The value of the "java.vm.version" system property + string version = 2; +} + message Capabilities { repeated Capability values = 1; repeated VMCapability vm_capabilities = 2; repeated FrameworkCapability framework_capabilities = 3; + VMInfo vm_info = 4; } diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java new file mode 100644 index 000000000000..4992c4bcc77a --- /dev/null +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java @@ -0,0 +1,753 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.platform.test.ravenwood; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.view.Display; +import android.view.DisplayAdjustments; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A subclass of Context with all the abstract methods replaced with concrete methods. + * + * <p>In order to make sure it implements all the abstract methods, we intentionally keep it + * non-abstract. + */ +public class RavenwoodBaseContext extends Context { + RavenwoodBaseContext() { + // Only usable by ravenwood. + } + + private static RuntimeException notSupported() { + return new RuntimeException("This Context API is not yet supported under" + + " the Ravenwood deviceless testing environment. Contact g/ravenwood"); + } + + @Override + public AssetManager getAssets() { + throw notSupported(); + } + + @Override + public Resources getResources() { + throw notSupported(); + } + + @Override + public PackageManager getPackageManager() { + throw notSupported(); + } + + @Override + public ContentResolver getContentResolver() { + throw notSupported(); + } + + @Override + public Looper getMainLooper() { + throw notSupported(); + } + + @Override + public Context getApplicationContext() { + throw notSupported(); + } + + @Override + public void setTheme(int resid) { + throw notSupported(); + } + + @Override + public Theme getTheme() { + throw notSupported(); + } + + @Override + public ClassLoader getClassLoader() { + throw notSupported(); + } + + @Override + public String getPackageName() { + throw notSupported(); + } + + @Override + public String getBasePackageName() { + throw notSupported(); + } + + @Override + public ApplicationInfo getApplicationInfo() { + throw notSupported(); + } + + @Override + public String getPackageResourcePath() { + throw notSupported(); + } + + @Override + public String getPackageCodePath() { + throw notSupported(); + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + throw notSupported(); + } + + @Override + public SharedPreferences getSharedPreferences(File file, int mode) { + throw notSupported(); + } + + @Override + public boolean moveSharedPreferencesFrom(Context sourceContext, String name) { + throw notSupported(); + } + + @Override + public boolean deleteSharedPreferences(String name) { + throw notSupported(); + } + + @Override + public void reloadSharedPreferences() { + throw notSupported(); + } + + @Override + public FileInputStream openFileInput(String name) throws FileNotFoundException { + throw notSupported(); + } + + @Override + public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { + throw notSupported(); + } + + @Override + public boolean deleteFile(String name) { + throw notSupported(); + } + + @Override + public File getFileStreamPath(String name) { + throw notSupported(); + } + + @Override + public File getSharedPreferencesPath(String name) { + throw notSupported(); + } + + @Override + public File getDataDir() { + throw notSupported(); + } + + @Override + public File getFilesDir() { + throw notSupported(); + } + + @Override + public File getNoBackupFilesDir() { + throw notSupported(); + } + + @Override + public File getExternalFilesDir(String type) { + throw notSupported(); + } + + @Override + public File[] getExternalFilesDirs(String type) { + throw notSupported(); + } + + @Override + public File getObbDir() { + throw notSupported(); + } + + @Override + public File[] getObbDirs() { + throw notSupported(); + } + + @Override + public File getCacheDir() { + throw notSupported(); + } + + @Override + public File getCodeCacheDir() { + throw notSupported(); + } + + @Override + public File getExternalCacheDir() { + throw notSupported(); + } + + @Override + public File getPreloadsFileCache() { + throw notSupported(); + } + + @Override + public File[] getExternalCacheDirs() { + throw notSupported(); + } + + @Override + public File[] getExternalMediaDirs() { + throw notSupported(); + } + + @Override + public String[] fileList() { + throw notSupported(); + } + + @Override + public File getDir(String name, int mode) { + throw notSupported(); + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) { + throw notSupported(); + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, + DatabaseErrorHandler errorHandler) { + throw notSupported(); + } + + @Override + public boolean moveDatabaseFrom(Context sourceContext, String name) { + throw notSupported(); + } + + @Override + public boolean deleteDatabase(String name) { + throw notSupported(); + } + + @Override + public File getDatabasePath(String name) { + throw notSupported(); + } + + @Override + public String[] databaseList() { + throw notSupported(); + } + + @Override + public Drawable getWallpaper() { + throw notSupported(); + } + + @Override + public Drawable peekWallpaper() { + throw notSupported(); + } + + @Override + public int getWallpaperDesiredMinimumWidth() { + throw notSupported(); + } + + @Override + public int getWallpaperDesiredMinimumHeight() { + throw notSupported(); + } + + @Override + public void setWallpaper(Bitmap bitmap) throws IOException { + throw notSupported(); + } + + @Override + public void setWallpaper(InputStream data) throws IOException { + throw notSupported(); + } + + @Override + public void clearWallpaper() throws IOException { + throw notSupported(); + } + + @Override + public void startActivity(Intent intent) { + throw notSupported(); + } + + @Override + public void startActivity(Intent intent, Bundle options) { + throw notSupported(); + } + + @Override + public void startActivities(Intent[] intents) { + throw notSupported(); + } + + @Override + public void startActivities(Intent[] intents, Bundle options) { + throw notSupported(); + } + + @Override + public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, + int flagsValues, int extraFlags) throws SendIntentException { + throw notSupported(); + } + + @Override + public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, + int flagsValues, int extraFlags, Bundle options) throws SendIntentException { + throw notSupported(); + } + + @Override + public void sendBroadcast(Intent intent) { + throw notSupported(); + } + + @Override + public void sendBroadcast(Intent intent, String receiverPermission) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, + String[] receiverPermissions) { + throw notSupported(); + } + + @Override + public void sendBroadcast(Intent intent, String receiverPermission, int appOp) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission, + BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, + String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission, + int appOp, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, + String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, + Bundle options) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, + int appOp) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, int appOp, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, int appOp, Bundle options, + BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, + String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendStickyBroadcast(Intent intent) { + throw notSupported(); + } + + @Override + public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + throw notSupported(); + + } + + @Override + public void removeStickyBroadcast(Intent intent) { + throw notSupported(); + + } + + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { + throw notSupported(); + } + + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + throw notSupported(); + + } + + @Override + public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, + BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, + String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) { + throw notSupported(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + throw notSupported(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { + throw notSupported(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler) { + throw notSupported(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler, int flags) { + throw notSupported(); + } + + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + throw notSupported(); + } + + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) { + throw notSupported(); + } + + @Override + public void unregisterReceiver(BroadcastReceiver receiver) { + throw notSupported(); + } + + @Override + public ComponentName startService(Intent service) { + throw notSupported(); + } + + @Override + public ComponentName startForegroundService(Intent service) { + throw notSupported(); + } + + @Override + public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) { + throw notSupported(); + } + + @Override + public boolean stopService(Intent service) { + throw notSupported(); + } + + @Override + public ComponentName startServiceAsUser(Intent service, UserHandle user) { + throw notSupported(); + } + + @Override + public boolean stopServiceAsUser(Intent service, UserHandle user) { + throw notSupported(); + } + + @Override + public boolean bindService(Intent service, ServiceConnection conn, int flags) { + throw notSupported(); + } + + @Override + public void unbindService(ServiceConnection conn) { + throw notSupported(); + } + + @Override + public boolean startInstrumentation(ComponentName className, String profileFile, + Bundle arguments) { + throw notSupported(); + } + + @Override + public Object getSystemService(String name) { + throw notSupported(); + } + + @Override + public String getSystemServiceName(Class<?> serviceClass) { + throw notSupported(); + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + throw notSupported(); + } + + @Override + public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { + throw notSupported(); + } + + @Override + public int checkCallingPermission(String permission) { + throw notSupported(); + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + throw notSupported(); + } + + @Override + public int checkSelfPermission(String permission) { + throw notSupported(); + } + + @Override + public void enforcePermission(String permission, int pid, int uid, String message) { + throw notSupported(); + } + + @Override + public void enforceCallingPermission(String permission, String message) { + throw notSupported(); + } + + @Override + public void enforceCallingOrSelfPermission(String permission, String message) { + throw notSupported(); + } + + @Override + public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public void revokeUriPermission(Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public void revokeUriPermission(String toPackage, Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { + throw notSupported(); + } + + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) { + throw notSupported(); + } + + @Override + public int checkCallingUriPermission(Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public int checkUriPermission(Uri uri, String readPermission, String writePermission, + int pid, int uid, int modeFlags) { + throw notSupported(); + } + + @Override + public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) { + throw notSupported(); + } + + @Override + public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) { + throw notSupported(); + } + + @Override + public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) { + throw notSupported(); + } + + @Override + public void enforceUriPermission(Uri uri, String readPermission, String writePermission, + int pid, int uid, int modeFlags, String message) { + throw notSupported(); + } + + @Override + public Context createPackageContext(String packageName, int flags) + throws NameNotFoundException { + throw notSupported(); + } + + @Override + public Context createApplicationContext(ApplicationInfo application, int flags) + throws NameNotFoundException { + throw notSupported(); + } + + @Override + public Context createContextForSplit(String splitName) throws NameNotFoundException { + throw notSupported(); + } + + @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + throw notSupported(); + } + + @Override + public Context createDisplayContext(Display display) { + throw notSupported(); + } + + @Override + public Context createDeviceProtectedStorageContext() { + throw notSupported(); + } + + @Override + public Context createCredentialProtectedStorageContext() { + throw notSupported(); + } + + @Override + public DisplayAdjustments getDisplayAdjustments(int displayId) { + throw notSupported(); + } + + @Override + public int getDisplayId() { + throw notSupported(); + } + + @Override + public void updateDisplay(int displayId) { + throw notSupported(); + } + + @Override + public boolean isDeviceProtectedStorage() { + throw notSupported(); + } + + @Override + public boolean isCredentialProtectedStorage() { + throw notSupported(); + } + + @Override + public boolean canLoadUnsafeResources() { + throw notSupported(); + } +} diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java index 109ef76b535f..1dd5e1ddd630 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java @@ -28,7 +28,6 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.ravenwood.example.BlueManager; import android.ravenwood.example.RedManager; -import android.test.mock.MockContext; import android.util.ArrayMap; import android.util.Singleton; @@ -36,7 +35,7 @@ import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Supplier; -public class RavenwoodContext extends MockContext { +public class RavenwoodContext extends RavenwoodBaseContext { private final String mPackageName; private final HandlerThread mMainThread; diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java index 96b7057d25ec..69ff262fe915 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java @@ -162,20 +162,23 @@ public class ClassLoadHook { android.graphics.Interpolator.class, android.graphics.Matrix.class, android.graphics.Path.class, + android.graphics.Color.class, + android.graphics.ColorSpace.class, }; /** - * @return if a given class has any native method or not. + * @return if a given class and its nested classes, if any, have any native method or not. */ private static boolean hasNativeMethod(Class<?> clazz) { - for (var method : clazz.getDeclaredMethods()) { - if (Modifier.isNative(method.getModifiers())) { - return true; + for (var nestedClass : clazz.getNestMembers()) { + for (var method : nestedClass.getDeclaredMethods()) { + if (Modifier.isNative(method.getModifiers())) { + return true; + } } } return false; } - /** * Create a list of classes as comma-separated that require JNI methods to be set up from * a given class list, ignoring classes with no native methods. diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index e452299373f8..f3172ae3090d 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -1,5 +1,7 @@ # Only classes listed here can use the Ravenwood annotations. +com.android.internal.ravenwood.* + com.android.internal.display.BrightnessSynchronizer com.android.internal.util.ArrayUtils com.android.internal.logging.MetricsLogger @@ -237,6 +239,8 @@ android.text.TextUtils$SimpleStringSplitter android.accounts.Account android.graphics.Bitmap$Config +android.graphics.Color +android.graphics.ColorSpace android.graphics.Insets android.graphics.Interpolator android.graphics.Matrix diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt index 371c3acab144..9d29a051d092 100644 --- a/ravenwood/texts/ravenwood-framework-policies.txt +++ b/ravenwood/texts/ravenwood-framework-policies.txt @@ -1,59 +1,59 @@ # Ravenwood "policy" file for framework-minus-apex. # Keep all AIDL interfaces -class :aidl stubclass +class :aidl keepclass # Keep all feature flag implementations -class :feature_flags stubclass +class :feature_flags keepclass # Keep all sysprops generated code implementations -class :sysprops stubclass +class :sysprops keepclass # Exported to Mainline modules; cannot use annotations -class com.android.internal.util.FastXmlSerializer stubclass -class com.android.internal.util.FileRotator stubclass -class com.android.internal.util.HexDump stubclass -class com.android.internal.util.IndentingPrintWriter stubclass -class com.android.internal.util.LocalLog stubclass -class com.android.internal.util.MessageUtils stubclass -class com.android.internal.util.TokenBucket stubclass -class android.os.HandlerExecutor stubclass -class android.util.BackupUtils stubclass -class android.util.IndentingPrintWriter stubclass -class android.util.LocalLog stubclass -class android.util.Pair stubclass -class android.util.Rational stubclass +class com.android.internal.util.FastXmlSerializer keepclass +class com.android.internal.util.FileRotator keepclass +class com.android.internal.util.HexDump keepclass +class com.android.internal.util.IndentingPrintWriter keepclass +class com.android.internal.util.LocalLog keepclass +class com.android.internal.util.MessageUtils keepclass +class com.android.internal.util.TokenBucket keepclass +class android.os.HandlerExecutor keepclass +class android.util.BackupUtils keepclass +class android.util.IndentingPrintWriter keepclass +class android.util.LocalLog keepclass +class android.util.Pair keepclass +class android.util.Rational keepclass # From modules-utils; cannot use annotations -class com.android.internal.util.Preconditions stubclass -class com.android.internal.logging.InstanceId stubclass -class com.android.internal.logging.InstanceIdSequence stubclass -class com.android.internal.logging.UiEvent stubclass -class com.android.internal.logging.UiEventLogger stubclass +class com.android.internal.util.Preconditions keepclass +class com.android.internal.logging.InstanceId keepclass +class com.android.internal.logging.InstanceIdSequence keepclass +class com.android.internal.logging.UiEvent keepclass +class com.android.internal.logging.UiEventLogger keepclass # From modules-utils; cannot use annotations -class com.android.modules.utils.BinaryXmlPullParser stubclass -class com.android.modules.utils.BinaryXmlSerializer stubclass -class com.android.modules.utils.FastDataInput stubclass -class com.android.modules.utils.FastDataOutput stubclass -class com.android.modules.utils.ModifiedUtf8 stubclass -class com.android.modules.utils.TypedXmlPullParser stubclass -class com.android.modules.utils.TypedXmlSerializer stubclass +class com.android.modules.utils.BinaryXmlPullParser keepclass +class com.android.modules.utils.BinaryXmlSerializer keepclass +class com.android.modules.utils.FastDataInput keepclass +class com.android.modules.utils.FastDataOutput keepclass +class com.android.modules.utils.ModifiedUtf8 keepclass +class com.android.modules.utils.TypedXmlPullParser keepclass +class com.android.modules.utils.TypedXmlSerializer keepclass # Uri -class android.net.Uri stubclass -class android.net.UriCodec stubclass +class android.net.Uri keepclass +class android.net.UriCodec keepclass # Telephony -class android.telephony.PinResult stubclass +class android.telephony.PinResult keepclass # Just enough to support mocking, no further functionality -class android.content.BroadcastReceiver stub - method <init> ()V stub -class android.content.Context stub - method <init> ()V stub - method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub -class android.content.pm.PackageManager stub - method <init> ()V stub -class android.text.ClipboardManager stub - method <init> ()V stub +class android.content.BroadcastReceiver keep + method <init> ()V keep +class android.content.Context keep + method <init> ()V keep + method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; keep +class android.content.pm.PackageManager keep + method <init> ()V keep +class android.text.ClipboardManager keep + method <init> ()V keep diff --git a/ravenwood/texts/ravenwood-services-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt index d8d563e05435..5cdb4f74d7c0 100644 --- a/ravenwood/texts/ravenwood-services-policies.txt +++ b/ravenwood/texts/ravenwood-services-policies.txt @@ -1,7 +1,7 @@ # Ravenwood "policy" file for services.core. # Keep all AIDL interfaces -class :aidl stubclass +class :aidl keepclass # Keep all feature flag implementations -class :feature_flags stubclass +class :feature_flags keepclass diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 82579d807eba..a50fb9a4c318 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -138,6 +138,16 @@ flag { } flag { + name: "manager_package_monitor_logic_fix" + namespace: "accessibility" + description: "Corrects the return values of the HandleForceStop function" + bug: "337392123" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "pinch_zoom_zero_min_span" namespace: "accessibility" description: "Whether to set min span of ScaleGestureDetector to zero." @@ -152,6 +162,16 @@ flag { } flag { + name: "remove_on_window_infos_changed_handler" + namespace: "accessibility" + description: "Updates onWindowInfosChanged() to run without posting to a handler." + bug: "333834990" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "reset_hover_event_timer_on_action_up" namespace: "accessibility" description: "Reset the timer for sending hover events on receiving ACTION_UP to guarantee the correct amount of time is available between taps." @@ -183,3 +203,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_color_correction_saturation" + namespace: "accessibility" + description: "Feature allows users to change color correction saturation for daltonizer." + bug: "322829049" +}
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index c70b6419e3b8..a15d2ca57788 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -697,7 +697,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /** * Returns the lock object for any synchronized test blocks. - * Should not be used outside of testing. + * External classes should only use for testing. * @return lock object. */ @VisibleForTesting @@ -801,7 +801,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * * @param packages list of packages that have stopped. * @param userState user state to be read & modified. - * @return {@code true} if a service was enabled or a button target was removed, + * @return {@code true} if the lists of enabled services or buttons were changed, * {@code false} otherwise. */ @VisibleForTesting @@ -824,6 +824,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.getBindingServicesLocked().remove(comp); userState.getCrashedServicesLocked().remove(comp); enabledServicesChanged = true; + break; } } } @@ -851,132 +852,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return mPackageMonitor; } - private void registerBroadcastReceivers() { - mPackageMonitor = new PackageMonitor(true) { - @Override - public void onSomePackagesChanged() { - if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged", - FLAGS_PACKAGE_BROADCAST_RECEIVER); - } - - final int userId = getChangingUserId(); - List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; - List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; - parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); - parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); - synchronized (mLock) { - // Only the profile parent can install accessibility services. - // Therefore we ignore packages from linked profiles. - if (userId != mCurrentUserId) { - return; - } - onSomePackagesChangedLocked(parsedAccessibilityServiceInfos, - parsedAccessibilityShortcutInfos); - } - } - - @Override - public void onPackageUpdateFinished(String packageName, int uid) { - // The package should already be removed from mBoundServices, and added into - // mBindingServices in binderDied() during updating. Remove services from this - // package from mBindingServices, and then update the user state to re-bind new - // versions of them. - if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".PM.onPackageUpdateFinished", - FLAGS_PACKAGE_BROADCAST_RECEIVER, - "packageName=" + packageName + ";uid=" + uid); - } - final int userId = getChangingUserId(); - List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; - List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; - parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); - parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); - synchronized (mLock) { - if (userId != mCurrentUserId) { - return; - } - final AccessibilityUserState userState = getUserStateLocked(userId); - final boolean reboundAService = userState.getBindingServicesLocked().removeIf( - component -> component != null - && component.getPackageName().equals(packageName)) - || userState.mCrashedServices.removeIf(component -> component != null - && component.getPackageName().equals(packageName)); - // Reloads the installed services info to make sure the rebound service could - // get a new one. - userState.mInstalledServices.clear(); - final boolean configurationChanged; - configurationChanged = readConfigurationForUserStateLocked(userState, - parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); - if (reboundAService || configurationChanged) { - onUserStateChangedLocked(userState); - } - // Passing 0 for restoreFromSdkInt to have this migration check execute each - // time. It can make sure a11y button settings are correctly if there's an a11y - // service updated and modifies the a11y button configuration. - migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName, - /* restoreFromSdkInt = */0); - } - } - - @Override - public void onPackageRemoved(String packageName, int uid) { - if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved", - FLAGS_PACKAGE_BROADCAST_RECEIVER, - "packageName=" + packageName + ";uid=" + uid); - } - - synchronized (mLock) { - final int userId = getChangingUserId(); - // Only the profile parent can install accessibility services. - // Therefore we ignore packages from linked profiles. - if (userId != mCurrentUserId) { - return; - } - onPackageRemovedLocked(packageName); - } - } - - /** - * Handles instances in which a package or packages have forcibly stopped. - * - * @param intent intent containing package event information. - * @param uid linux process user id (different from Android user id). - * @param packages array of package names that have stopped. - * @param doit whether to try and handle the stop or just log the trace. - * - * @return {@code true} if package should be restarted, {@code false} otherwise. - */ - @Override - public boolean onHandleForceStop(Intent intent, String[] packages, - int uid, boolean doit) { - if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop", - FLAGS_PACKAGE_BROADCAST_RECEIVER, - "intent=" + intent + ";packages=" + Arrays.toString(packages) - + ";uid=" + uid + ";doit=" + doit); - } - synchronized (mLock) { - final int userId = getChangingUserId(); - // Only the profile parent can install accessibility services. - // Therefore we ignore packages from linked profiles. - if (userId != mCurrentUserId) { - return false; - } - final AccessibilityUserState userState = getUserStateLocked(userId); - - if (doit && onPackagesForceStoppedLocked(packages, userState)) { - onUserStateChangedLocked(userState); - return false; - } else { - return true; - } - } - } - }; + @VisibleForTesting + void setPackageMonitor(PackageMonitor monitor) { + mPackageMonitor = monitor; + } + private void registerBroadcastReceivers() { // package changes + mPackageMonitor = new ManagerPackageMonitor(this); mPackageMonitor.register(mContext, null, UserHandle.ALL, true); // user change and unlock @@ -992,7 +875,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onReceive(Context context, Intent intent) { if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_USER_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".BR.onReceive", FLAGS_USER_BROADCAST_RECEIVER, + mTraceManager.logTrace( + LOG_TAG + ".BR.onReceive", + FLAGS_USER_BROADCAST_RECEIVER, "context=" + context + ";intent=" + intent); } @@ -1045,7 +930,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub setNonA11yToolNotificationToMatchSafetyCenter(); } }; - mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler, + mContext.registerReceiverAsUser( + receiver, UserHandle.ALL, filter, null, mMainHandler, Context.RECEIVER_EXPORTED); if (!android.companion.virtual.flags.Flags.vdmPublicApis()) { @@ -4371,7 +4257,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub ); if (!targetWithNoTile.isEmpty()) { - throw new IllegalArgumentException( + Slog.e(LOG_TAG, "Unable to add/remove Tiles for a11y features: " + targetWithNoTile + "as the Tiles aren't provided"); } @@ -6223,6 +6109,162 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + @VisibleForTesting + public static class ManagerPackageMonitor extends PackageMonitor { + private final AccessibilityManagerService mManagerService; + public ManagerPackageMonitor(AccessibilityManagerService managerService) { + super(/* supportsPackageRestartQuery = */ true); + mManagerService = managerService; + } + + @Override + public void onSomePackagesChanged() { + if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_PACKAGE_BROADCAST_RECEIVER)) { + mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged", + FLAGS_PACKAGE_BROADCAST_RECEIVER); + } + + final int userId = getChangingUserId(); + List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = mManagerService + .parseAccessibilityServiceInfos(userId); + List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = mManagerService + .parseAccessibilityShortcutInfos(userId); + synchronized (mManagerService.getLock()) { + // Only the profile parent can install accessibility services. + // Therefore we ignore packages from linked profiles. + if (userId != mManagerService.getCurrentUserIdLocked()) { + return; + } + mManagerService.onSomePackagesChangedLocked(parsedAccessibilityServiceInfos, + parsedAccessibilityShortcutInfos); + } + } + + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + // The package should already be removed from mBoundServices, and added into + // mBindingServices in binderDied() during updating. Remove services from this + // package from mBindingServices, and then update the user state to re-bind new + // versions of them. + if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_PACKAGE_BROADCAST_RECEIVER)) { + mManagerService.mTraceManager.logTrace( + LOG_TAG + ".PM.onPackageUpdateFinished", + FLAGS_PACKAGE_BROADCAST_RECEIVER, + "packageName=" + packageName + ";uid=" + uid); + } + final int userId = getChangingUserId(); + List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = mManagerService + .parseAccessibilityServiceInfos(userId); + List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = + mManagerService.parseAccessibilityShortcutInfos(userId); + synchronized (mManagerService.getLock()) { + if (userId != mManagerService.getCurrentUserIdLocked()) { + return; + } + final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId); + final boolean reboundAService = userState.getBindingServicesLocked().removeIf( + component -> component != null + && component.getPackageName().equals(packageName)) + || userState.mCrashedServices.removeIf(component -> component != null + && component.getPackageName().equals(packageName)); + // Reloads the installed services info to make sure the rebound service could + // get a new one. + userState.mInstalledServices.clear(); + final boolean configurationChanged; + configurationChanged = mManagerService.readConfigurationForUserStateLocked( + userState, parsedAccessibilityServiceInfos, + parsedAccessibilityShortcutInfos); + if (reboundAService || configurationChanged) { + mManagerService.onUserStateChangedLocked(userState); + } + // Passing 0 for restoreFromSdkInt to have this migration check execute each + // time. It can make sure a11y button settings are correctly if there's an a11y + // service updated and modifies the a11y button configuration. + mManagerService.migrateAccessibilityButtonSettingsIfNecessaryLocked( + userState, packageName, /* restoreFromSdkInt = */0); + } + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_PACKAGE_BROADCAST_RECEIVER)) { + mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved", + FLAGS_PACKAGE_BROADCAST_RECEIVER, + "packageName=" + packageName + ";uid=" + uid); + } + + synchronized (mManagerService.getLock()) { + final int userId = getChangingUserId(); + // Only the profile parent can install accessibility services. + // Therefore we ignore packages from linked profiles. + if (userId != mManagerService.getCurrentUserIdLocked()) { + return; + } + mManagerService.onPackageRemovedLocked(packageName); + } + } + + /** + * Handles instances in which a package or packages have forcibly stopped. + * + * @param intent intent containing package event information. + * @param uid linux process user id (different from Android user id). + * @param packages array of package names that have stopped. + * @param doit whether to try and handle the stop or just log the trace. + * + * @return {@code true} if doit == {@code false} + * and at least one of the provided packages is enabled. + * In any other case, returns {@code false}. + * This is to indicate whether further action is necessary. + */ + @Override + public boolean onHandleForceStop(Intent intent, String[] packages, + int uid, boolean doit) { + if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_PACKAGE_BROADCAST_RECEIVER)) { + mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop", + FLAGS_PACKAGE_BROADCAST_RECEIVER, + "intent=" + intent + ";packages=" + Arrays.toString(packages) + + ";uid=" + uid + ";doit=" + doit); + } + synchronized (mManagerService.getLock()) { + final int userId = getChangingUserId(); + // Only the profile parent can install accessibility services. + // Therefore we ignore packages from linked profiles. + if (userId != mManagerService.getCurrentUserIdLocked()) { + return false; + } + final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId); + + if (Flags.managerPackageMonitorLogicFix()) { + if (!doit) { + // if we're not handling the stop here, then we only need to know + // if any of the force-stopped packages are currently enabled. + return userState.mEnabledServices.stream().anyMatch( + (comp) -> Arrays.stream(packages).anyMatch( + (pkg) -> pkg.equals(comp.getPackageName())) + ); + } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) { + mManagerService.onUserStateChangedLocked(userState); + } + return false; + } else { + // this old logic did not properly indicate when base packageMonitor's routine + // should handle stopping the package. + if (doit && mManagerService.onPackagesForceStoppedLocked(packages, userState)) { + mManagerService.onUserStateChangedLocked(userState); + return false; + } else { + return true; + } + } + } + } + } + void sendPendingWindowStateChangedEventsForAvailableWindowLocked(int windowId) { final int eventSize = mSendWindowStateChangedEventRunnables.size(); for (int i = eventSize - 1; i >= 0; i--) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java index 468b9ab5710e..219b788448e8 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java @@ -36,6 +36,7 @@ import android.view.inputmethod.InlineSuggestionsResponse; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInlineSuggestionsResponseCallback; +import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.server.autofill.ui.InlineFillUi; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -376,8 +377,8 @@ final class AutofillInlineSuggestionsRequestSession { /** * Internal implementation of {@link IInlineSuggestionsRequestCallback}. */ - private static final class InlineSuggestionsRequestCallbackImpl extends - IInlineSuggestionsRequestCallback.Stub { + private static final class InlineSuggestionsRequestCallbackImpl + implements InlineSuggestionsRequestCallback { private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession; @@ -388,7 +389,7 @@ final class AutofillInlineSuggestionsRequestSession { @BinderThread @Override - public void onInlineSuggestionsUnsupported() throws RemoteException { + public void onInlineSuggestionsUnsupported() { if (sDebug) Slog.d(TAG, "onInlineSuggestionsUnsupported() called."); final AutofillInlineSuggestionsRequestSession session = mSession.get(); if (session != null) { @@ -412,7 +413,7 @@ final class AutofillInlineSuggestionsRequestSession { } @Override - public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException { + public void onInputMethodStartInput(AutofillId imeFieldId) { if (sVerbose) Slog.v(TAG, "onInputMethodStartInput() received on " + imeFieldId); final AutofillInlineSuggestionsRequestSession session = mSession.get(); if (session != null) { @@ -423,7 +424,7 @@ final class AutofillInlineSuggestionsRequestSession { } @Override - public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException { + public void onInputMethodShowInputRequested(boolean requestResult) { if (sVerbose) { Slog.v(TAG, "onInputMethodShowInputRequested() received: " + requestResult); } @@ -454,7 +455,7 @@ final class AutofillInlineSuggestionsRequestSession { } @Override - public void onInputMethodFinishInput() throws RemoteException { + public void onInputMethodFinishInput() { if (sVerbose) Slog.v(TAG, "onInputMethodFinishInput() received"); final AutofillInlineSuggestionsRequestSession session = mSession.get(); if (session != null) { @@ -466,7 +467,7 @@ final class AutofillInlineSuggestionsRequestSession { @BinderThread @Override - public void onInlineSuggestionsSessionInvalidated() throws RemoteException { + public void onInlineSuggestionsSessionInvalidated() { if (sDebug) Slog.d(TAG, "onInlineSuggestionsSessionInvalidated() called."); final AutofillInlineSuggestionsRequestSession session = mSession.get(); if (session != null) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 07b16c53ffe8..a8b123518db3 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1168,6 +1168,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } + @GuardedBy("mLock") @Nullable private AutofillValue findValueFromThisSessionOnlyLocked(@NonNull AutofillId autofillId) { final ViewState state = mViewStates.get(autofillId); @@ -1176,9 +1177,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } AutofillValue value = state.getCurrentValue(); + + // Some app clears the form before navigating to another activities. In this case, use the + // cached value instead. + if (value == null || value.isEmpty()) { + AutofillValue candidateSaveValue = state.getCandidateSaveValue(); + if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { + if (sDebug) { + Slog.d(TAG, "findValueLocked(): current value for " + autofillId + + " is empty, using candidateSaveValue instead."); + } + return candidateSaveValue; + } + } if (value == null) { - if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + autofillId); - value = getValueFromContextsLocked(autofillId); + if (sDebug) { + Slog.d(TAG, "findValueLocked(): no current value for " + autofillId + + ", checking value from previous fill contexts"); + value = getValueFromContextsLocked(autofillId); + } } return value; } @@ -3717,19 +3734,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState AutofillValue value = viewState.getCurrentValue(); if (value == null || value.isEmpty()) { - final AutofillValue initialValue = getValueFromContextsLocked(id); - if (initialValue != null) { - if (sDebug) { - Slog.d(TAG, "Value of required field " + id + " didn't change; " - + "using initial value (" + initialValue + ") instead"); + // Some apps clear the form before navigating to other activities. + // If current value is empty, consider fall back to last cached + // non-empty result first. + final AutofillValue candidateSaveValue = + viewState.getCandidateSaveValue(); + if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { + if (sVerbose) { + Slog.v(TAG, "current value is empty, using cached last non-empty " + + "value instead"); } - value = initialValue; + value = candidateSaveValue; } else { - if (sDebug) { - Slog.d(TAG, "empty value for required " + id ); + // If candidate save value is also empty, consider falling back to initial + // value in context. + final AutofillValue initialValue = getValueFromContextsLocked(id); + if (initialValue != null) { + if (sDebug) { + Slog.d(TAG, "Value of required field " + id + " didn't change; " + + "using initial value (" + initialValue + ") instead"); + } + value = initialValue; + } else { + if (sDebug) { + Slog.d(TAG, "empty value for required " + id); + } + allRequiredAreNotEmpty = false; + break; } - allRequiredAreNotEmpty = false; - break; } } @@ -3801,7 +3833,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState continue; } if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) { - final AutofillValue currentValue = viewState.getCurrentValue(); + AutofillValue currentValue = viewState.getCurrentValue(); + if (currentValue == null || currentValue.isEmpty()) { + // Some apps clear the form before navigating to other activities. + // If current value is empty, consider fall back to last cached + // non-empty result instead. + final AutofillValue candidateSaveValue = + viewState.getCandidateSaveValue(); + if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { + if (sVerbose) { + Slog.v(TAG, "current value is empty, using cached last " + + "non-empty value instead"); + } + currentValue = candidateSaveValue; + } + } final AutofillValue value = getSanitizedValue(sanitizers, id, currentValue); if (value == null) { if (sDebug) { @@ -4714,14 +4760,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value, ViewState viewState, int flags) { + // Cache the last non-empty value for save purpose. Some apps clear the form before + // navigating to other activities. if (mIgnoreViewStateResetToEmpty && (value == null || value.isEmpty()) && viewState.getCurrentValue() != null && viewState.getCurrentValue().isText() && viewState.getCurrentValue().getTextValue() != null && viewState.getCurrentValue().getTextValue().length() > 1) { if (sVerbose) { - Slog.v(TAG, "Ignoring view state reset to empty on id " + id); + Slog.v(TAG, "value is resetting to empty, caching the last non-empty value"); } - return; + viewState.setCandidateSaveValue(viewState.getCurrentValue()); + } else { + viewState.setCandidateSaveValue(null); } final String textValue; if (value == null || !value.isText()) { diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java index fec5aa531cdd..6ad0eb6ff54f 100644 --- a/services/autofill/java/com/android/server/autofill/ViewState.java +++ b/services/autofill/java/com/android/server/autofill/ViewState.java @@ -106,6 +106,15 @@ final class ViewState { */ private FillResponse mSecondaryFillResponse; private AutofillValue mCurrentValue; + + /** + * Some apps clear the form before navigating to another activity. The mCandidateSaveValue + * caches the value when a field with string longer than 2 characters are cleared. + * + * When showing save UI, if mCurrentValue of view state is empty, session would use + * mCandidateSaveValue to prompt save instead. + */ + private AutofillValue mCandidateSaveValue; private AutofillValue mAutofilledValue; private AutofillValue mSanitizedValue; private Rect mVirtualBounds; @@ -139,6 +148,18 @@ final class ViewState { mCurrentValue = value; } + /** + * Gets the candidate save value of the view. + */ + @Nullable + AutofillValue getCandidateSaveValue() { + return mCandidateSaveValue; + } + + void setCandidateSaveValue(AutofillValue value) { + mCandidateSaveValue = value; + } + @Nullable AutofillValue getAutofilledValue() { return mAutofilledValue; @@ -268,6 +289,9 @@ final class ViewState { if (mCurrentValue != null) { builder.append(", currentValue:" ).append(mCurrentValue); } + if (mCandidateSaveValue != null) { + builder.append(", candidateSaveValue:").append(mCandidateSaveValue); + } if (mAutofilledValue != null) { builder.append(", autofilledValue:" ).append(mAutofilledValue); } @@ -302,6 +326,9 @@ final class ViewState { if (mAutofilledValue != null) { pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue); } + if (mCandidateSaveValue != null) { + pw.print(prefix); pw.print("candidateSaveValue:"); pw.println(mCandidateSaveValue); + } if (mSanitizedValue != null) { pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue); } diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING index 340bc327fc7f..caa877c2b964 100644 --- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING +++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING @@ -81,9 +81,6 @@ "name": "CtsPermissionTestCases", "options": [ { - "include-filter": "android.permissionmultidevice.cts.DeviceAwarePermissionGrantTest" - }, - { "include-filter": "android.permission.cts.DevicePermissionsTest" }, { @@ -93,6 +90,14 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsPermissionMultiDeviceTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] } ] } diff --git a/services/core/Android.bp b/services/core/Android.bp index dc1155a484fb..0fdf6d0fd507 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -260,7 +260,6 @@ java_library_static { "connectivity_flags_lib", "dreams_flags_lib", "aconfig_new_storage_flags_lib", - "aconfigd_java_proto_lib", ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index a61925732256..c1d59db1f0c7 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -138,6 +138,11 @@ public class PackageWatchdog { static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10); + // Time needed to apply mitigation + private static final String MITIGATION_WINDOW_MS = + "persist.device_config.configuration.mitigation_window_ms"; + private static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5); + // Threshold level at which or above user might experience significant disruption. private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD = "persist.device_config.configuration.major_user_impact_level_threshold"; @@ -210,6 +215,9 @@ public class PackageWatchdog { @GuardedBy("mLock") private boolean mSyncRequired = false; + @GuardedBy("mLock") + private long mLastMitigation = -1000000; + @FunctionalInterface @VisibleForTesting interface SystemClock { @@ -400,6 +408,14 @@ public class PackageWatchdog { Slog.w(TAG, "Could not resolve a list of failing packages"); return; } + synchronized (mLock) { + final long now = mSystemClock.uptimeMillis(); + if (now >= mLastMitigation + && (now - mLastMitigation) < getMitigationWindowMs()) { + Slog.i(TAG, "Skipping onPackageFailure mitigation"); + return; + } + } mLongTaskHandler.post(() -> { synchronized (mLock) { if (mAllObservers.isEmpty()) { @@ -500,10 +516,17 @@ public class PackageWatchdog { int currentObserverImpact, int mitigationCount) { if (currentObserverImpact < getUserImpactLevelLimit()) { + synchronized (mLock) { + mLastMitigation = mSystemClock.uptimeMillis(); + } currentObserverToNotify.execute(versionedPackage, failureReason, mitigationCount); } } + private long getMitigationWindowMs() { + return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS); + } + /** * Called when the system server boots. If the system server is detected to be in a boot loop, diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index d04dbc2f417e..d9e6186639ce 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -870,6 +870,7 @@ public final class PinnerService extends SystemService { } synchronized (this) { pinnedApp.mFiles.add(pf); + mPinnedFiles.put(pf.fileName, pf); } apkPinSizeLimit -= pf.bytesPinned; @@ -1341,18 +1342,6 @@ public final class PinnerService extends SystemService { public List<PinnedFileStat> getPinnerStats() { ArrayList<PinnedFileStat> stats = new ArrayList<>(); - Collection<PinnedApp> pinnedApps; - synchronized(this) { - pinnedApps = mPinnedApps.values(); - } - for (PinnedApp pinnedApp : pinnedApps) { - for (PinnedFile pf : pinnedApp.mFiles) { - PinnedFileStat stat = - new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName); - stats.add(stat); - } - } - Collection<PinnedFile> pinnedFiles; synchronized(this) { pinnedFiles = mPinnedFiles.values(); diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index a508ebfdb950..8c1bb3b0cb71 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -1783,7 +1783,13 @@ public class SystemConfig { String gidStr = parser.getAttributeValue(null, "gid"); if (gidStr != null) { int gid = Process.getGidForName(gidStr); - perm.gids = appendInt(perm.gids, gid); + if (gid != -1) { + perm.gids = appendInt(perm.gids, gid); + } else { + Slog.w(TAG, "<group> with unknown gid \"" + + gidStr + " for permission " + name + " in " + + parser.getPositionDescription()); + } } else { Slog.w(TAG, "<group> without gid at " + parser.getPositionDescription()); diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 5933639f2317..a3b6d806c6ce 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -134,6 +134,13 @@ }, { "name": "CtsSuspendAppsTestCases" + }, + { + "name": "CtsWindowManagerBackgroundActivityTestCases", + "file_patterns": [ + "Background.*\\.java", + "Activity.*\\.java" + ] } ], "presubmit-large": [ @@ -187,6 +194,18 @@ }, { "name": "SelinuxFrameworksTests" + }, + { + "name": "WmTests", + "file_patterns": [ + "Background.*\\.java", + "Activity.*\\.java" + ], + "options": [ + { + "include-filter": "com.android.server.wm.BackgroundActivityStart*" + } + ] } - ] + ] } diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java index 2812233815a6..42391a55fed6 100644 --- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java +++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java @@ -24,7 +24,6 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.ParcelFileDescriptor; import android.util.Slog; @@ -59,10 +58,10 @@ public class WallpaperUpdateReceiver extends BroadcastReceiver { return; } if (DEBUG) Slog.d(TAG, "Set customized default_wallpaper."); - Bitmap blank = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); - // set a blank wallpaper to force a redraw of default_wallpaper - wallpaperManager.setBitmap(blank); - wallpaperManager.setResource(com.android.internal.R.drawable.default_wallpaper); + // Check if it is not a live wallpaper set + if (wallpaperManager.getWallpaperInfo() == null) { + wallpaperManager.clearWallpaper(); + } } catch (Exception e) { Slog.w(TAG, "Failed to customize system wallpaper." + e); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 008d7b20c5fd..316937c8280a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12334,8 +12334,8 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.SYSTEM_ADJ, ProcessList.PERSISTENT_PROC_ADJ, ProcessList.PERSISTENT_SERVICE_ADJ, ProcessList.FOREGROUND_APP_ADJ, ProcessList.VISIBLE_APP_ADJ, - ProcessList.PERCEPTIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ, - ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, + ProcessList.PERCEPTIBLE_APP_ADJ, + ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ, ProcessList.BACKUP_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ, ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ, ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.CACHED_APP_MIN_ADJ @@ -12343,7 +12343,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final String[] DUMP_MEM_OOM_LABEL = new String[] { "Native", "System", "Persistent", "Persistent Service", "Foreground", - "Visible", "Perceptible", "Perceptible Low", "Perceptible Medium", + "Visible", "Perceptible", "Perceptible Medium", "Perceptible Low", "Backup", "Heavy Weight", "A Services", "Home", "Previous", "B Services", "Cached" @@ -12351,7 +12351,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final String[] DUMP_MEM_OOM_COMPACT_LABEL = new String[] { "native", "sys", "pers", "persvc", "fore", - "vis", "percept", "perceptl", "perceptm", + "vis", "percept", "perceptm", "perceptl", "backup", "heavy", "servicea", "home", "prev", "serviceb", "cached" @@ -19975,6 +19975,26 @@ public class ActivityManagerService extends IActivityManager.Stub addStartInfoTimestampInternal(key, timestampNs, userId, uid); } + + @Override + public void killApplicationSync(String pkgName, int appId, int userId, + String reason, int exitInfoReason) { + if (pkgName == null) { + return; + } + // Make sure the uid is valid. + if (appId < 0) { + Slog.w(TAG, "Invalid appid specified for pkg : " + pkgName); + return; + } + synchronized (ActivityManagerService.this) { + ActivityManagerService.this.forceStopPackageLocked(pkgName, appId, + /* callerWillRestart= */ false, /*purgeCache= */ false, + /* doit= */ true, /* evenPersistent= */ false, + /* uninstalling= */ false, /* packageStateStopped= */ false, + userId, reason, exitInfoReason); + } + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index a182a106ed8c..bbd432340e8f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -130,6 +130,7 @@ import com.android.server.am.nano.Capabilities; import com.android.server.am.nano.Capability; import com.android.server.am.nano.FrameworkCapability; import com.android.server.am.nano.VMCapability; +import com.android.server.am.nano.VMInfo; import com.android.server.compat.PlatformCompat; import com.android.server.pm.UserManagerInternal; import com.android.server.utils.Slogf; @@ -460,6 +461,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return -1; } } + String vmName = System.getProperty("java.vm.name", "?"); + String vmVersion = System.getProperty("java.vm.version", "?"); if (outputAsProtobuf) { Capabilities capabilities = new Capabilities(); @@ -486,6 +489,11 @@ final class ActivityManagerShellCommand extends ShellCommand { capabilities.frameworkCapabilities[i] = cap; } + VMInfo vmInfo = new VMInfo(); + vmInfo.name = vmName; + vmInfo.version = vmVersion; + capabilities.vmInfo = vmInfo; + try { getRawOutputStream().write(Capabilities.toByteArray(capabilities)); } catch (IOException e) { @@ -505,6 +513,8 @@ final class ActivityManagerShellCommand extends ShellCommand { for (String capability : Debug.getFeatureList()) { pw.println("framework:" + capability); } + pw.println("vm_name:" + vmName); + pw.println("vm_version:" + vmVersion); } return 0; } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 4f841497b201..58732fd200d2 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -159,6 +159,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * All information we are collecting about things that can happen that impact @@ -409,26 +411,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel); final boolean resetOnUnplugAfterSignificantCharge = context.getResources().getBoolean( com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge); - final long powerStatsThrottlePeriodCpu = context.getResources().getInteger( - com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodCpu); - final long powerStatsThrottlePeriodMobileRadio = context.getResources().getInteger( - com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodMobileRadio); - final long powerStatsThrottlePeriodWifi = context.getResources().getInteger( - com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodWifi); - mBatteryStatsConfig = + BatteryStatsImpl.BatteryStatsConfig.Builder batteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder() .setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel) - .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge) - .setPowerStatsThrottlePeriodMillis( - BatteryConsumer.POWER_COMPONENT_CPU, - powerStatsThrottlePeriodCpu) - .setPowerStatsThrottlePeriodMillis( - BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, - powerStatsThrottlePeriodMobileRadio) - .setPowerStatsThrottlePeriodMillis( - BatteryConsumer.POWER_COMPONENT_WIFI, - powerStatsThrottlePeriodWifi) - .build(); + .setResetOnUnplugAfterSignificantCharge( + resetOnUnplugAfterSignificantCharge); + setPowerStatsThrottlePeriods(batteryStatsConfigBuilder, context.getResources().getString( + com.android.internal.R.string.config_powerStatsThrottlePeriods)); + mBatteryStatsConfig = batteryStatsConfigBuilder.build(); mPowerStatsUidResolver = new PowerStatsUidResolver(); mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile, @@ -515,6 +505,26 @@ public final class BatteryStatsService extends IBatteryStats.Stub return config; } + private void setPowerStatsThrottlePeriods(BatteryStatsImpl.BatteryStatsConfig.Builder builder, + String configString) { + Matcher matcher = Pattern.compile("([^:]+):(\\d+)\\s*").matcher(configString); + while (matcher.find()) { + String powerComponentName = matcher.group(1); + long throttlePeriod; + try { + throttlePeriod = Long.parseLong(matcher.group(2)); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException( + "Invalid config_powerStatsThrottlePeriods format: " + configString); + } + if (powerComponentName.equals("*")) { + builder.setDefaultPowerStatsThrottlePeriodMillis(throttlePeriod); + } else { + builder.setPowerStatsThrottlePeriodMillis(powerComponentName, throttlePeriod); + } + } + } + /** * Creates an instance of BatteryStatsService and restores data from stored state. */ diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index da45a7727faf..8d7a1c9f8228 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -18,6 +18,10 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.START_SUCCESS; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -389,13 +393,20 @@ public final class PendingIntentRecord extends IIntentSender.Stub { private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller( @Nullable Bundle options, int callingUid, @Nullable String callingPackage) { - if (options == null || !options.containsKey( - ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) { + if (options == null) { return getDefaultBackgroundStartPrivileges(callingUid, callingPackage); } - return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED) - ? BackgroundStartPrivileges.ALLOW_BAL - : BackgroundStartPrivileges.NONE; + switch (options.getInt(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)) { + case MODE_BACKGROUND_ACTIVITY_START_DENIED: + return BackgroundStartPrivileges.NONE; + case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: + return getDefaultBackgroundStartPrivileges(callingUid, callingPackage); + case MODE_BACKGROUND_ACTIVITY_START_ALLOWED: + case MODE_BACKGROUND_ACTIVITY_START_COMPAT: + default: + return BackgroundStartPrivileges.ALLOW_BAL; + } } /** diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 6779f7a37f20..a5449a0f0431 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -37,6 +37,7 @@ import static android.os.Process.startWebView; import static android.system.OsConstants.EAGAIN; import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit; +import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxInputSelector; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; @@ -2065,11 +2066,15 @@ public final class ProcessList { } } - return app.info.seInfo - + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser) + extraInfo; + if (selinuxSdkSandboxInputSelector()) { + return app.info.seInfo + extraInfo + TextUtils.emptyIfNull(app.info.seInfoUser); + } else { + return app.info.seInfo + + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser) + + extraInfo; + } } - @GuardedBy("mService") boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal, diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 827db579f111..5793758d7a6e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -29,6 +29,8 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -262,11 +264,11 @@ public class SettingsToPropertiesMapper { Uri settingUri = Settings.Global.getUriFor(globalSetting); String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting); if (settingUri == null) { - log("setting uri is null for globalSetting " + globalSetting); + logErr("setting uri is null for globalSetting " + globalSetting); continue; } if (propName == null) { - log("invalid prop name for globalSetting " + globalSetting); + logErr("invalid prop name for globalSetting " + globalSetting); continue; } @@ -294,7 +296,7 @@ public class SettingsToPropertiesMapper { for (String key : properties.getKeyset()) { String propertyName = makePropertyName(scope, key); if (propertyName == null) { - log("unable to construct system property for " + scope + "/" + logErr("unable to construct system property for " + scope + "/" + key); return; } @@ -306,7 +308,7 @@ public class SettingsToPropertiesMapper { // sys prop slot can be removed. String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key); if (aconfigPropertyName == null) { - log("unable to construct system property for " + scope + "/" + logErr("unable to construct system property for " + scope + "/" + key); return; } @@ -324,7 +326,7 @@ public class SettingsToPropertiesMapper { for (String key : properties.getKeyset()) { String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key); if (aconfigPropertyName == null) { - log("unable to construct system property for " + scope + "/" + logErr("unable to construct system property for " + scope + "/" + key); return; } @@ -354,7 +356,7 @@ public class SettingsToPropertiesMapper { if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { - log("unable to construct system property for " + actualNamespace + logErr("unable to construct system property for " + actualNamespace + "/" + flagName); continue; } @@ -383,18 +385,18 @@ public class SettingsToPropertiesMapper { /** * apply flag local override in aconfig new storage - * @param props - * @return aconfigd socket return + * @param requests: request proto output stream + * @return aconfigd socket return as proto input stream */ - public static StorageReturnMessages sendAconfigdRequests(StorageRequestMessages requests) { + static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) { // connect to aconfigd socket LocalSocket client = new LocalSocket(); try{ client.connect(new LocalSocketAddress( "aconfigd", LocalSocketAddress.Namespace.RESERVED)); - log("connected to aconfigd socket"); + Slog.d(TAG, "connected to aconfigd socket"); } catch (IOException ioe) { - log("failed to connect to aconfigd socket", ioe); + logErr("failed to connect to aconfigd socket", ioe); return null; } @@ -404,43 +406,93 @@ public class SettingsToPropertiesMapper { inputStream = new DataInputStream(client.getInputStream()); outputStream = new DataOutputStream(client.getOutputStream()); } catch (IOException ioe) { - log("failed to get local socket iostreams", ioe); + logErr("failed to get local socket iostreams", ioe); return null; } // send requests try { - byte[] requests_bytes = requests.toByteArray(); + byte[] requests_bytes = requests.getBytes(); outputStream.writeInt(requests_bytes.length); outputStream.write(requests_bytes, 0, requests_bytes.length); - log(requests.getMsgsCount() + " flag override requests sent to aconfigd"); + Slog.d(TAG, "flag override requests sent to aconfigd"); } catch (IOException ioe) { - log("failed to send requests to aconfigd", ioe); + logErr("failed to send requests to aconfigd", ioe); return null; } // read return - StorageReturnMessages return_msgs = null; try { int num_bytes = inputStream.readInt(); - byte[] buffer = new byte[num_bytes]; - inputStream.read(buffer, 0, num_bytes); - return_msgs = StorageReturnMessages.parseFrom(buffer); - log(return_msgs.getMsgsCount() + " acknowledgement received from aconfigd"); + ProtoInputStream returns = new ProtoInputStream(inputStream); + Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd"); + return returns; } catch (IOException ioe) { - log("failed to read requests return from aconfigd", ioe); + logErr("failed to read requests return from aconfigd", ioe); return null; } + } + + /** + * serialize a flag override request + * @param proto + */ + static void writeFlagOverrideRequest( + ProtoOutputStream proto, String packageName, String flagName, String flagValue, + boolean isLocal) { + long msgsToken = proto.start(StorageRequestMessages.MSGS); + long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE); + proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName); + proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName); + proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue); + proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal); + proto.end(msgToken); + proto.end(msgsToken); + } - return return_msgs; + /** + * deserialize a flag input proto stream and log + * @param proto + */ + static void parseAndLogAconfigdReturn(ProtoInputStream proto) throws IOException { + while (true) { + switch (proto.nextField()) { + case (int) StorageReturnMessages.MSGS: + long msgsToken = proto.start(StorageReturnMessages.MSGS); + switch (proto.nextField()) { + case (int) StorageReturnMessage.FLAG_OVERRIDE_MESSAGE: + Slog.d(TAG, "successfully handled override requests"); + long msgToken = proto.start(StorageReturnMessage.FLAG_OVERRIDE_MESSAGE); + proto.end(msgToken); + break; + case (int) StorageReturnMessage.ERROR_MESSAGE: + String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE); + Slog.d(TAG, "override request failed: " + errmsg); + break; + case ProtoInputStream.NO_MORE_FIELDS: + break; + default: + logErr("invalid message type, expecting only flag override return or error message"); + break; + } + proto.end(msgsToken); + break; + case ProtoInputStream.NO_MORE_FIELDS: + return; + default: + logErr("invalid message type, expect storage return message"); + break; + } + } } /** * apply flag local override in aconfig new storage * @param props */ - public static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) { - StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder(); + static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) { + int num_requests = 0; + ProtoOutputStream requests = new ProtoOutputStream(); for (String flagName : props.getKeyset()) { String flagValue = props.getString(flagName, null); if (flagName == null || flagValue == null) { @@ -449,32 +501,35 @@ public class SettingsToPropertiesMapper { int idx = flagName.indexOf(":"); if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { - log("invalid local flag override: " + flagName); + logErr("invalid local flag override: " + flagName); continue; } String actualNamespace = flagName.substring(0, idx); String fullFlagName = flagName.substring(idx+1); idx = fullFlagName.lastIndexOf("."); if (idx == -1) { - log("invalid flag name: " + fullFlagName); + logErr("invalid flag name: " + fullFlagName); continue; } String packageName = fullFlagName.substring(0, idx); String realFlagName = fullFlagName.substring(idx+1); + writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true); + ++num_requests; + } - StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder = - StorageRequestMessage.FlagOverrideMessage.newBuilder(); - override_msg_builder.setPackageName(packageName); - override_msg_builder.setFlagName(realFlagName); - override_msg_builder.setFlagValue(flagValue); - override_msg_builder.setIsLocal(true); + if (num_requests == 0) { + return; + } + + // send requests to aconfigd and obtain the return byte buffer + ProtoInputStream returns = sendAconfigdRequests(requests); - StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder(); - request_builder.setFlagOverrideMessage(override_msg_builder.build()); - requests_builder.addMsgs(request_builder.build()); + // deserialize back using proto input stream + try { + parseAndLogAconfigdReturn(returns); + } catch (IOException ioe) { + logErr("failed to parse aconfigd return", ioe); } - StorageRequestMessages requests = requests_builder.build(); - StorageReturnMessages acks = sendAconfigdRequests(requests); } public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -517,7 +572,7 @@ public class SettingsToPropertiesMapper { for (String property_name : property_names) { String[] segments = property_name.split("\\."); if (segments.length < 3) { - log("failed to extract category name from property " + property_name); + logErr("failed to extract category name from property " + property_name); continue; } categories.add(segments[2]); @@ -545,14 +600,16 @@ public class SettingsToPropertiesMapper { return propertyName; } + /** * stage flags in aconfig new storage * @param propsToStage */ @VisibleForTesting static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) { - // create storage request proto - StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder(); + // write aconfigd requests proto to proto output stream + int num_requests = 0; + ProtoOutputStream requests = new ProtoOutputStream(); for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) { String actualNamespace = entry.getKey(); HashMap<String, String> flagValuesToStage = entry.getValue(); @@ -560,26 +617,29 @@ public class SettingsToPropertiesMapper { String stagedValue = flagValuesToStage.get(fullFlagName); int idx = fullFlagName.lastIndexOf("."); if (idx == -1) { - log("invalid flag name: " + fullFlagName); + logErr("invalid flag name: " + fullFlagName); continue; } String packageName = fullFlagName.substring(0, idx); String flagName = fullFlagName.substring(idx+1); + writeFlagOverrideRequest(requests, packageName, flagName, stagedValue, false); + ++num_requests; + } + } - StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder = - StorageRequestMessage.FlagOverrideMessage.newBuilder(); - override_msg_builder.setPackageName(packageName); - override_msg_builder.setFlagName(flagName); - override_msg_builder.setFlagValue(stagedValue); - override_msg_builder.setIsLocal(false); + if (num_requests == 0) { + return; + } - StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder(); - request_builder.setFlagOverrideMessage(override_msg_builder.build()); - requests_builder.addMsgs(request_builder.build()); - } + // send requests to aconfigd and obtain the return + ProtoInputStream returns = sendAconfigdRequests(requests); + + // deserialize back using proto input stream + try { + parseAndLogAconfigdReturn(returns); + } catch (IOException ioe) { + logErr("failed to parse aconfigd return", ioe); } - StorageRequestMessages requests = requests_builder.build(); - StorageReturnMessages acks = sendAconfigdRequests(requests); } /** @@ -620,7 +680,7 @@ public class SettingsToPropertiesMapper { for (String flagName : properties.getKeyset()) { int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER); if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { - log("invalid staged flag: " + flagName); + logErr("invalid staged flag: " + flagName); continue; } String actualNamespace = flagName.substring(0, idx); @@ -671,7 +731,7 @@ public class SettingsToPropertiesMapper { } value = ""; } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) { - log("key=" + key + " value=" + value + " exceeds system property max length."); + logErr("key=" + key + " value=" + value + " exceeds system property max length."); return; } @@ -681,11 +741,11 @@ public class SettingsToPropertiesMapper { // Failure to set a property can be caused by SELinux denial. This usually indicates // that the property wasn't allowlisted in sepolicy. // No need to report it on all user devices, only on debug builds. - log("Unable to set property " + key + " value '" + value + "'", e); + logErr("Unable to set property " + key + " value '" + value + "'", e); } } - private static void log(String msg, Exception e) { + private static void logErr(String msg, Exception e) { if (Build.IS_DEBUGGABLE) { Slog.wtf(TAG, msg, e); } else { @@ -693,7 +753,7 @@ public class SettingsToPropertiesMapper { } } - private static void log(String msg) { + private static void logErr(String msg) { if (Build.IS_DEBUGGABLE) { Slog.wtf(TAG, msg); } else { @@ -711,7 +771,7 @@ public class SettingsToPropertiesMapper { br.close(); } catch (IOException ioe) { - log("failed to read file " + RESET_RECORD_FILE_PATH, ioe); + logErr("failed to read file " + RESET_RECORD_FILE_PATH, ioe); } return content; } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 875fd059d612..0fcdf198eece 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -798,7 +798,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return; } mDisplayStateController.overrideDozeScreenState(displayState, reason); - sendUpdatePowerState(); + updatePowerState(); }, mClock.uptimeMillis()); } diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 0bb93a96d9df..3883604b7134 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -78,6 +78,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.accessibility.Flags; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; @@ -356,6 +357,11 @@ public final class ColorDisplayService extends SystemService { case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER: onAccessibilityDaltonizerChanged(); break; + case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL: + if (Flags.enableColorCorrectionSaturation()) { + onAccessibilityDaltonizerChanged(); + } + break; case Secure.DISPLAY_WHITE_BALANCE_ENABLED: updateDisplayWhiteBalanceStatus(); break; @@ -398,6 +404,11 @@ public final class ColorDisplayService extends SystemService { false /* notifyForDescendants */, mContentObserver, mCurrentUser); cr.registerContentObserver(Secure.getUriFor(Secure.REDUCE_BRIGHT_COLORS_LEVEL), false /* notifyForDescendants */, mContentObserver, mCurrentUser); + if (Flags.enableColorCorrectionSaturation()) { + cr.registerContentObserver( + Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL), + false /* notifyForDescendants */, mContentObserver, mCurrentUser); + } // Apply the accessibility settings first, since they override most other settings. onAccessibilityInversionChanged(); @@ -597,21 +608,31 @@ public final class ColorDisplayService extends SystemService { if (mCurrentUser == UserHandle.USER_NULL) { return; } + var contentResolver = getContext().getContentResolver(); final int daltonizerMode = isAccessiblityDaltonizerEnabled() - ? Secure.getIntForUser(getContext().getContentResolver(), - Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, - AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser) + ? Secure.getIntForUser(contentResolver, + Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, + AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser) : AccessibilityManager.DALTONIZER_DISABLED; + int saturation = NOT_SET; + if (Flags.enableColorCorrectionSaturation()) { + saturation = Secure.getIntForUser( + contentResolver, + Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + NOT_SET, + mCurrentUser); + } + final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class); if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) { // Monochromacy isn't supported by the native Daltonizer implementation; use grayscale. dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE, MATRIX_GRAYSCALE); - dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED); + dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED, saturation); } else { dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE, null); - dtm.setDaltonizerMode(daltonizerMode); + dtm.setDaltonizerMode(daltonizerMode, saturation); } } diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java index 0dba9e1b0af1..a76c427bec0e 100644 --- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java +++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java @@ -16,6 +16,7 @@ package com.android.server.display.color; +import android.annotation.IntRange; import android.app.ActivityTaskManager; import android.hardware.display.ColorDisplayManager; import android.opengl.Matrix; @@ -111,9 +112,15 @@ public class DisplayTransformManager { /** * Lock used for synchronize access to {@link #mDaltonizerMode}. */ - private final Object mDaltonizerModeLock = new Object(); + @VisibleForTesting + final Object mDaltonizerModeLock = new Object(); + @VisibleForTesting + @GuardedBy("mDaltonizerModeLock") + int mDaltonizerMode = -1; + + @VisibleForTesting @GuardedBy("mDaltonizerModeLock") - private int mDaltonizerMode = -1; + int mDaltonizerLevel = -1; private static final IBinder sFlinger = ServiceManager.getService(SURFACE_FLINGER); @@ -168,12 +175,15 @@ public class DisplayTransformManager { * various types of color blindness. * * @param mode the new Daltonization mode, or -1 to disable + * @param level the level of saturation for color correction [-1,10] inclusive. -1 for when + * it is not set. */ - public void setDaltonizerMode(int mode) { + public void setDaltonizerMode(int mode, @IntRange(from = -1, to = 10) int level) { synchronized (mDaltonizerModeLock) { - if (mDaltonizerMode != mode) { + if (mDaltonizerMode != mode || mDaltonizerLevel != level) { mDaltonizerMode = mode; - applyDaltonizerMode(mode); + mDaltonizerLevel = level; + applyDaltonizerMode(mode, level); } } } @@ -223,10 +233,11 @@ public class DisplayTransformManager { /** * Propagates the provided Daltonization mode to the SurfaceFlinger. */ - private static void applyDaltonizerMode(int mode) { + private static void applyDaltonizerMode(int mode, int level) { final Parcel data = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); data.writeInt(mode); + data.writeInt(level); try { sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0); } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/display/config/RefreshRateData.java b/services/core/java/com/android/server/display/config/RefreshRateData.java index b186fd57e31f..d7ed904e398d 100644 --- a/services/core/java/com/android/server/display/config/RefreshRateData.java +++ b/services/core/java/com/android/server/display/config/RefreshRateData.java @@ -20,6 +20,10 @@ import android.annotation.Nullable; import android.content.res.Resources; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Collections; +import java.util.List; /** * RefreshRates config for display @@ -58,12 +62,17 @@ public class RefreshRateData { */ public final int defaultRefreshRateInHbmSunlight; + public final List<SupportedModeData> lowPowerSupportedModes; + + @VisibleForTesting public RefreshRateData(int defaultRefreshRate, int defaultPeakRefreshRate, - int defaultRefreshRateInHbmHdr, int defaultRefreshRateInHbmSunlight) { + int defaultRefreshRateInHbmHdr, int defaultRefreshRateInHbmSunlight, + List<SupportedModeData> lowPowerSupportedModes) { this.defaultRefreshRate = defaultRefreshRate; this.defaultPeakRefreshRate = defaultPeakRefreshRate; this.defaultRefreshRateInHbmHdr = defaultRefreshRateInHbmHdr; this.defaultRefreshRateInHbmSunlight = defaultRefreshRateInHbmSunlight; + this.lowPowerSupportedModes = Collections.unmodifiableList(lowPowerSupportedModes); } @@ -71,9 +80,10 @@ public class RefreshRateData { public String toString() { return "RefreshRateData {" + "defaultRefreshRate: " + defaultRefreshRate - + "defaultPeakRefreshRate: " + defaultPeakRefreshRate - + "defaultRefreshRateInHbmHdr: " + defaultRefreshRateInHbmHdr - + "defaultRefreshRateInHbmSunlight: " + defaultRefreshRateInHbmSunlight + + ", defaultPeakRefreshRate: " + defaultPeakRefreshRate + + ", defaultRefreshRateInHbmHdr: " + defaultRefreshRateInHbmHdr + + ", defaultRefreshRateInHbmSunlight: " + defaultRefreshRateInHbmSunlight + + ", lowPowerSupportedModes=" + lowPowerSupportedModes + "} "; } @@ -90,8 +100,13 @@ public class RefreshRateData { int defaultRefreshRateInHbmSunlight = loadDefaultRefreshRateInHbmSunlight( refreshRateConfigs, resources); + NonNegativeFloatToFloatMap modes = + refreshRateConfigs == null ? null : refreshRateConfigs.getLowPowerSupportedModes(); + List<SupportedModeData> lowPowerSupportedModes = SupportedModeData.load(modes); + return new RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate, - defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight); + defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight, + lowPowerSupportedModes); } private static int loadDefaultRefreshRate( diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java index 6ad13c3b8a40..8bfc4a3ad77a 100644 --- a/services/core/java/com/android/server/display/config/SensorData.java +++ b/services/core/java/com/android/server/display/config/SensorData.java @@ -24,7 +24,6 @@ import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.feature.DisplayManagerFlags; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -42,7 +41,7 @@ public class SensorData { public final String name; public final float minRefreshRate; public final float maxRefreshRate; - public final List<SupportedMode> supportedModes; + public final List<SupportedModeData> supportedModes; @VisibleForTesting public SensorData() { @@ -61,7 +60,7 @@ public class SensorData { @VisibleForTesting public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate, - List<SupportedMode> supportedModes) { + List<SupportedModeData> supportedModes) { this.type = type; this.name = name; this.minRefreshRate = minRefreshRate; @@ -214,26 +213,11 @@ public class SensorData { minRefreshRate = rr.getMinimum().floatValue(); maxRefreshRate = rr.getMaximum().floatValue(); } - ArrayList<SupportedMode> supportedModes = new ArrayList<>(); - NonNegativeFloatToFloatMap configSupportedModes = sensorDetails.getSupportedModes(); - if (configSupportedModes != null) { - for (NonNegativeFloatToFloatPoint supportedMode : configSupportedModes.getPoint()) { - supportedModes.add(new SupportedMode(supportedMode.getFirst().floatValue(), - supportedMode.getSecond().floatValue())); - } - } + List<SupportedModeData> supportedModes = SupportedModeData.load( + sensorDetails.getSupportedModes()); return new SensorData(sensorDetails.getType(), sensorDetails.getName(), minRefreshRate, maxRefreshRate, supportedModes); } - public static class SupportedMode { - public final float refreshRate; - public final float vsyncRate; - - public SupportedMode(float refreshRate, float vsyncRate) { - this.refreshRate = refreshRate; - this.vsyncRate = vsyncRate; - } - } } diff --git a/services/core/java/com/android/server/display/config/SupportedModeData.java b/services/core/java/com/android/server/display/config/SupportedModeData.java new file mode 100644 index 000000000000..3c82884e1024 --- /dev/null +++ b/services/core/java/com/android/server/display/config/SupportedModeData.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.config; + +import android.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Supported display mode data. Display mode is uniquely identified by refreshRate-vsync pair + */ +public class SupportedModeData { + public final float refreshRate; + public final float vsyncRate; + + public SupportedModeData(float refreshRate, float vsyncRate) { + this.refreshRate = refreshRate; + this.vsyncRate = vsyncRate; + } + + @Override + public String toString() { + return "SupportedModeData{" + + "refreshRate= " + refreshRate + + ", vsyncRate= " + vsyncRate + + '}'; + } + + static List<SupportedModeData> load(@Nullable NonNegativeFloatToFloatMap configMap) { + ArrayList<SupportedModeData> supportedModes = new ArrayList<>(); + if (configMap != null) { + for (NonNegativeFloatToFloatPoint supportedMode : configMap.getPoint()) { + supportedModes.add(new SupportedModeData(supportedMode.getFirst().floatValue(), + supportedMode.getSecond().floatValue())); + } + } + return supportedModes; + } +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 91bd80eb9037..846ee238499a 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -78,6 +78,7 @@ import com.android.server.LocalServices; import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; import com.android.server.display.config.RefreshRateData; +import com.android.server.display.config.SupportedModeData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.utils.AmbientFilter; @@ -451,15 +452,6 @@ public class DisplayModeDirector { return config != null && config.isVrrSupportEnabled(); } - private boolean isVrrSupportedByAnyDisplayLocked() { - for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) { - if (mDisplayDeviceConfigByDisplay.valueAt(i).isVrrSupportEnabled()) { - return true; - } - } - return false; - } - /** * Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate ranges. */ @@ -939,18 +931,44 @@ public class DisplayModeDirector { private final Uri mMatchContentFrameRateSetting = Settings.Secure.getUriFor(Settings.Secure.MATCH_CONTENT_FRAME_RATE); - private final boolean mVsynLowPowerVoteEnabled; + private final boolean mVsyncLowPowerVoteEnabled; private final boolean mPeakRefreshRatePhysicalLimitEnabled; private final Context mContext; + private final Handler mHandler; private float mDefaultPeakRefreshRate; private float mDefaultRefreshRate; + private boolean mIsLowPower = false; + + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + synchronized (mLock) { + updateLowPowerModeAllowedModesLocked(); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + mVotesStorage.updateVote(displayId, Vote.PRIORITY_LOW_POWER_MODE_MODES, + null); + } + + @Override + public void onDisplayChanged(int displayId) { + synchronized (mLock) { + updateLowPowerModeAllowedModesLocked(); + } + } + }; SettingsObserver(@NonNull Context context, @NonNull Handler handler, DisplayManagerFlags flags) { super(handler); mContext = context; - mVsynLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled(); + mHandler = handler; + mVsyncLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled(); mPeakRefreshRatePhysicalLimitEnabled = flags.isPeakRefreshRatePhysicalLimitEnabled(); // We don't want to load from the DeviceConfig while constructing since this leads to // a spike in the latency of DisplayManagerService startup. This happens because @@ -983,6 +1001,7 @@ public class DisplayModeDirector { UserHandle.USER_SYSTEM); cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/, this); + mInjector.registerDisplayListener(mDisplayListener, mHandler); float deviceConfigDefaultPeakRefresh = mConfigParameterProvider.getPeakRefreshRateDefault(); @@ -995,6 +1014,7 @@ public class DisplayModeDirector { updateLowPowerModeSettingLocked(); updateModeSwitchingTypeSettingLocked(); } + } public void setDefaultRefreshRate(float refreshRate) { @@ -1061,23 +1081,36 @@ public class DisplayModeDirector { } private void updateLowPowerModeSettingLocked() { - boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(), + mIsLowPower = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0; final Vote vote; - if (inLowPowerMode && mVsynLowPowerVoteEnabled && isVrrSupportedByAnyDisplayLocked()) { - vote = Vote.forSupportedRefreshRates(List.of( - new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, - /* vsyncRate= */ 240f), - new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, - /* vsyncRate= */ 60f) - )); - } else if (inLowPowerMode) { + if (mIsLowPower) { vote = Vote.forRenderFrameRates(0f, 60f); } else { vote = null; } - mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE, vote); - mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, vote); + mBrightnessObserver.onLowPowerModeEnabledLocked(mIsLowPower); + updateLowPowerModeAllowedModesLocked(); + } + + private void updateLowPowerModeAllowedModesLocked() { + if (!mVsyncLowPowerVoteEnabled) { + return; + } + if (mIsLowPower) { + for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) { + DisplayDeviceConfig config = mDisplayDeviceConfigByDisplay.valueAt(i); + List<SupportedModeData> supportedModes = config + .getRefreshRateData().lowPowerSupportedModes; + mVotesStorage.updateVote( + mDisplayDeviceConfigByDisplay.keyAt(i), + Vote.PRIORITY_LOW_POWER_MODE_MODES, + Vote.forSupportedRefreshRates(supportedModes)); + } + } else { + mVotesStorage.removeAllVotesForPriority(Vote.PRIORITY_LOW_POWER_MODE_MODES); + } } /** diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index ddb334ee1a9d..8167c1fe1e91 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -18,6 +18,9 @@ package com.android.server.display.mode; import android.annotation.NonNull; +import com.android.server.display.config.SupportedModeData; + +import java.util.ArrayList; import java.util.List; interface Vote { @@ -102,9 +105,15 @@ interface Vote { // For internal application to limit display modes to specific ids int PRIORITY_SYSTEM_REQUESTED_MODES = 14; - // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if + // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if + // Settings.Global.LOW_POWER_MODE is on. + // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other + // higher priority votes), render rate limit can still apply + int PRIORITY_LOW_POWER_MODE_MODES = 14; + + // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - int PRIORITY_LOW_POWER_MODE = 15; + int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 15; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. @@ -177,22 +186,26 @@ interface Vote { return new BaseModeRefreshRateVote(baseModeRefreshRate); } - static Vote forSupportedRefreshRates( - List<SupportedRefreshRatesVote.RefreshRates> refreshRates) { - return new SupportedRefreshRatesVote(refreshRates); + static Vote forSupportedRefreshRates(List<SupportedModeData> supportedModes) { + if (supportedModes.isEmpty()) { + return null; + } + List<SupportedRefreshRatesVote.RefreshRates> rates = new ArrayList<>(); + for (SupportedModeData data : supportedModes) { + rates.add(new SupportedRefreshRatesVote.RefreshRates(data.refreshRate, data.vsyncRate)); + } + return new SupportedRefreshRatesVote(rates); } static Vote forSupportedModes(List<Integer> modeIds) { return new SupportedModesVote(modeIds); } - - static Vote forSupportedRefreshRatesAndDisableSwitching( List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates) { return new CombinedVote( List.of(forDisableRefreshRateSwitching(), - forSupportedRefreshRates(supportedRefreshRates))); + new SupportedRefreshRatesVote(supportedRefreshRates))); } static String priorityToString(int priority) { @@ -213,8 +226,10 @@ interface Vote { return "PRIORITY_HIGH_BRIGHTNESS_MODE"; case PRIORITY_PROXIMITY: return "PRIORITY_PROXIMITY"; - case PRIORITY_LOW_POWER_MODE: - return "PRIORITY_LOW_POWER_MODE"; + case PRIORITY_LOW_POWER_MODE_MODES: + return "PRIORITY_LOW_POWER_MODE_MODES"; + case PRIORITY_LOW_POWER_MODE_RENDER_RATE: + return "PRIORITY_LOW_POWER_MODE_RENDER_RATE"; case PRIORITY_SKIN_TEMPERATURE: return "PRIORITY_SKIN_TEMPERATURE"; case PRIORITY_UDFPS: @@ -227,6 +242,8 @@ interface Vote { return "PRIORITY_LIMIT_MODE"; case PRIORITY_SYNCHRONIZED_REFRESH_RATE: return "PRIORITY_SYNCHRONIZED_REFRESH_RATE"; + case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE: + return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE"; case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE: return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE"; case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE: diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index d21fc85d6448..5db17bb90637 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -29,7 +29,6 @@ import android.os.Binder; import android.os.Handler; import android.os.PowerManager; import android.os.SystemProperties; -import android.os.UserHandle; import android.sysprop.HdmiProperties; import android.util.Slog; @@ -278,8 +277,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { void dismissUiOnActiveSourceStatusRecovered() { assertRunOnServiceThread(); Intent intent = new Intent(HdmiControlManager.ACTION_ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI); - mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - HdmiControlService.PERMISSION); + mService.sendBroadcastAsUser(intent); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 46061a56631c..275c9309ffc9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -206,6 +206,10 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { launchDeviceDiscovery(); startQueuedActions(); if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { + if (hasAction(RequestActiveSourceAction.class)) { + Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting."); + removeAction(RequestActiveSourceAction.class); + } addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() { @Override public void onComplete(int result) { @@ -1308,6 +1312,8 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.sendCecCommand( HdmiCecMessageBuilder.buildActiveSource( getDeviceInfo().getLogicalAddress(), activePath)); + updateActiveSource(getDeviceInfo().getLogicalAddress(), activePath, + "HdmiCecLocalDeviceTv#launchRoutingControl()"); } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index d2d0279e768e..cca73b5eabf0 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -40,6 +40,7 @@ import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -1645,6 +1646,13 @@ public class HdmiControlService extends SystemService { case Constants.MESSAGE_ROUTING_CHANGE: case Constants.MESSAGE_SET_STREAM_PATH: case Constants.MESSAGE_TEXT_VIEW_ON: + // RequestActiveSourceAction is started after the TV finished logical address + // allocation. This action is used by the TV to get the active source from the CEC + // network. If the TV sent a source changing CEC message, this action does not have + // to continue anymore. + if (isTvDeviceEnabled()) { + tv().removeAction(RequestActiveSourceAction.class); + } sendCecCommandWithRetries(command, callback); break; default: @@ -4392,8 +4400,7 @@ public class HdmiControlService extends SystemService { assertRunOnServiceThread(); Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); - getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - HdmiControlService.PERMISSION); + sendBroadcastAsUser(intent); } @ServiceThreadOnly @@ -4402,8 +4409,17 @@ public class HdmiControlService extends SystemService { Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra); - getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - HdmiControlService.PERMISSION); + sendBroadcastAsUser(intent); + } + + // This method is used such that we can override it inside unit tests to avoid a + // SecurityException. + @ServiceThreadOnly + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + assertRunOnServiceThread(); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, HdmiControlService.PERMISSION); } @VisibleForTesting diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java index d2504164a6df..539a00db45b8 100644 --- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java +++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java @@ -21,13 +21,20 @@ import android.hardware.hdmi.IHdmiControlCallback; import android.util.Slog; /** - * Feature action that sends <Request Active Source> message and waits for <Active Source>. + * Feature action that sends <Request Active Source> message and waits for <Active Source> on TV + * panels. + * This action has a delay before sending <Request Active Source>. This is because it should wait + * for a possible request from LauncherX and can be cancelled if an <Active Source> message was + * received or the TV switched to another input. */ public class RequestActiveSourceAction extends HdmiCecFeatureAction { private static final String TAG = "RequestActiveSourceAction"; + // State to wait for the LauncherX to call the CEC API. + private static final int STATE_WAIT_FOR_LAUNCHERX_API_CALL = 1; + // State to wait for the <Active Source> message. - private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1; + private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 2; // Number of retries <Request Active Source> is sent if no device answers this message. private static final int MAX_SEND_RETRY_COUNT = 1; @@ -43,10 +50,12 @@ public class RequestActiveSourceAction extends HdmiCecFeatureAction { boolean start() { Slog.v(TAG, "RequestActiveSourceAction started."); - sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress())); + mState = STATE_WAIT_FOR_LAUNCHERX_API_CALL; - mState = STATE_WAIT_FOR_ACTIVE_SOURCE; - addTimer(mState, HdmiConfig.TIMEOUT_MS); + // We wait for default timeout to allow the message triggered by the LauncherX API call to + // be sent by the TV and another default timeout in case the message has to be answered + // (e.g. TV sent a <Set Stream Path> or <Routing Change>). + addTimer(mState, HdmiConfig.TIMEOUT_MS * 2); return true; } @@ -65,13 +74,23 @@ public class RequestActiveSourceAction extends HdmiCecFeatureAction { if (mState != state) { return; } - if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) { - if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) { + + switch (mState) { + case STATE_WAIT_FOR_LAUNCHERX_API_CALL: + mState = STATE_WAIT_FOR_ACTIVE_SOURCE; sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress())); addTimer(mState, HdmiConfig.TIMEOUT_MS); - } else { - finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); - } + return; + case STATE_WAIT_FOR_ACTIVE_SOURCE: + if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) { + sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress())); + addTimer(mState, HdmiConfig.TIMEOUT_MS); + } else { + finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); + } + return; + default: + return; } } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 8685d2c45762..8e85b81d9bd2 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -2671,24 +2671,6 @@ public class InputManagerService extends IInputManager.Stub return null; } - private static class PointerDisplayIdChangedArgs { - final int mPointerDisplayId; - final float mXPosition; - final float mYPosition; - PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) { - mPointerDisplayId = pointerDisplayId; - mXPosition = xPosition; - mYPosition = yPosition; - } - } - - // Native callback. - @SuppressWarnings("unused") - @VisibleForTesting - void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) { - // TODO(b/311416205): Remove. - } - @Override @EnforcePermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE) public void registerStickyModifierStateListener( diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java index 00bc7517bebd..ad98b4a8db13 100644 --- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java +++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java @@ -29,6 +29,7 @@ import android.view.inputmethod.InputMethodInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInlineSuggestionsResponseCallback; +import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; /** @@ -49,12 +50,12 @@ final class AutofillSuggestionsController { private static final class CreateInlineSuggestionsRequest { @NonNull final InlineSuggestionsRequestInfo mRequestInfo; - @NonNull final IInlineSuggestionsRequestCallback mCallback; + @NonNull final InlineSuggestionsRequestCallback mCallback; @NonNull final String mPackageName; CreateInlineSuggestionsRequest( @NonNull InlineSuggestionsRequestInfo requestInfo, - @NonNull IInlineSuggestionsRequestCallback callback, + @NonNull InlineSuggestionsRequestCallback callback, @NonNull String packageName) { mRequestInfo = requestInfo; mCallback = callback; @@ -78,7 +79,7 @@ final class AutofillSuggestionsController { */ @GuardedBy("ImfLock.class") @Nullable - private IInlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback; + private InlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback; AutofillSuggestionsController(@NonNull InputMethodManagerService service) { mService = service; @@ -97,33 +98,30 @@ final class AutofillSuggestionsController { @GuardedBy("ImfLock.class") void onCreateInlineSuggestionsRequest(@UserIdInt int userId, - InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback, + InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback callback, boolean touchExplorationEnabled) { clearPendingInlineSuggestionsRequest(); mInlineSuggestionsRequestCallback = callback; final InputMethodInfo imi = mService.queryInputMethodForCurrentUserLocked( mService.getSelectedMethodIdLocked()); - try { - if (userId == mService.getCurrentImeUserIdLocked() - && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) { - mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest( - requestInfo, callback, imi.getPackageName()); - if (mService.getCurMethodLocked() != null) { - // In the normal case when the IME is connected, we can make the request here. - performOnCreateInlineSuggestionsRequest(); - } else { - // Otherwise, the next time the IME connection is established, - // InputMethodBindingController.mMainConnection#onServiceConnected() will call - // into #performOnCreateInlineSuggestionsRequestLocked() to make the request. - if (DEBUG) { - Slog.d(TAG, "IME not connected. Delaying inline suggestions request."); - } - } + + if (userId == mService.getCurrentImeUserIdLocked() + && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) { + mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest( + requestInfo, callback, imi.getPackageName()); + if (mService.getCurMethodLocked() != null) { + // In the normal case when the IME is connected, we can make the request here. + performOnCreateInlineSuggestionsRequest(); } else { - callback.onInlineSuggestionsUnsupported(); + // Otherwise, the next time the IME connection is established, + // InputMethodBindingController.mMainConnection#onServiceConnected() will call + // into #performOnCreateInlineSuggestionsRequestLocked() to make the request. + if (DEBUG) { + Slog.d(TAG, "IME not connected. Delaying inline suggestions request."); + } } - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e); + } else { + callback.onInlineSuggestionsUnsupported(); } } @@ -166,11 +164,7 @@ final class AutofillSuggestionsController { @GuardedBy("ImfLock.class") void invalidateAutofillSession() { if (mInlineSuggestionsRequestCallback != null) { - try { - mInlineSuggestionsRequestCallback.onInlineSuggestionsSessionInvalidated(); - } catch (RemoteException e) { - Slog.e(TAG, "Cannot invalidate autofill session.", e); - } + mInlineSuggestionsRequestCallback.onInlineSuggestionsSessionInvalidated(); } } @@ -180,13 +174,13 @@ final class AutofillSuggestionsController { */ private final class InlineSuggestionsRequestCallbackDecorator extends IInlineSuggestionsRequestCallback.Stub { - @NonNull private final IInlineSuggestionsRequestCallback mCallback; + @NonNull private final InlineSuggestionsRequestCallback mCallback; @NonNull private final String mImePackageName; private final int mImeDisplayId; @NonNull private final IBinder mImeToken; InlineSuggestionsRequestCallbackDecorator( - @NonNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName, + @NonNull InlineSuggestionsRequestCallback callback, @NonNull String imePackageName, int displayId, @NonNull IBinder imeToken) { mCallback = callback; mImePackageName = imePackageName; @@ -195,7 +189,7 @@ final class AutofillSuggestionsController { } @Override - public void onInlineSuggestionsUnsupported() throws RemoteException { + public void onInlineSuggestionsUnsupported() { mCallback.onInlineSuggestionsUnsupported(); } @@ -220,32 +214,32 @@ final class AutofillSuggestionsController { } @Override - public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException { + public void onInputMethodStartInput(AutofillId imeFieldId) { mCallback.onInputMethodStartInput(imeFieldId); } @Override - public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException { + public void onInputMethodShowInputRequested(boolean requestResult) { mCallback.onInputMethodShowInputRequested(requestResult); } @Override - public void onInputMethodStartInputView() throws RemoteException { + public void onInputMethodStartInputView() { mCallback.onInputMethodStartInputView(); } @Override - public void onInputMethodFinishInputView() throws RemoteException { + public void onInputMethodFinishInputView() { mCallback.onInputMethodFinishInputView(); } @Override - public void onInputMethodFinishInput() throws RemoteException { + public void onInputMethodFinishInput() { mCallback.onInputMethodFinishInput(); } @Override - public void onInlineSuggestionsSessionInvalidated() throws RemoteException { + public void onInlineSuggestionsSessionInvalidated() { mCallback.onInlineSuggestionsSessionInvalidated(); } } diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java index 4c20c3b9784a..f78ea84efb77 100644 --- a/services/core/java/com/android/server/inputmethod/ImeBindingState.java +++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java @@ -21,7 +21,9 @@ import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCU import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.os.IBinder; +import android.os.UserHandle; import android.util.Printer; import android.util.proto.ProtoOutputStream; import android.view.WindowManager; @@ -36,6 +38,9 @@ import com.android.server.wm.WindowManagerInternal; */ final class ImeBindingState { + @UserIdInt + final int mUserId; + /** * The last window token that we confirmed to be focused. This is always updated upon * reports from the input method client. If the window state is already changed before the @@ -90,6 +95,7 @@ final class ImeBindingState { static ImeBindingState newEmptyState() { return new ImeBindingState( + /*userId=*/ UserHandle.USER_NULL, /*focusedWindow=*/ null, /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED, /*focusedWindowClient=*/ null, @@ -97,10 +103,12 @@ final class ImeBindingState { ); } - ImeBindingState(@Nullable IBinder focusedWindow, + ImeBindingState(@UserIdInt int userId, + @Nullable IBinder focusedWindow, @SoftInputModeFlags int focusedWindowSoftInputMode, @Nullable ClientState focusedWindowClient, @Nullable EditorInfo focusedWindowEditorInfo) { + mUserId = userId; mFocusedWindow = focusedWindow; mFocusedWindowSoftInputMode = focusedWindowSoftInputMode; mFocusedWindowClient = focusedWindowClient; diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java index 1c14fc1b08dd..fff0e6e1b995 100644 --- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java +++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java @@ -133,6 +133,13 @@ public final class ImeTrackerService extends IImeTracker.Stub { } } + @Override + public void onDispatched(@NonNull ImeTracker.Token statsToken) { + synchronized (mLock) { + mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); + } + } + /** * Updates the IME request tracking token with new information available in IMMS. * diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index b7091744c3b6..e862c7e96200 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -443,7 +443,16 @@ final class InputMethodBindingController { mCurId = info.getId(); mLastBindTime = SystemClock.uptimeMillis(); - addFreshWindowToken(); + final int displayIdToShowIme = mService.getDisplayIdToShowImeLocked(); + mCurToken = new Binder(); + mService.setCurTokenDisplayIdLocked(displayIdToShowIme); + if (DEBUG) { + Slog.v(TAG, "Adding window token: " + mCurToken + " for display: " + + displayIdToShowIme); + } + mWindowManagerInternal.addWindowToken(mCurToken, + WindowManager.LayoutParams.TYPE_INPUT_METHOD, + displayIdToShowIme, null /* options */); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, null, null, null, mCurId, mCurSeq, false); @@ -471,22 +480,6 @@ final class InputMethodBindingController { } @GuardedBy("ImfLock.class") - private void addFreshWindowToken() { - int displayIdToShowIme = mService.getDisplayIdToShowImeLocked(); - mCurToken = new Binder(); - - mService.setCurTokenDisplayIdLocked(displayIdToShowIme); - - if (DEBUG) { - Slog.v(TAG, "Adding window token: " + mCurToken + " for display: " - + displayIdToShowIme); - } - mWindowManagerInternal.addWindowToken(mCurToken, - WindowManager.LayoutParams.TYPE_INPUT_METHOD, - displayIdToShowIme, null /* options */); - } - - @GuardedBy("ImfLock.class") private void unbindMainConnection() { mContext.unbindService(mMainConnection); mHasMainConnection = false; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java index 6339686629f5..458c3598d7ae 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java @@ -22,6 +22,7 @@ import static com.android.server.inputmethod.SubtypeUtils.SUBTYPE_MODE_KEYBOARD; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.os.Parcel; import android.text.TextUtils; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; @@ -323,4 +324,24 @@ final class InputMethodInfoUtils { return SubtypeUtils.containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode); } + + /** + * Marshals the given {@link InputMethodInfo} into a byte array. + * + * @param imi {@link InputMethodInfo} to be marshalled + * @return a byte array where the given {@link InputMethodInfo} is marshalled + */ + @NonNull + static byte[] marshal(@NonNull InputMethodInfo imi) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + parcel.writeTypedObject(imi, 0); + return parcel.marshall(); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index e8543f225ef0..dace67f2c462 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -25,7 +25,7 @@ import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputMethodInfo; import com.android.internal.inputmethod.IAccessibilityInputMethodSession; -import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; +import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.server.LocalServices; @@ -86,11 +86,11 @@ public abstract class InputMethodManagerInternal { * * @param userId the user ID to be queried * @param requestInfo information needed to create an {@link InlineSuggestionsRequest}. - * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request + * @param cb {@link InlineSuggestionsRequestCallback} used to pass back the request * object */ public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId, - InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb); + InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb); /** * Force switch to the enabled input method by {@code imeId} for current user. If the input @@ -263,7 +263,7 @@ public abstract class InputMethodManagerInternal { @Override public void onCreateInlineSuggestionsRequest(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, - IInlineSuggestionsRequestCallback cb) { + InlineSuggestionsRequestCallback cb) { } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 691145c500d9..f8dda6073b0b 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -147,7 +147,6 @@ import com.android.internal.inputmethod.IAccessibilityInputMethodSession; import com.android.internal.inputmethod.IBooleanListener; import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IImeTracker; -import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.IInputMethodClient; @@ -157,6 +156,7 @@ import com.android.internal.inputmethod.IInputMethodSessionCallback; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.ImeTracing; +import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodDebug; @@ -262,6 +262,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; private static final String HANDLER_THREAD_NAME = "android.imms"; + private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2"; /** * When set, {@link #startInputUncheckedLocked} will return @@ -281,10 +282,35 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @NonNull private final String[] mNonPreemptibleInputMethods; + /** + * See {@link #shouldEnableExperimentalConcurrentMultiUserMode(Context)} about when set to be + * {@code true}. + */ + private final boolean mExperimentalConcurrentMultiUserModeEnabled; + + /** + * Returns {@code true} if experimental concurrent multi-user mode is enabled. + * + * <p>Currently not compatible with profiles (e.g. work profile).</p> + * + * @param context {@link Context} to be used to query + * {@link PackageManager#FEATURE_AUTOMOTIVE} + * @return {@code true} if experimental concurrent multi-user mode is enabled. + */ + static boolean shouldEnableExperimentalConcurrentMultiUserMode(@NonNull Context context) { + return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + && UserManager.isVisibleBackgroundUsersEnabled() + && context.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled) + && Flags.concurrentInputMethods(); + } + final Context mContext; final Resources mRes; private final Handler mHandler; + @NonNull + private final Handler mPackageMonitorHandler; + @MultiUserUnawareField @UserIdInt @GuardedBy("ImfLock.class") @@ -1024,8 +1050,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } private void onFinishPackageChangesInternal() { + final int userId = getChangingUserId(); + + // Instantiating InputMethodInfo requires disk I/O. + // Do them before acquiring the lock to minimize the chances of ANR (b/340221861). + final var newMethodMapWithoutAdditionalSubtypes = + queryInputMethodServicesInternal(mContext, userId, + AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO) + .getMethodMap(); + synchronized (ImfLock.class) { - final int userId = getChangingUserId(); final boolean isCurrentUser = (userId == mCurrentUserId); final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); @@ -1077,9 +1111,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) { return; } - - final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, - userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO); + final var newMethodMap = newMethodMapWithoutAdditionalSubtypes + .applyAdditionalSubtypes(newAdditionalSubtypeMap); + final InputMethodSettings newSettings = + InputMethodSettings.create(newMethodMap, userId); InputMethodSettingsRepository.put(userId, newSettings); if (!isCurrentUser) { return; @@ -1178,8 +1213,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public static final class Lifecycle extends SystemService { private final InputMethodManagerService mService; + public Lifecycle(Context context) { - this(context, new InputMethodManagerService(context)); + this(context, new InputMethodManagerService(context, + shouldEnableExperimentalConcurrentMultiUserMode(context))); } public Lifecycle( @@ -1278,16 +1315,21 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mHandler.post(task); } - public InputMethodManagerService(Context context) { - this(context, null, null); + public InputMethodManagerService(Context context, + boolean experimentalConcurrentMultiUserModeEnabled) { + this(context, experimentalConcurrentMultiUserModeEnabled, null, null, null); } @VisibleForTesting InputMethodManagerService( Context context, + boolean experimentalConcurrentMultiUserModeEnabled, @Nullable ServiceThread serviceThreadForTesting, + @Nullable ServiceThread packageMonitorThreadForTesting, @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) { synchronized (ImfLock.class) { + mExperimentalConcurrentMultiUserModeEnabled = + experimentalConcurrentMultiUserModeEnabled; mContext = context; mRes = context.getResources(); SecureSettingsWrapper.onStart(mContext); @@ -1303,6 +1345,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. true /* allowIo */); thread.start(); mHandler = Handler.createAsync(thread.getLooper(), this); + { + final ServiceThread packageMonitorThread = + packageMonitorThreadForTesting != null + ? packageMonitorThreadForTesting + : new ServiceThread( + PACKAGE_MONITOR_THREAD_NAME, + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); + packageMonitorThread.start(); + mPackageMonitorHandler = Handler.createAsync(packageMonitorThread.getLooper()); + } SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler); mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null ? serviceThreadForTesting.getLooper() : Looper.getMainLooper()); @@ -1478,7 +1531,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Note that in b/197848765 we want to see if we can keep the binding alive for better // profile switching. final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - userData.mBindingController.unbindCurrentMethod(); + final var bindingController = userData.mBindingController; + bindingController.unbindCurrentMethod(); unbindCurrentClientLocked(UnbindReason.SWITCH_USER); @@ -1576,7 +1630,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes"); - mMyPackageMonitor.register(mContext, UserHandle.ALL, mHandler); + mMyPackageMonitor.register(mContext, UserHandle.ALL, mPackageMonitorHandler); mSettingsObserver.registerContentObserverLocked(currentUserId); final IntentFilter broadcastFilterForAllUsers = new IntentFilter(); @@ -1604,8 +1658,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Returns true iff the caller is identified to be the current input method with the token. - * @param token The window token given to the input method when it was started. - * @return true if and only if non-null valid token is specified. + * + * @param token the window token given to the input method when it was started + * @return true if and only if non-null valid token is specified */ @GuardedBy("ImfLock.class") private boolean calledWithValidTokenLocked(@NonNull IBinder token) { @@ -1697,9 +1752,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Check if selected IME of current user supports handwriting. if (userId == mCurrentUserId) { final var userData = mUserDataRepository.getOrCreate(userId); - return userData.mBindingController.supportsStylusHandwriting() + final var bindingController = userData.mBindingController; + return bindingController.supportsStylusHandwriting() && (!connectionless - || userData.mBindingController.supportsConnectionlessStylusHandwriting()); + || bindingController.supportsConnectionlessStylusHandwriting()); } final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final InputMethodInfo imi = settings.getMethodMap().get( @@ -1760,10 +1816,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Gets enabled subtypes of the specified {@link InputMethodInfo}. * - * @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}. + * @param imiId if null, returns enabled subtypes for the current + * {@link InputMethodInfo} * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled - * subtypes. - * @param userId the user ID to be queried about. + * subtypes + * @param userId the user ID to be queried about */ @Override public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, @@ -1807,10 +1864,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * <p>As a general principle, IPCs from the application process that take * {@link IInputMethodClient} will be rejected without this step.</p> * - * @param client {@link android.os.Binder} proxy that is associated with the singleton instance - * of {@link android.view.inputmethod.InputMethodManager} that runs on the client - * process - * @param inputConnection communication channel for the fallback {@link InputConnection} + * @param client {@link android.os.Binder} proxy that is associated with the + * singleton instance of + * {@link android.view.inputmethod.InputMethodManager} that runs + * on the client process + * @param inputConnection communication channel for the fallback {@link InputConnection} * @param selfReportedDisplayId self-reported display ID to which the client is associated. * Whether the client is still allowed to access to this display * or not needs to be evaluated every time the client interacts @@ -1835,10 +1893,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } - // TODO(b/325515685): Move this method to InputMethodBindingController /** * Hide the IME if the removed user is the current user. */ + // TODO(b/325515685): Move this method to InputMethodBindingController @GuardedBy("ImfLock.class") private void onClientRemoved(ClientState client) { clearClientSessionLocked(client); @@ -1895,7 +1953,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // following dependencies also need to be user independent: mCurClient, mBoundToMethod, // getCurMethodLocked(), and mMenuController. final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - mCurClient.mClient.onUnbindMethod(userData.mBindingController.getSequenceNumber(), + final var bindingController = userData.mBindingController; + mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(), unbindClientReason); mCurClient.mSessionRequested = false; mCurClient.mSessionRequestedForAccessibility = false; @@ -1975,13 +2034,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean restarting = !initial; final Binder startInputToken = new Binder(); final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var bindingController = userData.mBindingController; final StartInputInfo info = new StartInputInfo(mCurrentUserId, getCurTokenLocked(), - mCurTokenDisplayId, userData.mBindingController.getCurId(), startInputReason, + getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason, restarting, UserHandle.getUserId(mCurClient.mUid), mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo, mImeBindingState.mFocusedWindowSoftInputMode, - userData.mBindingController.getSequenceNumber()); + bindingController.getSequenceNumber()); mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow); mStartInputHistory.addEntry(info); @@ -1993,7 +2053,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mCurrentUserId == UserHandle.getUserId( mCurClient.mUid)) { mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */, - UserHandle.getAppId(userData.mBindingController.getCurMethodUid()), + UserHandle.getAppId(bindingController.getCurMethodUid()), mCurClient.mUid, true /* direct */); } @@ -2014,20 +2074,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } - final var curId = userData.mBindingController.getCurId(); + final var curId = bindingController.getCurId(); final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId) .getMethodMap().get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); - if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) { + if (bindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) { mHwController.setInkWindowInitializer(new InkWindowInitializer()); } return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, session.mSession, accessibilityInputMethodSessions, (session.mChannel != null ? session.mChannel.dup() : null), - curId, userData.mBindingController.getSequenceNumber(), suppressesSpellChecker); + curId, bindingController.getSequenceNumber(), suppressesSpellChecker); } @GuardedBy("ImfLock.class") @@ -2121,7 +2181,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean connectionWasActive = mCurInputConnection != null; // Bump up the sequence for this client and attach it. - userData.mBindingController.advanceSequenceNumber(); + final var bindingController = userData.mBindingController; + bindingController.advanceSequenceNumber(); mCurClient = cs; mCurInputConnection = inputConnection; @@ -2144,7 +2205,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (connectionIsActive != connectionWasActive) { mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive); } - final var bindingController = userData.mBindingController; // If configured, we want to avoid starting up the IME if it is not supposed to be showing if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags, @@ -2162,7 +2222,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // display ID. final String curId = bindingController.getCurId(); if (curId != null && curId.equals(bindingController.getSelectedMethodId()) - && mDisplayIdToShowIme == mCurTokenDisplayId) { + && mDisplayIdToShowIme == getCurTokenDisplayIdLocked()) { if (cs.mCurSession != null) { // Fast case: if we are already connected to the input method, // then just return it. @@ -2193,11 +2253,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Update the current deviceId and return the relevant imeId for this device. - * 1. If the device changes to virtual and its custom IME is not available, then disable IME. - * 2. If the device changes to virtual with valid custom IME, then return the custom IME. If - * the old device was default, then store the current imeId so it can be restored. - * 3. If the device changes to default, restore the default device IME. - * 4. Otherwise keep the current imeId. + * + * <p>1. If the device changes to virtual and its custom IME is not available, then disable + * IME.</p> + * <p>2. If the device changes to virtual with valid custom IME, then return the custom IME. If + * the old device was default, then store the current imeId so it can be restored.</p> + * <p>3. If the device changes to default, restore the default device IME.</p> + * <p>4. Otherwise keep the current imeId.</p> */ @GuardedBy("ImfLock.class") private String computeCurrentDeviceMethodIdLocked(String currentMethodId) { @@ -2295,7 +2357,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Nullable private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData, @NonNull ClientState cs) { - if (userData.mBindingController.hasMainConnection()) { + final var bindingController = userData.mBindingController; + if (bindingController.hasMainConnection()) { if (getCurMethodLocked() != null) { // Return to client, and we will get back with it when // we have had a session made for it. @@ -2304,10 +2367,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION, null, null, null, - userData.mBindingController.getCurId(), - userData.mBindingController.getSequenceNumber(), false); + bindingController.getCurId(), + bindingController.getSequenceNumber(), false); } else { - final long lastBindTime = userData.mBindingController.getLastBindTime(); + final long lastBindTime = bindingController.getLastBindTime(); long bindingDuration = SystemClock.uptimeMillis() - lastBindTime; if (bindingDuration < TIME_TO_RECONNECT) { // In this case we have connected to the service, but @@ -2320,8 +2383,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, null, null, null, - userData.mBindingController.getCurId(), - userData.mBindingController.getSequenceNumber(), false); + bindingController.getCurId(), + bindingController.getSequenceNumber(), false); } else { EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodIdLocked(), bindingDuration, 0); @@ -2340,12 +2403,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Find the display where the IME should be shown. * - * @param displayId the ID of the display where the IME client target is. - * @param checker instance of {@link ImeDisplayValidator} which is used for - * checking display config to adjust the final target display. - * @return The ID of the display where the IME should be shown or - * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of - * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}. + * @param displayId the ID of the display where the IME client target is + * @param checker instance of {@link ImeDisplayValidator} which is used for + * checking display config to adjust the final target display + * @return the ID of the display where the IME should be shown or + * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of + * {@link WindowManager#DISPLAY_IME_POLICY_HIDE} */ static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { @@ -2368,7 +2431,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) { if (DEBUG) { Slog.v(TAG, "Sending attach of token: " + token + " for display: " - + mCurTokenDisplayId); + + getCurTokenDisplayIdLocked()); } inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token), getInputMethodNavButtonFlagsLocked()); @@ -2448,13 +2511,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - userData.mBindingController.setSelectedMethodId(null); + final var bindingController = + mUserDataRepository.getOrCreate(mCurrentUserId).mBindingController; + bindingController.setSelectedMethodId(null); // Callback before clean-up binding states. // TODO(b/338461930): Check if this is still necessary or not. onUnbindCurrentMethodByReset(); - userData.mBindingController.unbindCurrentMethod(); + bindingController.unbindCurrentMethod(); unbindCurrentClientLocked(unbindClientReason); } @@ -2657,9 +2721,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // Whether the current display has a navigation bar. When this is false (e.g. emulator), // the IME should not draw the IME navigation bar. + final int tokenDisplayId = getCurTokenDisplayIdLocked(); final boolean hasNavigationBar = mWindowManagerInternal - .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY - ? mCurTokenDisplayId : DEFAULT_DISPLAY); + .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY + ? tokenDisplayId : DEFAULT_DISPLAY); final boolean canImeDrawsImeNavBar = mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar; final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked( @@ -2755,8 +2820,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Note that we still need to update IME status when focusing external display // that does not support system decoration and fallback to show IME on default // display since it is intentional behavior. - if (mCurTokenDisplayId != topFocusedDisplayId - && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) { + final int tokenDisplayId = getCurTokenDisplayIdLocked(); + if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) { return; } mImeWindowVis = vis; @@ -2819,7 +2884,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Slog.d(TAG, "IME window vis: " + vis + " active: " + (vis & InputMethodService.IME_ACTIVE) + " inv: " + (vis & InputMethodService.IME_INVISIBLE) - + " displayId: " + mCurTokenDisplayId); + + " displayId: " + getCurTokenDisplayIdLocked()); } final IBinder focusedWindowToken = mImeBindingState != null ? mImeBindingState.mFocusedWindow : null; @@ -2848,7 +2913,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); if (mStatusBarManagerInternal != null) { - mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId, + mStatusBarManagerInternal.setImeWindowStatus(getCurTokenDisplayIdLocked(), getCurTokenLocked(), vis, backDisposition, needsToShowImeSwitcher); } } finally { @@ -3537,13 +3602,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final InputBindResult result; synchronized (ImfLock.class) { final var userData = mUserDataRepository.getOrCreate(userId); + final var bindingController = userData.mBindingController; // If the system is not yet ready, we shouldn't be running third party code. if (!mSystemReady) { return new InputBindResult( InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, null /* method */, null /* accessibilitySessions */, null /* channel */, getSelectedMethodIdLocked(), - userData.mBindingController.getSequenceNumber(), + bindingController.getSequenceNumber(), false /* isInputMethodSuppressingSpellChecker */); } final ClientState cs = mClientController.getClient(client.asBinder()); @@ -3712,7 +3778,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. null, null, null, null, -1, false); } - mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo); + mImeBindingState = new ImeBindingState(userData.mUserId, windowToken, softInputMode, cs, + editorInfo); mFocusedWindowPerceptible.put(windowToken, true); // We want to start input before showing the IME, but after closing @@ -3751,7 +3818,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // window token removed. // Note that we can trust client's display ID as long as it matches // to the display ID obtained from the window. - if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) { + if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) { userData.mBindingController.unbindCurrentMethod(); } } @@ -4115,7 +4182,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * This is kept due to {@code @UnsupportedAppUsage} in * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in * {@link InputMethodService#onCreate()}. - * * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight(int)} * * @deprecated TODO(b/113914148): Check if we can remove this @@ -4133,7 +4199,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // This should probably use the caller's display id, but because this is unsupported // and maintained only for compatibility, there's no point in fixing it. - curTokenDisplayId = mCurTokenDisplayId; + curTokenDisplayId = getCurTokenDisplayIdLocked(); } return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId); }); @@ -4214,8 +4280,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // a new Stylus is detected. If IME supports handwriting, and we don't have // handwriting initialized, lets do it now. final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var bindingController = userData.mBindingController; if (!mHwController.getCurrentRequestId().isPresent() - && userData.mBindingController.supportsStylusHandwriting()) { + && bindingController.supportsStylusHandwriting()) { scheduleResetStylusHandwriting(); } } @@ -4273,7 +4340,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Helper method to set a stylus idle-timeout after which handwriting {@code InkWindow} * will be removed. - * @param timeout to set in milliseconds. To reset to default, use a value <= zero. + * + * @param timeout to set in milliseconds. To reset to default, use a value <= zero */ @BinderThread @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) @@ -4397,9 +4465,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (ImfLock.class) { final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var bindingController = userData.mBindingController; final long token = proto.start(fieldId); proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked()); - proto.write(CUR_SEQ, userData.mBindingController.getSequenceNumber()); + proto.write(CUR_SEQ, bindingController.getSequenceNumber()); proto.write(CUR_CLIENT, Objects.toString(mCurClient)); mImeBindingState.dumpDebug(proto, mWindowManagerInternal); proto.write(LAST_IME_TARGET_WINDOW_NAME, @@ -4409,13 +4478,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mCurEditorInfo != null) { mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); } - proto.write(CUR_ID, userData.mBindingController.getCurId()); + proto.write(CUR_ID, bindingController.getCurId()); mVisibilityStateComputer.dumpDebug(proto, fieldId); proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode); proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked())); - proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId); + proto.write(CUR_TOKEN_DISPLAY_ID, getCurTokenDisplayIdLocked()); proto.write(SYSTEM_READY, mSystemReady); - proto.write(HAVE_CONNECTION, userData.mBindingController.hasMainConnection()); + proto.write(HAVE_CONNECTION, bindingController.hasMainConnection()); proto.write(BOUND_TO_METHOD, mBoundToMethod); proto.write(IS_INTERACTIVE, mIsInteractive); proto.write(BACK_DISPOSITION, mBackDisposition); @@ -4527,7 +4596,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken); final WindowManagerInternal.ImeTargetInfo info = mWindowManagerInternal.onToggleImeRequested( - show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId); + show, mImeBindingState.mFocusedWindow, requestToken, + getCurTokenDisplayIdLocked()); mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo, info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason, @@ -4786,10 +4856,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. case MSG_RESET_HANDWRITING: { synchronized (ImfLock.class) { final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - if (userData.mBindingController.supportsStylusHandwriting() + final var bindingController = userData.mBindingController; + if (bindingController.supportsStylusHandwriting() && getCurMethodLocked() != null && hasSupportedStylusLocked()) { Slog.d(TAG, "Initializing Handwriting Spy"); - mHwController.initializeHandwritingSpy(mCurTokenDisplayId); + mHwController.initializeHandwritingSpy(getCurTokenDisplayIdLocked()); } else { mHwController.reset(); } @@ -4812,11 +4883,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return true; } final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var bindingController = userData.mBindingController; final HandwritingModeController.HandwritingSession session = mHwController.startHandwritingSession( msg.arg1 /*requestId*/, msg.arg2 /*pid*/, - userData.mBindingController.getCurMethodUid(), + bindingController.getCurMethodUid(), mImeBindingState.mFocusedWindow); if (session == null) { Slog.e(TAG, @@ -4868,8 +4940,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // TODO(b/325515685): user data must be retrieved by a userId parameter final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var bindingController = userData.mBindingController; if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol( - userData.mBindingController.getCurMethodUid())) { + bindingController.getCurMethodUid())) { // Handle IME visibility when interactive changed before finishing the input to // ensure we preserve the last state as possible. final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged( @@ -5114,7 +5187,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") void sendOnNavButtonFlagsChangedLocked() { final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - final IInputMethodInvoker curMethod = userData.mBindingController.getCurMethod(); + final var bindingController = userData.mBindingController; + final IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod == null) { // No need to send the data if the IME is not yet bound. return; @@ -5159,10 +5233,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}. * - * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently not - * recognized by the system. - * @param enabled {@code true} if {@code id} needs to be enabled. - * @return {@code true} if the IME was previously enabled. {@code false} otherwise. + * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently + * not recognized by the system + * @param enabled {@code true} if {@code id} needs to be enabled + * @return {@code true} if the IME was previously enabled */ @GuardedBy("ImfLock.class") private boolean setInputMethodEnabledLocked(String id, boolean enabled) { @@ -5269,8 +5343,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Gets the current subtype of this input method. * - * @param userId User ID to be queried about. - * @return The current {@link InputMethodSubtype} for the specified user. + * @param userId User ID to be queried about + * @return the current {@link InputMethodSubtype} for the specified user */ @Nullable @Override @@ -5344,7 +5418,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Returns the default {@link InputMethodInfo} for the specific userId. - * @param userId user ID to query. + * + * @param userId user ID to query */ @GuardedBy("ImfLock.class") private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) { @@ -5378,11 +5453,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * Filter the access to the input method by rules of the package visibility. Return {@code true} * if the given input method is the currently selected one or visible to the caller. * - * @param targetPkgName The package name of input method to check. - * @param callingUid The caller that is going to access the input method. - * @param userId The user ID where the input method resides. - * @param settings The input method settings under the given user ID. - * @return {@code true} if caller is able to access the input method. + * @param targetPkgName the package name of input method to check + * @param callingUid the caller that is going to access the input method + * @param userId the user ID where the input method resides + * @param settings the input method settings under the given user ID + * @return {@code true} if caller is able to access the input method */ private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid, @UserIdInt int userId, @NonNull InputMethodSettings settings) { @@ -5476,7 +5551,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void onCreateInlineSuggestionsRequest(@UserIdInt int userId, - InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) { + InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb) { // Get the device global touch exploration state before lock to avoid deadlock. final boolean touchExplorationEnabled = AccessibilityManagerInternal.get() .isTouchExplorationEnabled(userId); @@ -5548,7 +5623,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. //TODO(b/150843766): Check if Input Token is valid. final IBinder curHostInputToken; synchronized (ImfLock.class) { - if (displayId != mCurTokenDisplayId) { + if (displayId != getCurTokenDisplayIdLocked()) { return false; } curHostInputToken = mAutofillController.getCurHostInputToken(); @@ -5600,6 +5675,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. IAccessibilityInputMethodSession session, @UserIdInt int userId) { synchronized (ImfLock.class) { final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var bindingController = userData.mBindingController; // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId); @@ -5622,8 +5698,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final InputBindResult res = new InputBindResult( InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION, imeSession, accessibilityInputMethodSessions, /* channel= */ null, - userData.mBindingController.getCurId(), - userData.mBindingController.getSequenceNumber(), + bindingController.getCurId(), + bindingController.getSequenceNumber(), /* isInputMethodSuppressingSpellChecker= */ false); mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId); } @@ -5635,6 +5711,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @UserIdInt int userId) { synchronized (ImfLock.class) { final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var bindingController = userData.mBindingController; // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { if (DEBUG) { @@ -5644,7 +5721,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // A11yManagerService unbinds the disabled accessibility service. We don't need // to do it here. mCurClient.mClient.onUnbindAccessibilityService( - userData.mBindingController.getSequenceNumber(), + bindingController.getSequenceNumber(), accessibilityConnectionId); } // We only have sessions when we bound to an input method. Remove this session @@ -5867,18 +5944,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. }; mClientController.forAllClients(clientControllerDump); final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + final var bindingController = userData.mBindingController; p.println(" mCurrentUserId=" + mCurrentUserId); p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" - + userData.mBindingController.getSequenceNumber()); + + bindingController.getSequenceNumber()); p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); mImeBindingState.dump(/* prefix= */ " ", p); - p.println(" mCurId=" + userData.mBindingController.getCurId() - + " mHaveConnection=" + userData.mBindingController.hasMainConnection() + p.println(" mCurId=" + bindingController.getCurId() + + " mHaveConnection=" + bindingController.hasMainConnection() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" - + userData.mBindingController.isVisibleBound()); + + bindingController.isVisibleBound()); p.println(" mUserDataRepository="); // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. @@ -5892,15 +5970,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mUserDataRepository.forAllUserData(userDataDump); p.println(" mCurToken=" + getCurTokenLocked()); - p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId); + p.println(" mCurTokenDisplayId=" + getCurTokenDisplayIdLocked()); p.println(" mCurHostInputToken=" + mAutofillController.getCurHostInputToken()); - p.println(" mCurIntent=" + userData.mBindingController.getCurIntent()); + p.println(" mCurIntent=" + bindingController.getCurIntent()); method = getCurMethodLocked(); p.println(" mCurMethod=" + getCurMethodLocked()); p.println(" mEnabledSession=" + mEnabledSession); mVisibilityStateComputer.dump(pw, " "); p.println(" mInFullscreenMode=" + mInFullscreenMode); p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive); + p.println(" mExperimentalConcurrentMultiUserModeEnabled=" + + mExperimentalConcurrentMultiUserModeEnabled); p.println(" ENABLE_HIDE_IME_CAPTION_BAR=" + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR); p.println(" mSettingsObserver=" + mSettingsObserver); @@ -6133,8 +6213,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell ime list}. - * @param shellCommand {@link ShellCommand} object that is handling this command. - * @return Exit code of the command. + * + * @param shellCommand {@link ShellCommand} object that is handling this command + * @return exit code of the command */ @BinderThread @ShellCommandResult @@ -6192,9 +6273,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell ime enable} and {@code adb shell ime disable}. * - * @param shellCommand {@link ShellCommand} object that is handling this command. - * @param enabled {@code true} if the command was {@code adb shell ime enable}. - * @return Exit code of the command. + * @param shellCommand {@link ShellCommand} object that is handling this command + * @param enabled {@code true} if the command was {@code adb shell ime enable} + * @return exit code of the command */ @BinderThread @ShellCommandResult @@ -6229,8 +6310,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * {@link ShellCommand#getNextArg()} and {@link ShellCommand#getNextArgRequired()} for the * main arguments.</p> * - * @param shellCommand {@link ShellCommand} from which options should be obtained. - * @return User ID to be resolved. {@link UserHandle#CURRENT} if not specified. + * @param shellCommand {@link ShellCommand} from which options should be obtained + * @return user ID to be resolved. {@link UserHandle#CURRENT} if not specified */ @BinderThread @UserIdInt @@ -6252,12 +6333,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles core logic of {@code adb shell ime enable} and {@code adb shell ime disable}. * - * @param userId user ID specified to the command. Pseudo user IDs are not supported. - * @param imeId IME ID specified to the command. - * @param enabled {@code true} for {@code adb shell ime enable}. {@code false} otherwise. - * @param out {@link PrintWriter} to output standard messages. - * @param error {@link PrintWriter} to output error messages. - * @return {@code false} if it fails to enable the IME. {@code false} otherwise. + * @param userId user ID specified to the command (pseudo user IDs are not supported) + * @param imeId IME ID specified to the command + * @param enabled {@code true} for {@code adb shell ime enable} + * @param out {@link PrintWriter} to output standard messages + * @param error {@link PrintWriter} to output error messages + * @return {@code false} if it fails to enable the IME */ @BinderThread @GuardedBy("ImfLock.class") @@ -6315,7 +6396,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell ime set}. * - * @param shellCommand {@link ShellCommand} object that is handling this command. + * @param shellCommand {@link ShellCommand} object that is handling this command * @return Exit code of the command. */ @BinderThread @@ -6358,7 +6439,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell ime reset-ime}. - * @param shellCommand {@link ShellCommand} object that is handling this command. + * + * @param shellCommand {@link ShellCommand} object that is handling this command * @return Exit code of the command. */ @BinderThread @@ -6385,7 +6467,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); final var userData = mUserDataRepository.getOrCreate(userId); - userData.mBindingController.unbindCurrentMethod(); + final var bindingController = userData.mBindingController; + bindingController.unbindCurrentMethod(); // Enable default IMEs, disable others var toDisable = settings.getEnabledInputMethodList(); @@ -6438,7 +6521,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}. - * @param shellCommand {@link ShellCommand} object that is handling this command. + * + * @param shellCommand {@link ShellCommand} object that is handling this command * @return Exit code of the command. */ @BinderThread @@ -6483,9 +6567,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * @param userId the actual user handle obtained by {@link UserHandle#getIdentifier()} - * and *not* pseudo ids like {@link UserHandle#USER_ALL etc}. - * @return {@code true} if userId has debugging privileges. - * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}. + * and *not* pseudo ids like {@link UserHandle#USER_ALL etc} + * @return {@code true} if userId has debugging privileges + * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false} */ private boolean userHasDebugPriv(@UserIdInt int userId, ShellCommand shellCommand) { if (mUserManagerInternal.hasUserRestriction( @@ -6506,8 +6590,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Creates an IME request tracking token for the current focused client. * - * @param show whether this is a show or a hide request. - * @param reason the reason why the IME request was created. + * @param show whether this is a show or a hide request + * @param reason the reason why the IME request was created */ @NonNull private ImeTracker.Token createStatsTokenForFocusedClient(boolean show, diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java index a8e5e2ef4f72..bab21e8b7b06 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMap.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMap.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.util.ArrayMap; import android.view.inputmethod.InputMethodInfo; +import java.util.Arrays; import java.util.List; /** @@ -75,4 +76,61 @@ final class InputMethodMap { int size() { return mMap.size(); } + + @AnyThread + @NonNull + public InputMethodMap applyAdditionalSubtypes( + @NonNull AdditionalSubtypeMap additionalSubtypeMap) { + if (additionalSubtypeMap.isEmpty()) { + return this; + } + final int size = size(); + final ArrayMap<String, InputMethodInfo> newMethodMap = new ArrayMap<>(size); + boolean updated = false; + for (int i = 0; i < size; ++i) { + final var imi = valueAt(i); + final var imeId = imi.getId(); + final var newAdditionalSubtypes = additionalSubtypeMap.get(imeId); + if (newAdditionalSubtypes == null || newAdditionalSubtypes.isEmpty()) { + newMethodMap.put(imi.getId(), imi); + } else { + newMethodMap.put(imi.getId(), new InputMethodInfo(imi, newAdditionalSubtypes)); + updated = true; + } + } + return updated ? InputMethodMap.of(newMethodMap) : this; + } + + /** + * Compares the given two {@link InputMethodMap} instances to see if they contain the same data + * or not. + * + * @param map1 {@link InputMethodMap} to be compared with + * @param map2 {@link InputMethodMap} to be compared with + * @return {@code true} if both {@link InputMethodMap} instances contain exactly the same data + */ + @AnyThread + static boolean areSame(@NonNull InputMethodMap map1, @NonNull InputMethodMap map2) { + if (map1 == map2) { + return true; + } + final int size = map1.size(); + if (size != map2.size()) { + return false; + } + for (int i = 0; i < size; ++i) { + final var imi1 = map1.valueAt(i); + final var imeId = imi1.getId(); + final var imi2 = map2.get(imeId); + if (imi2 == null) { + return false; + } + final var marshaled1 = InputMethodInfoUtils.marshal(imi1); + final var marshaled2 = InputMethodInfoUtils.marshal(imi2); + if (!Arrays.equals(marshaled1, marshaled2)) { + return false; + } + } + return true; + } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java index a0dbfa082978..ec94e2be2c59 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java @@ -412,10 +412,15 @@ import java.util.concurrent.atomic.AtomicInteger; /* package */ synchronized void addTransaction( ContextHubServiceTransaction transaction) throws IllegalStateException { + if (transaction == null) { + return; + } + if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) { throw new IllegalStateException("Transaction queue is full (capacity = " + MAX_PENDING_REQUESTS + ")"); } + mTransactionQueue.add(transaction); mTransactionRecordDeque.add(new TransactionRecord(transaction.toString())); @@ -517,7 +522,10 @@ import java.util.concurrent.atomic.AtomicInteger; * the caller has obtained a lock on this ContextHubTransactionManager object. */ private void removeTransactionAndStartNext() { - mTimeoutFuture.cancel(false /* mayInterruptIfRunning */); + if (mTimeoutFuture != null) { + mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false); + mTimeoutFuture = null; + } ContextHubServiceTransaction transaction = mTransactionQueue.remove(); transaction.setComplete(); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 73647dbbe978..e1f893953d66 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -2800,6 +2800,10 @@ class MediaRouter2ServiceImpl { final String providerId = route.getProviderId(); final MediaRoute2Provider provider = findProvider(providerId); if (provider == null) { + Slog.w( + TAG, + "Ignoring transferToRoute due to lack of matching provider for target: " + + route); return; } provider.transferToRoute( diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index a3c5d2d336f2..69f07d5c5f7b 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -1057,6 +1057,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde return -1; } + @NonNull private PlaybackInfo getVolumeAttributes() { int volumeType; AudioAttributes attributes; @@ -1850,6 +1851,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde return mFlags; } + @NonNull @Override public PlaybackInfo getVolumeAttributes() { return MediaSessionRecord.this.getVolumeAttributes(); diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java index d25f52973085..5ea3e70f7957 100644 --- a/services/core/java/com/android/server/net/NetworkManagementService.java +++ b/services/core/java/com/android/server/net/NetworkManagementService.java @@ -20,6 +20,9 @@ import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -31,6 +34,9 @@ import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -143,6 +149,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { private final Object mQuotaLock = new Object(); private final Object mRulesLock = new Object(); + private final boolean mUseMeteredFirewallChains; + /** Set of interfaces with active quotas. */ @GuardedBy("mQuotaLock") private HashMap<String, Long> mActiveQuotas = Maps.newHashMap(); @@ -150,9 +158,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @GuardedBy("mQuotaLock") private HashMap<String, Long> mActiveAlerts = Maps.newHashMap(); /** Set of UIDs denied on metered networks. */ + // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains. @GuardedBy("mRulesLock") private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray(); /** Set of UIDs allowed on metered networks. */ + // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains. @GuardedBy("mRulesLock") private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray(); /** Set of UIDs with cleartext penalties. */ @@ -196,10 +206,32 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @GuardedBy("mRulesLock") private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray(); + /** + * Contains the per-UID firewall rules that are used to allowlist the app from metered-network + * restrictions when data saver is enabled. + */ + @GuardedBy("mRulesLock") + private final SparseIntArray mUidMeteredFirewallAllowRules = new SparseIntArray(); + + /** + * Contains the per-UID firewall rules that are used to deny app access to metered networks + * due to user action. + */ + @GuardedBy("mRulesLock") + private final SparseIntArray mUidMeteredFirewallDenyUserRules = new SparseIntArray(); + + /** + * Contains the per-UID firewall rules that are used to deny app access to metered networks + * due to admin action. + */ + @GuardedBy("mRulesLock") + private final SparseIntArray mUidMeteredFirewallDenyAdminRules = new SparseIntArray(); + /** Set of states for the child firewall chains. True if the chain is active. */ @GuardedBy("mRulesLock") final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray(); + // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains. @GuardedBy("mQuotaLock") private volatile boolean mDataSaverMode; @@ -217,6 +249,15 @@ public class NetworkManagementService extends INetworkManagementService.Stub { mContext = context; mDeps = deps; + mUseMeteredFirewallChains = Flags.useMeteredFirewallChains(); + + if (mUseMeteredFirewallChains) { + // These firewalls are always on and currently ConnectivityService does not allow + // changing their enabled state. + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_USER, true); + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_ADMIN, true); + } + mDaemonHandler = new Handler(FgThread.get().getLooper()); mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener(); @@ -410,33 +451,39 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } } - SparseBooleanArray uidRejectOnQuota = null; - SparseBooleanArray uidAcceptOnQuota = null; - synchronized (mRulesLock) { - size = mUidRejectOnMetered.size(); - if (size > 0) { - if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules"); - uidRejectOnQuota = mUidRejectOnMetered; - mUidRejectOnMetered = new SparseBooleanArray(); - } + if (!mUseMeteredFirewallChains) { + SparseBooleanArray uidRejectOnQuota = null; + SparseBooleanArray uidAcceptOnQuota = null; + synchronized (mRulesLock) { + size = mUidRejectOnMetered.size(); + if (size > 0) { + if (DBG) { + Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules"); + } + uidRejectOnQuota = mUidRejectOnMetered; + mUidRejectOnMetered = new SparseBooleanArray(); + } - size = mUidAllowOnMetered.size(); - if (size > 0) { - if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules"); - uidAcceptOnQuota = mUidAllowOnMetered; - mUidAllowOnMetered = new SparseBooleanArray(); + size = mUidAllowOnMetered.size(); + if (size > 0) { + if (DBG) { + Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules"); + } + uidAcceptOnQuota = mUidAllowOnMetered; + mUidAllowOnMetered = new SparseBooleanArray(); + } } - } - if (uidRejectOnQuota != null) { - for (int i = 0; i < uidRejectOnQuota.size(); i++) { - setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i), - uidRejectOnQuota.valueAt(i)); + if (uidRejectOnQuota != null) { + for (int i = 0; i < uidRejectOnQuota.size(); i++) { + setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i), + uidRejectOnQuota.valueAt(i)); + } } - } - if (uidAcceptOnQuota != null) { - for (int i = 0; i < uidAcceptOnQuota.size(); i++) { - setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i), - uidAcceptOnQuota.valueAt(i)); + if (uidAcceptOnQuota != null) { + for (int i = 0; i < uidAcceptOnQuota.size(); i++) { + setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i), + uidAcceptOnQuota.valueAt(i)); + } } } @@ -459,8 +506,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub { syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted "); syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby "); syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND); + if (mUseMeteredFirewallChains) { + syncFirewallChainLocked(FIREWALL_CHAIN_METERED_ALLOW, + FIREWALL_CHAIN_NAME_METERED_ALLOW); + syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_USER, + FIREWALL_CHAIN_NAME_METERED_DENY_USER); + syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_ADMIN, + FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN); + } - final int[] chains = { + final int[] chainsToEnable = { FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE, @@ -469,14 +524,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub { FIREWALL_CHAIN_BACKGROUND, }; - for (int chain : chains) { + for (int chain : chainsToEnable) { if (getFirewallChainState(chain)) { setFirewallChainEnabled(chain, true); } } } - try { getBatteryStats().noteNetworkStatsEnabled(); } catch (RemoteException e) { @@ -1077,6 +1131,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub { mContext.getSystemService(ConnectivityManager.class) .setDataSaverEnabled(enable); mDataSaverMode = enable; + if (mUseMeteredFirewallChains) { + // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW + // until ConnectivityService allows manipulation of the data saver mode via + // FIREWALL_CHAIN_METERED_ALLOW. + synchronized (mRulesLock) { + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable); + } + } return true; } else { final boolean changed = mNetdService.bandwidthEnableDataSaver(enable); @@ -1191,9 +1253,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub { setFirewallChainState(chain, enable); } - final String chainName = getFirewallChainName(chain); - if (chain == FIREWALL_CHAIN_NONE) { - throw new IllegalArgumentException("Bad child chain: " + chainName); + if (!isValidFirewallChainForSetEnabled(chain)) { + throw new IllegalArgumentException("Invalid chain for setFirewallChainEnabled: " + + NetworkPolicyLogger.getFirewallChainName(chain)); } final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); @@ -1205,38 +1267,29 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } } - private String getFirewallChainName(int chain) { - switch (chain) { - case FIREWALL_CHAIN_STANDBY: - return FIREWALL_CHAIN_NAME_STANDBY; - case FIREWALL_CHAIN_DOZABLE: - return FIREWALL_CHAIN_NAME_DOZABLE; - case FIREWALL_CHAIN_POWERSAVE: - return FIREWALL_CHAIN_NAME_POWERSAVE; - case FIREWALL_CHAIN_RESTRICTED: - return FIREWALL_CHAIN_NAME_RESTRICTED; - case FIREWALL_CHAIN_LOW_POWER_STANDBY: - return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; - case FIREWALL_CHAIN_BACKGROUND: - return FIREWALL_CHAIN_NAME_BACKGROUND; - default: - throw new IllegalArgumentException("Bad child chain: " + chain); - } + private boolean isValidFirewallChainForSetEnabled(int chain) { + return switch (chain) { + case FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE, + FIREWALL_CHAIN_RESTRICTED, FIREWALL_CHAIN_LOW_POWER_STANDBY, + FIREWALL_CHAIN_BACKGROUND -> true; + // METERED_* firewall chains are not yet supported by + // ConnectivityService#setFirewallChainEnabled. + default -> false; + }; } private int getFirewallType(int chain) { switch (chain) { case FIREWALL_CHAIN_STANDBY: + case FIREWALL_CHAIN_METERED_DENY_ADMIN: + case FIREWALL_CHAIN_METERED_DENY_USER: return FIREWALL_DENYLIST; case FIREWALL_CHAIN_DOZABLE: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_POWERSAVE: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_RESTRICTED: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_LOW_POWER_STANDBY: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_BACKGROUND: + case FIREWALL_CHAIN_METERED_ALLOW: return FIREWALL_ALLOWLIST; default: return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST; @@ -1360,6 +1413,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub { return mUidFirewallLowPowerStandbyRules; case FIREWALL_CHAIN_BACKGROUND: return mUidFirewallBackgroundRules; + case FIREWALL_CHAIN_METERED_ALLOW: + return mUidMeteredFirewallAllowRules; + case FIREWALL_CHAIN_METERED_DENY_USER: + return mUidMeteredFirewallDenyUserRules; + case FIREWALL_CHAIN_METERED_DENY_ADMIN: + return mUidMeteredFirewallDenyAdminRules; case FIREWALL_CHAIN_NONE: return mUidFirewallRules; default: @@ -1378,6 +1437,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + pw.println("Flags:"); + pw.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": " + mUseMeteredFirewallChains); + pw.println(); + synchronized (mQuotaLock) { pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString()); pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString()); @@ -1416,6 +1479,27 @@ public class NetworkManagementService extends INetworkManagementService.Stub { pw.print("UID firewall background chain enabled: "); pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND)); dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules); + + pw.print("UID firewall metered allow chain enabled (Data saver mode): "); + // getFirewallChainState should maintain a duplicated state from mDataSaverMode when + // mUseMeteredFirewallChains is enabled. + pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_ALLOW, + mUidMeteredFirewallAllowRules); + + pw.print("UID firewall metered deny_user chain enabled (always-on): "); + // This always-on state should be reflected by getFirewallChainState when + // mUseMeteredFirewallChains is enabled. + pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_USER, + mUidMeteredFirewallDenyUserRules); + + pw.print("UID firewall metered deny_admin chain enabled (always-on): "); + // This always-on state should be reflected by getFirewallChainState when + // mUseMeteredFirewallChains is enabled. + pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN, + mUidMeteredFirewallDenyAdminRules); } pw.print("Firewall enabled: "); pw.println(mFirewallEnabled); @@ -1520,14 +1604,40 @@ public class NetworkManagementService extends INetworkManagementService.Stub { if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background"); return true; } - if (mUidRejectOnMetered.get(uid)) { - if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data" - + " in the background"); - return true; - } - if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) { - if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); - return true; + if (mUseMeteredFirewallChains) { + if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER) + && mUidMeteredFirewallDenyUserRules.get(uid) == FIREWALL_RULE_DENY) { + if (DBG) { + Slog.d(TAG, "Uid " + uid + " restricted because of user-restricted metered" + + " data in the background"); + } + return true; + } + if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN) + && mUidMeteredFirewallDenyAdminRules.get(uid) == FIREWALL_RULE_DENY) { + if (DBG) { + Slog.d(TAG, "Uid " + uid + " restricted because of admin-restricted metered" + + " data in the background"); + } + return true; + } + if (getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW) + && mUidMeteredFirewallAllowRules.get(uid) != FIREWALL_RULE_ALLOW) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); + return true; + } + } else { + if (mUidRejectOnMetered.get(uid)) { + if (DBG) { + Slog.d(TAG, "Uid " + uid + + " restricted because of no metered data in the background"); + } + return true; + } + if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); + return true; + } } return false; } diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 8e2d7780204a..681aa8aef219 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -19,6 +19,9 @@ import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -28,6 +31,9 @@ import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -379,7 +385,7 @@ public class NetworkPolicyLogger { return "Interfaces of netId=" + netId + " changed to " + newIfaces; } - private static String getFirewallChainName(int chain) { + static String getFirewallChainName(int chain) { switch (chain) { case FIREWALL_CHAIN_DOZABLE: return FIREWALL_CHAIN_NAME_DOZABLE; @@ -393,6 +399,12 @@ public class NetworkPolicyLogger { return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; case FIREWALL_CHAIN_BACKGROUND: return FIREWALL_CHAIN_NAME_BACKGROUND; + case FIREWALL_CHAIN_METERED_ALLOW: + return FIREWALL_CHAIN_NAME_METERED_ALLOW; + case FIREWALL_CHAIN_METERED_DENY_USER: + return FIREWALL_CHAIN_NAME_METERED_DENY_USER; + case FIREWALL_CHAIN_METERED_DENY_ADMIN: + return FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN; default: return String.valueOf(chain); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 22f5332e150c..c60ac3a74ebd 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -60,6 +60,9 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -514,6 +517,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ private boolean mBackgroundNetworkRestricted; + /** + * Whether or not metered firewall chains should be used for uid policy controlling access to + * metered networks. + */ + private boolean mUseMeteredFirewallChains; + // See main javadoc for instructions on how to use these locks. final Object mUidRulesFirstLock = new Object(); final Object mNetworkPoliciesSecondLock = new Object(); @@ -997,6 +1006,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mAppStandby = LocalServices.getService(AppStandbyInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + mUseMeteredFirewallChains = Flags.useMeteredFirewallChains(); + synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { updatePowerSaveAllowlistUL(); @@ -4030,8 +4041,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.println(); fout.println("Flags:"); - fout.println("Network blocked for TOP_SLEEPING and above: " + fout.println(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE + ": " + mBackgroundNetworkRestricted); + fout.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": " + + mUseMeteredFirewallChains); fout.println(); fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode); @@ -5373,23 +5386,44 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { postUidRulesChangedMsg(uid, uidRules); } - // Note that the conditionals below are for avoiding unnecessary calls to netd. - // TODO: Measure the performance for doing a no-op call to netd so that we can - // remove the conditionals to simplify the logic below. We can also further reduce - // some calls to netd if they turn out to be costly. - final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED - | BLOCKED_METERED_REASON_USER_RESTRICTED; - if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE - || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) { - setMeteredNetworkDenylist(uid, - (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE); - } - final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND - | ALLOWED_METERED_REASON_USER_EXEMPTED; - if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE - || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) { - setMeteredNetworkAllowlist(uid, - (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE); + if (mUseMeteredFirewallChains) { + if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_ADMIN_DISABLED) + != BLOCKED_REASON_NONE) { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DENY); + } else { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DEFAULT); + } + if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_USER_RESTRICTED) + != BLOCKED_REASON_NONE) { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY); + } else { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DEFAULT); + } + if ((newAllowedReasons & (ALLOWED_METERED_REASON_FOREGROUND + | ALLOWED_METERED_REASON_USER_EXEMPTED)) != ALLOWED_REASON_NONE) { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW); + } else { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DEFAULT); + } + } else { + // Note that the conditionals below are for avoiding unnecessary calls to netd. + // TODO: Measure the performance for doing a no-op call to netd so that we can + // remove the conditionals to simplify the logic below. We can also further reduce + // some calls to netd if they turn out to be costly. + final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED + | BLOCKED_METERED_REASON_USER_RESTRICTED; + if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE + || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) { + setMeteredNetworkDenylist(uid, + (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE); + } + final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND + | ALLOWED_METERED_REASON_USER_EXEMPTED; + if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE + || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) { + setMeteredNetworkAllowlist(uid, + (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE); + } } } @@ -6149,6 +6183,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } else if (chain == FIREWALL_CHAIN_BACKGROUND) { mUidFirewallBackgroundRules.put(uid, rule); } + // Note that we do not need keep a separate cache of uid rules for chains that we do + // not call #setUidFirewallRulesUL for. try { mNetworkManager.setFirewallUidRule(chain, uid, rule); @@ -6206,10 +6242,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { FIREWALL_RULE_DEFAULT); mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT); - mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false); - mLogger.meteredAllowlistChanged(uid, false); - mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false); - mLogger.meteredDenylistChanged(uid, false); + if (mUseMeteredFirewallChains) { + mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, + FIREWALL_RULE_DEFAULT); + mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, + FIREWALL_RULE_DEFAULT); + mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, uid, + FIREWALL_RULE_DEFAULT); + } else { + mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false); + mLogger.meteredAllowlistChanged(uid, false); + mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false); + mLogger.meteredDenylistChanged(uid, false); + } } catch (IllegalStateException e) { Log.wtf(TAG, "problem resetting firewall uid rules for " + uid, e); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig index d9491de52d87..e986dd81b94b 100644 --- a/services/core/java/com/android/server/net/flags.aconfig +++ b/services/core/java/com/android/server/net/flags.aconfig @@ -7,3 +7,13 @@ flag { description: "Block network access for apps in a low importance background state" bug: "304347838" } + +flag { + name: "use_metered_firewall_chains" + namespace: "backstage_power" + description: "Use metered firewall chains to control access to metered networks" + bug: "336693007" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index bf49671e2d82..13429db47ebd 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -22,12 +22,14 @@ import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.media.audio.Flags.focusExclusiveWithRecording; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; +import android.Manifest.permission; import android.annotation.IntDef; import android.app.ActivityManager; import android.app.KeyguardManager; @@ -135,7 +137,7 @@ public final class NotificationAttentionHelper { private LogicalLight mAttentionLight; private final boolean mUseAttentionLight; - boolean mHasLight = true; + boolean mHasLight; private final SettingsObserver mSettingsObserver; @@ -149,7 +151,7 @@ public final class NotificationAttentionHelper { private boolean mInCallStateOffHook = false; private boolean mScreenOn = true; private boolean mUserPresent = false; - boolean mNotificationPulseEnabled; + private boolean mNotificationPulseEnabled; private final Uri mInCallNotificationUri; private final AudioAttributes mInCallNotificationAudioAttributes; private final float mInCallNotificationVolume; @@ -223,7 +225,10 @@ public final class NotificationAttentionHelper { mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), - mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET)); + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET), + record -> mPackageManager.checkPermission( + permission.RECEIVE_EMERGENCY_BROADCAST, + record.getSbn().getPackageName()) == PERMISSION_GRANTED); return new StrategyAvalanche( mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), @@ -231,14 +236,17 @@ public final class NotificationAttentionHelper { mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT), - appStrategy); + appStrategy, appStrategy.mExemptionProvider); } else { return new StrategyPerApp( mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), - mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET)); + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET), + record -> mPackageManager.checkPermission( + permission.RECEIVE_EMERGENCY_BROADCAST, + record.getSbn().getPackageName()) == PERMISSION_GRANTED); } } @@ -305,6 +313,13 @@ public final class NotificationAttentionHelper { } private void loadUserSettings() { + boolean pulseEnabled = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0; + if (mNotificationPulseEnabled != pulseEnabled) { + mNotificationPulseEnabled = pulseEnabled; + updateLightsLocked(); + } + if (Flags.politeNotifications()) { try { mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser()); @@ -874,6 +889,9 @@ public final class NotificationAttentionHelper { boolean canShowLightsLocked(final NotificationRecord record, final Signals signals, boolean aboveThreshold) { + if (!mSystemReady) { + return false; + } // device lacks light if (!mHasLight) { return false; @@ -1088,6 +1106,11 @@ public final class NotificationAttentionHelper { } } + // Returns true if a notification should be exempted from attenuation + private interface ExemptionProvider { + boolean isExempted(NotificationRecord record); + } + @VisibleForTesting abstract static class PolitenessStrategy { static final int POLITE_STATE_DEFAULT = 0; @@ -1118,8 +1141,10 @@ public final class NotificationAttentionHelper { protected boolean mIsActive = true; + protected final ExemptionProvider mExemptionProvider; + public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted) { + int volumeMuted, ExemptionProvider exemptionProvider) { mVolumeStates = new HashMap<>(); mLastUpdatedTimestampByPackage = new HashMap<>(); @@ -1127,6 +1152,7 @@ public final class NotificationAttentionHelper { this.mTimeoutMuted = timeoutMuted; this.mVolumePolite = volumePolite / 100.0f; this.mVolumeMuted = volumeMuted / 100.0f; + this.mExemptionProvider = exemptionProvider; } abstract void onNotificationPosted(NotificationRecord record); @@ -1284,8 +1310,8 @@ public final class NotificationAttentionHelper { private final int mMaxPostedForReset; public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted, int maxPosted) { - super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider); mNumPosted = new HashMap<>(); mMaxPostedForReset = maxPosted; @@ -1306,7 +1332,12 @@ public final class NotificationAttentionHelper { final String key = getChannelKey(record); @PolitenessState final int currState = getPolitenessState(record); - @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif); + @PolitenessState int nextState; + if (Flags.politeNotificationsAttnUpdate()) { + nextState = getNextState(currState, timeSinceLastNotif, record); + } else { + nextState = getNextState(currState, timeSinceLastNotif); + } // Reset to default state if number of posted notifications exceed this value when muted int numPosted = mNumPosted.getOrDefault(key, 0) + 1; @@ -1324,6 +1355,14 @@ public final class NotificationAttentionHelper { mVolumeStates.put(key, nextState); } + @PolitenessState int getNextState(@PolitenessState final int currState, + final long timeSinceLastNotif, final NotificationRecord record) { + if (mExemptionProvider.isExempted(record)) { + return POLITE_STATE_DEFAULT; + } + return getNextState(currState, timeSinceLastNotif); + } + @Override public void onUserInteraction(final NotificationRecord record) { super.onUserInteraction(record); @@ -1344,8 +1383,9 @@ public final class NotificationAttentionHelper { private long mLastAvalancheTriggerTimestamp = 0; StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) { - super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy, + ExemptionProvider exemptionProvider) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider); mTimeoutAvalanche = timeoutAvalanche; mAppStrategy = appStrategy; @@ -1518,7 +1558,7 @@ public final class NotificationAttentionHelper { return true; } - return false; + return mExemptionProvider.isExempted(record); } private boolean isAvalancheExempted(final NotificationRecord record) { @@ -1721,8 +1761,6 @@ public final class NotificationAttentionHelper { void setLights(LogicalLight light) { mNotificationLight = light; mAttentionLight = light; - mNotificationPulseEnabled = true; - mHasLight = true; } @VisibleForTesting diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 4f87c83bb0d7..44e76941c09b 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5832,7 +5832,16 @@ public class NotificationManagerService extends SystemService { @Override public ComponentName getEffectsSuppressor() { - return !mEffectsSuppressors.isEmpty() ? mEffectsSuppressors.get(0) : null; + ComponentName suppressor = !mEffectsSuppressors.isEmpty() + ? mEffectsSuppressors.get(0) + : null; + if (isCallerSystemOrSystemUiOrShell() || suppressor == null + || mPackageManagerInternal.isSameApp(suppressor.getPackageName(), + Binder.getCallingUid(), UserHandle.getUserId(Binder.getCallingUid()))) { + return suppressor; + } + + return null; } @Override @@ -7225,7 +7234,15 @@ public class NotificationManagerService extends SystemService { callingUid, userId, true, false, "cancelNotificationWithTag", pkg); // ensure opPkg is delegate if does not match pkg - int uid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + + int uid = INVALID_UID; + + try { + uid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + } catch (NameNotFoundException e) { + // package either never existed so there's no posted notification or it's being + // uninstalled so we'll be cleaning it up soon. log and return immediately below. + } if (uid == INVALID_UID) { Slog.w(TAG, opPkg + ":" + callingUid + " trying to cancel notification " @@ -7319,7 +7336,13 @@ public class NotificationManagerService extends SystemService { // Can throw a SecurityException if the calling uid doesn't have permission to post // as "pkg" - final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + int notificationUid = INVALID_UID; + + try { + notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + } catch (NameNotFoundException e) { + // not great - throw immediately below + } if (notificationUid == INVALID_UID) { throw new SecurityException("Caller " + opPkg + ":" + callingUid @@ -7876,7 +7899,8 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting - int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) { + int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) + throws NameNotFoundException { if (userId == USER_ALL) { userId = USER_SYSTEM; } @@ -7887,12 +7911,8 @@ public class NotificationManagerService extends SystemService { return callingUid; } - int targetUid = INVALID_UID; - try { - targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId); - } catch (NameNotFoundException e) { - /* ignore, handled by caller */ - } + int targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId); + // posted from app A on behalf of app B if (isCallerAndroid(callingPkg, callingUid) || mPreferencesHelper.isDelegateAllowed( diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java index 681dd0b49f4e..96ab2ccc8611 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java +++ b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java @@ -33,6 +33,7 @@ import android.graphics.Bitmap; import android.os.BadParcelableException; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; @@ -41,7 +42,10 @@ import android.system.ErrnoException; import android.system.Os; import android.util.Log; +import com.android.internal.infra.AndroidFuture; + import java.util.concurrent.Executor; +import java.util.concurrent.TimeoutException; /** * Util methods for ensuring the Bundle passed in various methods are read-only and restricted to @@ -78,16 +82,16 @@ public class BundleUtil { if (canMarshall(obj) || obj instanceof CursorWindow) { continue; } - - if (obj instanceof ParcelFileDescriptor) { + if (obj instanceof Bundle) { + sanitizeInferenceParams((Bundle) obj); + } else if (obj instanceof ParcelFileDescriptor) { validatePfdReadOnly((ParcelFileDescriptor) obj); } else if (obj instanceof SharedMemory) { ((SharedMemory) obj).setProtect(PROT_READ); } else if (obj instanceof Bitmap) { - if (((Bitmap) obj).isMutable()) { - throw new BadParcelableException( - "Encountered a mutable Bitmap in the Bundle at key : " + key); - } + validateBitmap((Bitmap) obj); + } else if (obj instanceof Parcelable[]) { + validateParcelableArray((Parcelable[]) obj); } else { throw new BadParcelableException( "Unsupported Parcelable type encountered in the Bundle: " @@ -125,20 +129,20 @@ public class BundleUtil { continue; } - if (obj instanceof ParcelFileDescriptor) { + if (obj instanceof Bundle) { + sanitizeResponseParams((Bundle) obj); + } else if (obj instanceof ParcelFileDescriptor) { validatePfdReadOnly((ParcelFileDescriptor) obj); } else if (obj instanceof Bitmap) { - if (((Bitmap) obj).isMutable()) { - throw new BadParcelableException( - "Encountered a mutable Bitmap in the Bundle at key : " + key); - } + validateBitmap((Bitmap) obj); + } else if (obj instanceof Parcelable[]) { + validateParcelableArray((Parcelable[]) obj); } else { throw new BadParcelableException( "Unsupported Parcelable type encountered in the Bundle: " + obj.getClass().getSimpleName()); } } - Log.e(TAG, "validateResponseParams : Finished"); } /** @@ -183,7 +187,8 @@ public class BundleUtil { public static IStreamingResponseCallback wrapWithValidation( IStreamingResponseCallback streamingResponseCallback, - Executor resourceClosingExecutor) { + Executor resourceClosingExecutor, + AndroidFuture future) { return new IStreamingResponseCallback.Stub() { @Override public void onNewContent(Bundle processedResult) throws RemoteException { @@ -203,6 +208,7 @@ public class BundleUtil { streamingResponseCallback.onSuccess(resultBundle); } finally { resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle)); + future.complete(null); } } @@ -210,6 +216,7 @@ public class BundleUtil { public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) throws RemoteException { streamingResponseCallback.onFailure(errorCode, errorMessage, errorParams); + future.completeExceptionally(new TimeoutException()); } @Override @@ -237,7 +244,8 @@ public class BundleUtil { } public static IResponseCallback wrapWithValidation(IResponseCallback responseCallback, - Executor resourceClosingExecutor) { + Executor resourceClosingExecutor, + AndroidFuture future) { return new IResponseCallback.Stub() { @Override public void onSuccess(Bundle resultBundle) @@ -247,6 +255,7 @@ public class BundleUtil { responseCallback.onSuccess(resultBundle); } finally { resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle)); + future.complete(null); } } @@ -254,6 +263,7 @@ public class BundleUtil { public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) throws RemoteException { responseCallback.onFailure(errorCode, errorMessage, errorParams); + future.completeExceptionally(new TimeoutException()); } @Override @@ -280,17 +290,20 @@ public class BundleUtil { } - public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback) { + public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback, + AndroidFuture future) { return new ITokenInfoCallback.Stub() { @Override public void onSuccess(TokenInfo tokenInfo) throws RemoteException { responseCallback.onSuccess(tokenInfo); + future.complete(null); } @Override public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) throws RemoteException { responseCallback.onFailure(errorCode, errorMessage, errorParams); + future.completeExceptionally(new TimeoutException()); } }; } @@ -310,6 +323,26 @@ public class BundleUtil { } } + private static void validateParcelableArray(Parcelable[] parcelables) { + if (parcelables.length > 0 + && parcelables[0] instanceof ParcelFileDescriptor) { + // Safe to cast + validatePfdsReadOnly(parcelables); + } else if (parcelables.length > 0 + && parcelables[0] instanceof Bitmap) { + validateBitmapsImmutable(parcelables); + } else { + throw new BadParcelableException( + "Could not cast to any known parcelable array"); + } + } + + public static void validatePfdsReadOnly(Parcelable[] pfds) { + for (Parcelable pfd : pfds) { + validatePfdReadOnly((ParcelFileDescriptor) pfd); + } + } + public static void validatePfdReadOnly(ParcelFileDescriptor pfd) { if (pfd == null) { return; @@ -326,6 +359,19 @@ public class BundleUtil { } } + private static void validateBitmap(Bitmap obj) { + if (obj.isMutable()) { + throw new BadParcelableException( + "Encountered a mutable Bitmap in the Bundle at key : " + obj); + } + } + + private static void validateBitmapsImmutable(Parcelable[] bitmaps) { + for (Parcelable bitmap : bitmaps) { + validateBitmap((Bitmap) bitmap); + } + } + public static void tryCloseResource(Bundle bundle) { if (bundle == null || bundle.isEmpty() || !bundle.hasFileDescriptors()) { return; diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java index 235e3cd7c9d2..cdc1a5e738a0 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -16,6 +16,7 @@ package com.android.server.ondeviceintelligence; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY; import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY; import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY; import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY; @@ -33,6 +34,7 @@ import android.annotation.RequiresPermission; import android.app.AppGlobals; import android.app.ondeviceintelligence.DownloadCallback; import android.app.ondeviceintelligence.Feature; +import android.app.ondeviceintelligence.FeatureDetails; import android.app.ondeviceintelligence.IDownloadCallback; import android.app.ondeviceintelligence.IFeatureCallback; import android.app.ondeviceintelligence.IFeatureDetailsCallback; @@ -64,6 +66,7 @@ import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.provider.Settings; import android.service.ondeviceintelligence.IOnDeviceIntelligenceService; import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService; import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback; @@ -82,13 +85,17 @@ import com.android.internal.infra.ServiceConnector; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback; import java.io.FileDescriptor; import java.io.IOException; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * This is the system service for handling calls on the @@ -109,9 +116,10 @@ public class OnDeviceIntelligenceManagerService extends SystemService { /** Handler message to {@link #resetTemporaryServices()} */ private static final int MSG_RESET_TEMPORARY_SERVICE = 0; - /** Handler message to clean up temporary broadcast keys. */ private static final int MSG_RESET_BROADCAST_KEYS = 1; + /** Handler message to clean up temporary config namespace. */ + private static final int MSG_RESET_CONFIG_NAMESPACE = 2; /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_SERVICE_ENABLED = true; @@ -123,6 +131,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { private final Executor resourceClosingExecutor = Executors.newCachedThreadPool(); private final Executor callbackExecutor = Executors.newCachedThreadPool(); private final Executor broadcastExecutor = Executors.newCachedThreadPool(); + private final Executor mConfigExecutor = Executors.newCachedThreadPool(); private final Context mContext; @@ -135,16 +144,23 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @GuardedBy("mLock") private String[] mTemporaryServiceNames; - @GuardedBy("mLock") private String[] mTemporaryBroadcastKeys; @GuardedBy("mLock") private String mBroadcastPackageName; + @GuardedBy("mLock") + private String mTemporaryConfigNamespace; + + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = + this::sendUpdatedConfig; + /** * Handler used to reset the temporary service names. */ private Handler mTemporaryHandler; + private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper()); + public OnDeviceIntelligenceManagerService(Context context) { super(context); @@ -204,8 +220,16 @@ public class OnDeviceIntelligenceManagerService extends SystemService { return; } ensureRemoteIntelligenceServiceInitialized(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.getVersion(remoteCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.getVersion(new RemoteCallback( + result -> { + remoteCallback.sendResult(result); + future.complete(null); + })); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); } @Override @@ -225,8 +249,25 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.getFeature(callerUid, id, featureCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.getFeature(callerUid, id, new IFeatureCallback.Stub() { + @Override + public void onSuccess(Feature result) throws RemoteException { + featureCallback.onSuccess(result); + future.complete(null); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) throws RemoteException { + featureCallback.onFailure(errorCode, errorMessage, errorParams); + future.completeExceptionally(new TimeoutException()); + } + }); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); } @Override @@ -246,9 +287,29 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.listFeatures(callerUid, - listFeaturesCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.listFeatures(callerUid, + new IListFeaturesCallback.Stub() { + @Override + public void onSuccess(List<Feature> result) + throws RemoteException { + listFeaturesCallback.onSuccess(result); + future.complete(null); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) + throws RemoteException { + listFeaturesCallback.onFailure(errorCode, errorMessage, + errorParams); + future.completeExceptionally(new TimeoutException()); + } + }); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); } @Override @@ -270,9 +331,29 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.getFeatureDetails(callerUid, feature, - featureDetailsCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.getFeatureDetails(callerUid, feature, + new IFeatureDetailsCallback.Stub() { + @Override + public void onSuccess(FeatureDetails result) + throws RemoteException { + future.complete(null); + featureDetailsCallback.onSuccess(result); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) + throws RemoteException { + future.completeExceptionally(null); + featureDetailsCallback.onFailure(errorCode, + errorMessage, errorParams); + } + }); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); } @Override @@ -293,10 +374,20 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.requestFeatureDownload(callerUid, feature, - wrapCancellationFuture(cancellationSignalFuture), - downloadCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + ListenableDownloadCallback listenableDownloadCallback = + new ListenableDownloadCallback( + downloadCallback, + mMainHandler, future, getIdleTimeoutMs()); + service.requestFeatureDownload(callerUid, feature, + wrapCancellationFuture(cancellationSignalFuture), + listenableDownloadCallback); + return future; // this future has no timeout because, actual download + // might take long, but we fail early if there is no progress callbacks. + } + ); } @@ -323,11 +414,15 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteInferenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - result = mRemoteInferenceService.post( - service -> service.requestTokenInfo(callerUid, feature, - request, - wrapCancellationFuture(cancellationSignalFuture), - wrapWithValidation(tokenInfoCallback))); + result = mRemoteInferenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.requestTokenInfo(callerUid, feature, + request, + wrapCancellationFuture(cancellationSignalFuture), + wrapWithValidation(tokenInfoCallback, future)); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request), resourceClosingExecutor); } finally { @@ -362,13 +457,18 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteInferenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - result = mRemoteInferenceService.post( - service -> service.processRequest(callerUid, feature, - request, - requestType, - wrapCancellationFuture(cancellationSignalFuture), - wrapProcessingFuture(processingSignalFuture), - wrapWithValidation(responseCallback, resourceClosingExecutor))); + result = mRemoteInferenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.processRequest(callerUid, feature, + request, + requestType, + wrapCancellationFuture(cancellationSignalFuture), + wrapProcessingFuture(processingSignalFuture), + wrapWithValidation(responseCallback, + resourceClosingExecutor, future)); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request), resourceClosingExecutor); } finally { @@ -402,13 +502,18 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteInferenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - result = mRemoteInferenceService.post( - service -> service.processRequestStreaming(callerUid, - feature, - request, requestType, - wrapCancellationFuture(cancellationSignalFuture), - wrapProcessingFuture(processingSignalFuture), - streamingCallback)); + result = mRemoteInferenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.processRequestStreaming(callerUid, + feature, + request, requestType, + wrapCancellationFuture(cancellationSignalFuture), + wrapProcessingFuture(processingSignalFuture), + wrapWithValidation(streamingCallback, + resourceClosingExecutor, future)); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request), resourceClosingExecutor); } finally { @@ -497,12 +602,14 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @NonNull IOnDeviceSandboxedInferenceService service) { try { ensureRemoteIntelligenceServiceInitialized(); + service.registerRemoteStorageService( + getIRemoteStorageService()); mRemoteOnDeviceIntelligenceService.run( IOnDeviceIntelligenceService::notifyInferenceServiceConnected); broadcastExecutor.execute( () -> registerModelLoadingBroadcasts(service)); - service.registerRemoteStorageService( - getIRemoteStorageService()); + mConfigExecutor.execute( + () -> registerDeviceConfigChangeListener()); } catch (RemoteException ex) { Slog.w(TAG, "Failed to send connected event", ex); } @@ -562,6 +669,58 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } } + private void registerDeviceConfigChangeListener() { + Log.e(TAG, "registerDeviceConfigChangeListener"); + String configNamespace = getConfigNamespace(); + if (configNamespace.isEmpty()) { + Slog.e(TAG, "config_defaultOnDeviceIntelligenceDeviceConfigNamespace is empty"); + return; + } + DeviceConfig.addOnPropertiesChangedListener( + configNamespace, + mConfigExecutor, + mOnPropertiesChangedListener); + } + + private String getConfigNamespace() { + synchronized (mLock) { + if (mTemporaryConfigNamespace != null) { + return mTemporaryConfigNamespace; + } + + return mContext.getResources().getString( + R.string.config_defaultOnDeviceIntelligenceDeviceConfigNamespace); + } + } + + private void sendUpdatedConfig( + DeviceConfig.Properties props) { + Log.e(TAG, "sendUpdatedConfig"); + + PersistableBundle persistableBundle = new PersistableBundle(); + for (String key : props.getKeyset()) { + persistableBundle.putString(key, props.getString(key, "")); + } + Bundle bundle = new Bundle(); + bundle.putParcelable(DEVICE_CONFIG_UPDATE_BUNDLE_KEY, persistableBundle); + ensureRemoteInferenceServiceInitialized(); + Log.e(TAG, "sendUpdatedConfig: BUNDLE: " + bundle); + + mRemoteInferenceService.run(service -> service.updateProcessingState(bundle, + new IProcessingUpdateStatusCallback.Stub() { + @Override + public void onSuccess(PersistableBundle result) { + Slog.d(TAG, "Config update successful." + result); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + Slog.e(TAG, "Config update failed with code [" + + String.valueOf(errorCode) + "] and message = " + errorMessage); + } + })); + } + @NonNull private IRemoteStorageService.Stub getIRemoteStorageService() { return new IRemoteStorageService.Stub() { @@ -753,8 +912,23 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void setTemporaryDeviceConfigNamespace(@NonNull String configNamespace, + int durationMs) { + Objects.requireNonNull(configNamespace); + enforceShellOnly(Binder.getCallingUid(), "setTemporaryDeviceConfigNamespace"); + mContext.enforceCallingPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + synchronized (mLock) { + mTemporaryConfigNamespace = configNamespace; + if (durationMs != -1) { + getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_CONFIG_NAMESPACE, + durationMs); + } + } + } + + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void resetTemporaryServices() { - enforceShellOnly(Binder.getCallingUid(), "resetTemporaryServices"); mContext.enforceCallingPermission( Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); synchronized (mLock) { @@ -841,17 +1015,17 @@ public class OnDeviceIntelligenceManagerService extends SystemService { mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) { @Override public void handleMessage(Message msg) { - if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { - synchronized (mLock) { + synchronized (mLock) { + if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { resetTemporaryServices(); - } - } else if (msg.what == MSG_RESET_BROADCAST_KEYS) { - synchronized (mLock) { + } else if (msg.what == MSG_RESET_BROADCAST_KEYS) { mTemporaryBroadcastKeys = null; mBroadcastPackageName = SYSTEM_PACKAGE; + } else if (msg.what == MSG_RESET_CONFIG_NAMESPACE) { + mTemporaryConfigNamespace = null; + } else { + Slog.wtf(TAG, "invalid handler msg: " + msg); } - } else { - Slog.wtf(TAG, "invalid handler msg: " + msg); } } }; @@ -859,4 +1033,10 @@ public class OnDeviceIntelligenceManagerService extends SystemService { return mTemporaryHandler; } + + private long getIdleTimeoutMs() { + return Settings.Secure.getLongForUser(mContext.getContentResolver(), + Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1), + mContext.getUserId()); + } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java index 5744b5c3c2c4..d2c84fa1b18a 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java @@ -17,6 +17,7 @@ package com.android.server.ondeviceintelligence; import android.annotation.NonNull; +import android.os.Binder; import android.os.ShellCommand; import java.io.PrintWriter; @@ -45,6 +46,8 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { return getConfiguredServices(); case "set-model-broadcasts": return setBroadcastKeys(); + case "set-deviceconfig-namespace": + return setDeviceConfigNamespace(); default: return handleDefaultCommands(cmd); } @@ -69,6 +72,10 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { + "[ReceiverPackageName] " + "[DURATION] To set the names of broadcast intent keys that are to be " + "emitted for cts tests."); + pw.println( + " set-deviceconfig-namespace [DeviceConfigNamespace] " + + "[DURATION] To set the device config namespace " + + "to use for cts tests."); } private int setTemporaryServices() { @@ -78,6 +85,8 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { if (getRemainingArgsCount() == 0 && intelligenceServiceName == null && inferenceServiceName == null) { + OnDeviceIntelligenceManagerService.enforceShellOnly(Binder.getCallingUid(), + "resetTemporaryServices"); mService.resetTemporaryServices(); out.println("OnDeviceIntelligenceManagerService temporary reset. "); return 0; @@ -120,4 +129,16 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { return 0; } + private int setDeviceConfigNamespace() { + final PrintWriter out = getOutPrintWriter(); + final String configNamespace = getNextArg(); + + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryDeviceConfigNamespace(configNamespace, duration); + out.println("OnDeviceIntelligence DeviceConfig Namespace temporarily set to " + + configNamespace + + " for " + duration + "ms"); + return 0; + } + }
\ No newline at end of file diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java index 48258d7bea72..ac9747aa83b3 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java @@ -22,17 +22,21 @@ import static android.content.Context.BIND_INCLUDE_CAPABILITIES; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.provider.Settings; import android.service.ondeviceintelligence.IOnDeviceIntelligenceService; import android.service.ondeviceintelligence.OnDeviceIntelligenceService; import com.android.internal.infra.ServiceConnector; +import java.util.concurrent.TimeUnit; + /** * Manages the connection to the remote on-device intelligence service. Also, handles unbinding * logic set by the service implementation via a Secure Settings flag. */ public class RemoteOnDeviceIntelligenceService extends ServiceConnector.Impl<IOnDeviceIntelligenceService> { + private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(4); private static final String TAG = RemoteOnDeviceIntelligenceService.class.getSimpleName(); @@ -48,9 +52,15 @@ public class RemoteOnDeviceIntelligenceService extends } @Override + protected long getRequestTimeoutMs() { + return LONG_TIMEOUT; + } + + @Override protected long getAutoDisconnectTimeoutMs() { - // Disable automatic unbinding. - // TODO: add logic to fetch this flag via SecureSettings. - return -1; + return Settings.Secure.getLongForUser(mContext.getContentResolver(), + Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, + TimeUnit.SECONDS.toMillis(30), + mContext.getUserId()); } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java index 69ba1d2fb599..18b13838ea7c 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java @@ -22,18 +22,24 @@ import static android.content.Context.BIND_INCLUDE_CAPABILITIES; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.provider.Settings; import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService; import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService; import com.android.internal.infra.ServiceConnector; +import java.util.concurrent.TimeUnit; + /** - * Manages the connection to the remote on-device sand boxed inference service. Also, handles unbinding + * Manages the connection to the remote on-device sand boxed inference service. Also, handles + * unbinding * logic set by the service implementation via a SecureSettings flag. */ public class RemoteOnDeviceSandboxedInferenceService extends ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> { + private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(1); + /** * Creates an instance of {@link ServiceConnector} * @@ -54,11 +60,17 @@ public class RemoteOnDeviceSandboxedInferenceService extends connect(); } + @Override + protected long getRequestTimeoutMs() { + return LONG_TIMEOUT; + } + @Override protected long getAutoDisconnectTimeoutMs() { - // Disable automatic unbinding. - // TODO: add logic to fetch this flag via SecureSettings. - return -1; + return Settings.Secure.getLongForUser(mContext.getContentResolver(), + Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, + TimeUnit.SECONDS.toMillis(30), + mContext.getUserId()); } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java new file mode 100644 index 000000000000..32f0698a8f9c --- /dev/null +++ b/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.ondeviceintelligence.callbacks; + +import android.app.ondeviceintelligence.IDownloadCallback; +import android.os.Handler; +import android.os.PersistableBundle; +import android.os.RemoteException; + +import com.android.internal.infra.AndroidFuture; + +import java.util.concurrent.TimeoutException; + +/** + * This class extends the {@link IDownloadCallback} and adds a timeout Runnable to the callback + * such that, in the case where the callback methods are not invoked, we do not have to wait for + * timeout based on {@link #onDownloadCompleted} which might take minutes or hours to complete in + * some cases. Instead, in such cases we rely on the remote service sending progress updates and if + * there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the + * download will not complete and enabling faster cleanup. + */ +public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable { + private final IDownloadCallback callback; + private final Handler handler; + private final AndroidFuture future; + private final long idleTimeoutMs; + + /** + * Constructor to create a ListenableDownloadCallback. + * + * @param callback callback to send download updates to caller. + * @param handler handler to schedule timeout runnable. + * @param future future to complete to signal the callback has reached a terminal state. + * @param idleTimeoutMs timeout within which download updates should be received. + */ + public ListenableDownloadCallback(IDownloadCallback callback, Handler handler, + AndroidFuture future, + long idleTimeoutMs) { + this.callback = callback; + this.handler = handler; + this.future = future; + this.idleTimeoutMs = idleTimeoutMs; + handler.postDelayed(this, + idleTimeoutMs); // init the timeout runnable in case no callback is ever invoked + } + + @Override + public void onDownloadStarted(long bytesToDownload) throws RemoteException { + callback.onDownloadStarted(bytesToDownload); + handler.removeCallbacks(this); + handler.postDelayed(this, idleTimeoutMs); + } + + @Override + public void onDownloadProgress(long bytesDownloaded) throws RemoteException { + callback.onDownloadProgress(bytesDownloaded); + handler.removeCallbacks(this); // remove previously queued timeout tasks. + handler.postDelayed(this, idleTimeoutMs); // queue fresh timeout task for next update. + } + + @Override + public void onDownloadFailed(int failureStatus, + String errorMessage, PersistableBundle errorParams) throws RemoteException { + callback.onDownloadFailed(failureStatus, errorMessage, errorParams); + handler.removeCallbacks(this); + future.completeExceptionally(new TimeoutException()); + } + + @Override + public void onDownloadCompleted( + android.os.PersistableBundle downloadParams) throws RemoteException { + callback.onDownloadCompleted(downloadParams); + handler.removeCallbacks(this); + future.complete(null); + } + + @Override + public void run() { + future.completeExceptionally( + new TimeoutException()); // complete the future as we haven't received updates + // for download progress. + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 908b47df35e1..472f228b689f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -37,7 +37,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_STAGED; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; -import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH; +import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL; import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN; import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile; @@ -505,6 +505,7 @@ final class InstallPackageHelper { // metadata file path for the new package. if (oldPkgSetting != null) { pkgSetting.setAppMetadataFilePath(null); + pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN); } // If the app metadata file path is not null then this is a system app with a preloaded app // metadata file on the system image. Do not reset the path and source if this is the @@ -523,7 +524,7 @@ final class InstallPackageHelper { } } else if (Flags.aslInApkAppMetadataSource()) { Map<String, PackageManager.Property> properties = pkg.getProperties(); - if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) { + if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) { // ASL file extraction is done in post-install pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath()); pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK); diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java index 7c5615771607..0afda4598bcb 100644 --- a/services/core/java/com/android/server/pm/PackageFreezer.java +++ b/services/core/java/com/android/server/pm/PackageFreezer.java @@ -18,6 +18,7 @@ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.Flags; import android.content.pm.PackageManager; import dalvik.system.CloseGuard; @@ -76,8 +77,13 @@ final class PackageFreezer implements AutoCloseable { ps = mPm.mSettings.getPackageLPr(mPackageName); } if (ps != null) { - mPm.killApplication(ps.getPackageName(), ps.getAppId(), userId, killReason, - exitInfoReason); + if (Flags.waitApplicationKilled()) { + mPm.killApplicationSync(ps.getPackageName(), ps.getAppId(), userId, killReason, + exitInfoReason); + } else { + mPm.killApplication(ps.getPackageName(), ps.getAppId(), userId, killReason, + exitInfoReason); + } } mCloseGuard.open("close"); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 121cf3f231b0..f8fceda0582c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -53,6 +53,7 @@ import android.annotation.StringRes; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.ApplicationExitInfo; import android.app.ApplicationPackageManager; @@ -3132,6 +3133,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } + void killApplicationSync(String pkgName, @AppIdInt int appId, + @UserIdInt int userId, String reason, int exitInfoReason) { + ActivityManagerInternal mAmi = LocalServices.getService(ActivityManagerInternal.class); + if (mAmi != null) { + if (Thread.holdsLock(mLock)) { + // holds PM's lock, go back killApplication to avoid it run into watchdog reset. + Slog.e(TAG, "Holds PM's locker, unable kill application synchronized"); + killApplication(pkgName, appId, userId, reason, exitInfoReason); + } else { + mAmi.killApplicationSync(pkgName, appId, userId, reason, exitInfoReason); + } + } + } + @Override public void notifyPackageAdded(String packageName, int uid) { mPackageObserverHelper.notifyAdded(packageName, uid); @@ -4003,7 +4018,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService final PackageMetrics.ComponentStateMetrics componentStateMetrics = new PackageMetrics.ComponentStateMetrics(setting, UserHandle.getUid(userId, packageSetting.getAppId()), - packageSetting.getEnabled(userId)); + packageSetting.getEnabled(userId), callingUid); if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId, callingPackage)) { continue; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index b369f03d002f..23ae98325cb9 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -19,7 +19,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; -import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH; +import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL; import static android.content.pm.SigningDetails.CertCapabilities.SHARED_USER_ID; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDWR; @@ -71,8 +71,12 @@ import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; +import android.content.res.ApkAssets; +import android.content.res.AssetManager; +import android.content.res.Resources; import android.os.Binder; import android.os.Build; +import android.os.CancellationSignal; import android.os.Debug; import android.os.Environment; import android.os.FileUtils; @@ -93,6 +97,7 @@ import android.system.Os; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Base64; +import android.util.DisplayMetrics; import android.util.Log; import android.util.LogPrinter; import android.util.Printer; @@ -147,11 +152,10 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.function.Predicate; import java.util.zip.GZIPInputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; /** * Class containing helper methods for the PackageManagerService. @@ -1668,11 +1672,11 @@ public class PackageManagerServiceUtils { return true; } Map<String, Property> properties = pkg.getProperties(); - if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) { + if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) { return false; } - Property fileInAPkPathProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL_PATH); - if (!fileInAPkPathProperty.isString()) { + Property fileInApkProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL); + if (!fileInApkProperty.isResourceId()) { return false; } if (isSystem && !appMetadataFile.getParentFile().exists()) { @@ -1684,28 +1688,46 @@ public class PackageManagerServiceUtils { return false; } } - String fileInApkPath = fileInAPkPathProperty.getString(); List<AndroidPackageSplit> splits = pkg.getSplits(); + AssetManager.Builder builder = new AssetManager.Builder(); for (int i = 0; i < splits.size(); i++) { - try (ZipFile zipFile = new ZipFile(splits.get(i).getPath())) { - ZipEntry zipEntry = zipFile.getEntry(fileInApkPath); - if (zipEntry != null - && (isSystem || zipEntry.getSize() <= getAppMetadataSizeLimit())) { - try (InputStream in = zipFile.getInputStream(zipEntry)) { - try (FileOutputStream out = new FileOutputStream(appMetadataFile)) { - FileUtils.copy(in, out); - Os.chmod(appMetadataFile.getAbsolutePath(), - APP_METADATA_FILE_ACCESS_MODE); - return true; + try { + builder.addApkAssets(ApkAssets.loadFromPath(splits.get(i).getPath())); + } catch (IOException e) { + Slog.e(TAG, "Failed to load resources from APK " + splits.get(i).getPath()); + } + } + AssetManager assetManager = builder.build(); + DisplayMetrics displayMetrics = new DisplayMetrics(); + displayMetrics.setToDefaults(); + Resources res = new Resources(assetManager, displayMetrics, null); + AtomicBoolean copyFailed = new AtomicBoolean(false); + try (InputStream in = res.openRawResource(fileInApkProperty.getResourceId())) { + try (FileOutputStream out = new FileOutputStream(appMetadataFile)) { + if (isSystem) { + FileUtils.copy(in, out); + } else { + long sizeLimit = getAppMetadataSizeLimit(); + CancellationSignal signal = new CancellationSignal(); + FileUtils.copy(in, out, signal, Runnable::run, (long progress) -> { + if (progress > sizeLimit) { + copyFailed.set(true); + signal.cancel(); } - } + }); } - } catch (Exception e) { - Slog.e(TAG, e.getMessage()); + Os.chmod(appMetadataFile.getAbsolutePath(), + APP_METADATA_FILE_ACCESS_MODE); + } + } catch (Exception e) { + Slog.e(TAG, e.getMessage()); + copyFailed.set(true); + } finally { + if (copyFailed.get()) { appMetadataFile.delete(); } } - return false; + return !copyFailed.get(); } public static void linkFilesToOldDirs(@NonNull Installer installer, diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 7a36f6dabe06..0a8b2b2c6219 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -328,6 +328,8 @@ class PackageManagerShellCommand extends ShellCommand { return runGetPrivappDenyPermissions(); case "get-oem-permissions": return runGetOemPermissions(); + case "get-signature-permission-allowlist": + return runGetSignaturePermissionAllowlist(); case "trim-caches": return runTrimCaches(); case "create-user": @@ -2920,6 +2922,54 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runGetSignaturePermissionAllowlist() { + final var partition = getNextArg(); + if (partition == null) { + getErrPrintWriter().println("Error: no partition specified."); + return 1; + } + final var permissionAllowlist = + SystemConfig.getInstance().getPermissionAllowlist(); + final ArrayMap<String, ArrayMap<String, Boolean>> allowlist; + switch (partition) { + case "system": + allowlist = permissionAllowlist.getSignatureAppAllowlist(); + break; + case "vendor": + allowlist = permissionAllowlist.getVendorSignatureAppAllowlist(); + break; + case "product": + allowlist = permissionAllowlist.getProductSignatureAppAllowlist(); + break; + case "system-ext": + allowlist = permissionAllowlist.getSystemExtSignatureAppAllowlist(); + break; + default: + getErrPrintWriter().println("Error: unknown partition: " + partition); + return 1; + } + final var ipw = new IndentingPrintWriter(getOutPrintWriter(), " "); + final var allowlistSize = allowlist.size(); + for (var allowlistIndex = 0; allowlistIndex < allowlistSize; allowlistIndex++) { + final var packageName = allowlist.keyAt(allowlistIndex); + final var permissions = allowlist.valueAt(allowlistIndex); + ipw.print("Package: "); + ipw.println(packageName); + ipw.increaseIndent(); + final var permissionsSize = permissions.size(); + for (var permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) { + final var permissionName = permissions.keyAt(permissionsIndex); + final var granted = permissions.valueAt(permissionsIndex); + if (granted) { + ipw.print("Permission: "); + ipw.println(permissionName); + } + } + ipw.decreaseIndent(); + } + return 0; + } + private int runTrimCaches() throws RemoteException { String size = getNextArg(); if (size == null) { @@ -4852,6 +4902,10 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" get-oem-permissions TARGET-PACKAGE"); pw.println(" Prints all OEM permissions for a package."); pw.println(""); + pw.println(" get-signature-permission-allowlist PARTITION"); + pw.println(" Prints the signature permission allowlist for a partition."); + pw.println(" PARTITION is one of system, vendor, product and system-ext"); + pw.println(""); pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]"); pw.println(" Trim cache files to reach the given free space."); pw.println(""); diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java index 20598f91a51d..2081f73e7336 100644 --- a/services/core/java/com/android/server/pm/PackageMetrics.java +++ b/services/core/java/com/android/server/pm/PackageMetrics.java @@ -358,6 +358,7 @@ final class PackageMetrics { public static class ComponentStateMetrics { public int mUid; + public int mCallingUid; public int mComponentOldState; public int mComponentNewState; public boolean mIsForWholeApp; @@ -365,13 +366,14 @@ final class PackageMetrics { @Nullable private String mClassName; ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid, - int componentOldState) { + int componentOldState, int callingUid) { mUid = uid; mComponentOldState = componentOldState; mComponentNewState = setting.getEnabledState(); mIsForWholeApp = !setting.isComponent(); mPackageName = setting.getPackageName(); mClassName = setting.getClassName(); + mCallingUid = callingUid; } public boolean isSameComponent(ActivityInfo activityInfo) { @@ -412,14 +414,15 @@ final class PackageMetrics { componentStateMetrics.mComponentOldState, componentStateMetrics.mComponentNewState, isLauncher, - componentStateMetrics.mIsForWholeApp); + componentStateMetrics.mIsForWholeApp, + componentStateMetrics.mCallingUid); } } private static void reportComponentStateChanged(int uid, int componentOldState, - int componentNewState, boolean isLauncher, boolean isForWholeApp) { + int componentNewState, boolean isLauncher, boolean isForWholeApp, int callingUid) { FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED, - uid, componentOldState, componentNewState, isLauncher, isForWholeApp); + uid, componentOldState, componentNewState, isLauncher, isForWholeApp, callingUid); } private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer, diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 41d6288d4411..8d6d774a9959 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2142,7 +2142,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString( originalComponentName); if (unflattenOriginalComponentName == null) { - Slog.d(TAG, "Incorrect component name from the attributes"); + Slog.wtf(TAG, "Incorrect component name: " + originalComponentName + + " from the attributes"); continue; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 4ff345fbedf9..ebdca5bcec6f 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1924,16 +1924,20 @@ public class UserManagerService extends IUserManager.Stub { private void showConfirmCredentialToDisableQuietMode( @UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) { if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) { - // TODO (b/308121702) It may be brittle to rely on user states to check profile state - int state; - synchronized (mUserStates) { - state = mUserStates.get(userId, UserState.STATE_NONE); - } - if (state != UserState.STATE_NONE) { - Slog.i(LOG_TAG, - "showConfirmCredentialToDisableQuietMode() called too early, user " + userId - + " is still alive."); - return; + if (!android.multiuser.Flags.restrictQuietModeCredentialBugFixToManagedProfiles() + || getUserInfo(userId).isManagedProfile()) { + // TODO (b/308121702) It may be brittle to rely on user states to check managed + // profile state + int state; + synchronized (mUserStates) { + state = mUserStates.get(userId, UserState.STATE_NONE); + } + if (state != UserState.STATE_NONE) { + Slog.i(LOG_TAG, + "showConfirmCredentialToDisableQuietMode() called too early, managed " + + "user " + userId + " is still alive."); + return; + } } } // otherwise, we show a profile challenge to trigger decryption of the user diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java index 5aad570ffd41..884c26ca3c00 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java @@ -19,12 +19,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.os.BatteryConsumer; -import com.android.internal.os.PowerStats; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -206,25 +203,9 @@ public class AggregatedPowerStatsConfig { return mPowerComponents; } - private static final PowerStatsProcessor NO_OP_PROCESSOR = - new PowerStatsProcessor() { - @Override - void finish(PowerComponentAggregatedPowerStats stats) { - } - - @Override - String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - return Arrays.toString(stats); - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - return descriptor.getStateLabel(key) + " " + Arrays.toString(stats); - } - - @Override - String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - return Arrays.toString(stats); - } - }; + private static final PowerStatsProcessor NO_OP_PROCESSOR = new PowerStatsProcessor() { + @Override + void finish(PowerComponentAggregatedPowerStats stats) { + } + }; } diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 49c4000d7308..9a4155122402 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -463,11 +463,17 @@ public class BatteryStatsImpl extends BatteryStats { public static class BatteryStatsConfig { static final int RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG = 1 << 0; static final int RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG = 1 << 1; - static final long DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD = - TimeUnit.HOURS.toMillis(1); private final int mFlags; - private SparseLongArray mPowerStatsThrottlePeriods; + private final Long mDefaultPowerStatsThrottlePeriod; + private final Map<String, Long> mPowerStatsThrottlePeriods; + + @VisibleForTesting + public BatteryStatsConfig() { + mFlags = 0; + mDefaultPowerStatsThrottlePeriod = 0L; + mPowerStatsThrottlePeriods = Map.of(); + } private BatteryStatsConfig(Builder builder) { int flags = 0; @@ -478,6 +484,7 @@ public class BatteryStatsImpl extends BatteryStats { flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG; } mFlags = flags; + mDefaultPowerStatsThrottlePeriod = builder.mDefaultPowerStatsThrottlePeriod; mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods; } @@ -485,7 +492,7 @@ public class BatteryStatsImpl extends BatteryStats { * Returns whether a BatteryStats reset should occur on unplug when the battery level is * high. */ - boolean shouldResetOnUnplugHighBatteryLevel() { + public boolean shouldResetOnUnplugHighBatteryLevel() { return (mFlags & RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG) == RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG; } @@ -494,14 +501,18 @@ public class BatteryStatsImpl extends BatteryStats { * Returns whether a BatteryStats reset should occur on unplug if the battery charge a * significant amount since it has been plugged in. */ - boolean shouldResetOnUnplugAfterSignificantCharge() { + public boolean shouldResetOnUnplugAfterSignificantCharge() { return (mFlags & RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG) == RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG; } - long getPowerStatsThrottlePeriod(@BatteryConsumer.PowerComponent int powerComponent) { - return mPowerStatsThrottlePeriods.get(powerComponent, - DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD); + /** + * Returns the minimum amount of time (in millis) to wait between passes + * of power stats collection for the specified power component. + */ + public long getPowerStatsThrottlePeriod(String powerComponentName) { + return mPowerStatsThrottlePeriods.getOrDefault(powerComponentName, + mDefaultPowerStatsThrottlePeriod); } /** @@ -510,18 +521,19 @@ public class BatteryStatsImpl extends BatteryStats { public static class Builder { private boolean mResetOnUnplugHighBatteryLevel; private boolean mResetOnUnplugAfterSignificantCharge; - private SparseLongArray mPowerStatsThrottlePeriods; + public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD = + TimeUnit.HOURS.toMillis(1); + public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU = + TimeUnit.MINUTES.toMillis(1); + private long mDefaultPowerStatsThrottlePeriod = DEFAULT_POWER_STATS_THROTTLE_PERIOD; + private final Map<String, Long> mPowerStatsThrottlePeriods = new HashMap<>(); public Builder() { mResetOnUnplugHighBatteryLevel = true; mResetOnUnplugAfterSignificantCharge = true; - mPowerStatsThrottlePeriods = new SparseLongArray(); - setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU, - TimeUnit.MINUTES.toMillis(1)); - setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, - TimeUnit.HOURS.toMillis(1)); - setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_WIFI, - TimeUnit.HOURS.toMillis(1)); + setPowerStatsThrottlePeriodMillis(BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_CPU), + DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU); } /** @@ -553,9 +565,18 @@ public class BatteryStatsImpl extends BatteryStats { * Sets the minimum amount of time (in millis) to wait between passes * of power stats collection for the specified power component. */ - public Builder setPowerStatsThrottlePeriodMillis( - @BatteryConsumer.PowerComponent int powerComponent, long periodMs) { - mPowerStatsThrottlePeriods.put(powerComponent, periodMs); + public Builder setPowerStatsThrottlePeriodMillis(String powerComponentName, + long periodMs) { + mPowerStatsThrottlePeriods.put(powerComponentName, periodMs); + return this; + } + + /** + * Sets the minimum amount of time (in millis) to wait between passes + * of power stats collection for any components not configured explicitly. + */ + public Builder setDefaultPowerStatsThrottlePeriodMillis(long periodMs) { + mDefaultPowerStatsThrottlePeriod = periodMs; return this; } } @@ -1586,8 +1607,7 @@ public class BatteryStatsImpl extends BatteryStats { protected final Constants mConstants; @VisibleForTesting - @GuardedBy("this") - protected BatteryStatsConfig mBatteryStatsConfig; + protected final BatteryStatsConfig mBatteryStatsConfig; @GuardedBy("this") private AlarmManager mAlarmManager = null; @@ -1933,6 +1953,11 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return mBatteryStatsConfig.getPowerStatsThrottlePeriod(powerComponentName); + } + + @Override public PowerStatsUidResolver getUidResolver() { return mPowerStatsUidResolver; } @@ -11167,19 +11192,14 @@ public class BatteryStatsImpl extends BatteryStats { mConstants.MAX_HISTORY_FILES, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock, traceDelegate, eventLogger); - mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector, - mBatteryStatsConfig.getPowerStatsThrottlePeriod( - BatteryConsumer.POWER_COMPONENT_CPU)); + mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector); mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector( - mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod( - BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)); + mPowerStatsCollectorInjector); mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats); - mWifiPowerStatsCollector = new WifiPowerStatsCollector( - mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod( - BatteryConsumer.POWER_COMPONENT_WIFI)); + mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector); mWifiPowerStatsCollector.addConsumer(this::recordPowerStats); mStartCount++; diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java index f53a1b0682c0..b5ef67b44e75 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java @@ -59,6 +59,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { KernelCpuStatsReader getKernelCpuStatsReader(); ConsumedEnergyRetriever getConsumedEnergyRetriever(); IntSupplier getVoltageSupplier(); + long getPowerStatsCollectionThrottlePeriod(String powerComponentName); default int getDefaultCpuPowerBrackets() { return DEFAULT_CPU_POWER_BRACKETS; @@ -94,9 +95,11 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { private int mLastVoltageMv; private long[] mLastConsumedEnergyUws; - public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) { - super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(), - injector.getClock()); + CpuPowerStatsCollector(Injector injector) { + super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_CPU)), + injector.getUidResolver(), injector.getClock()); mInjector = injector; } diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java index 1bcb2c4bc5fa..2a02bd0f9e6a 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java @@ -44,7 +44,7 @@ public class CpuPowerStatsLayout extends PowerStatsLayout { * Declare that the stats array has a section capturing CPU time per scaling step */ public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) { - mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount); + mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount, "steps"); mDeviceCpuTimeByScalingStepCount = scalingStepCount; } @@ -72,7 +72,7 @@ public class CpuPowerStatsLayout extends PowerStatsLayout { * Declare that the stats array has a section capturing CPU time in each cluster */ public void addDeviceSectionCpuTimeByCluster(int clusterCount) { - mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount); + mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount, "clusters"); mDeviceCpuTimeByClusterCount = clusterCount; } @@ -102,7 +102,7 @@ public class CpuPowerStatsLayout extends PowerStatsLayout { public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) { mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap; updatePowerBracketCount(); - mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount); + mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount, "time"); } private void updatePowerBracketCount() { diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java index c34b8a8dc992..57b7259f9b56 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java @@ -16,7 +16,6 @@ package com.android.server.power.stats; -import android.os.BatteryStats; import android.util.ArraySet; import android.util.Log; @@ -487,64 +486,4 @@ public class CpuPowerStatsProcessor extends PowerStatsProcessor { stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); } } - - @Override - public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - StringBuilder sb = new StringBuilder(); - int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount(); - sb.append("steps: ["); - for (int step = 0; step < cpuScalingStepCount; step++) { - if (step != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getTimeByScalingStep(stats, step)); - } - int clusterCount = mStatsLayout.getCpuClusterCount(); - sb.append("] clusters: ["); - for (int cluster = 0; cluster < clusterCount; cluster++) { - if (cluster != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getTimeByCluster(stats, cluster)); - } - sb.append("] uptime: ").append(mStatsLayout.getUsageDuration(stats)); - int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); - if (energyConsumerCount > 0) { - sb.append(" energy: ["); - for (int i = 0; i < energyConsumerCount; i++) { - if (i != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getConsumedEnergy(stats, i)); - } - sb.append("]"); - } - sb.append(" power: ").append( - BatteryStats.formatCharge(mStatsLayout.getDevicePowerEstimate(stats))); - return sb.toString(); - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - // Unsupported for this power component - return null; - } - - @Override - public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - StringBuilder sb = new StringBuilder(); - sb.append("["); - int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); - for (int bracket = 0; bracket < powerBracketCount; bracket++) { - if (bracket != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getUidTimeByPowerBracket(stats, bracket)); - } - sb.append("] power: ").append( - BatteryStats.formatCharge(mStatsLayout.getUidPowerEstimate(stats))); - return sb.toString(); - } } diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java index 7bc681752802..a96e01bdeadf 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java @@ -73,6 +73,7 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { Handler getHandler(); Clock getClock(); PowerStatsUidResolver getUidResolver(); + long getPowerStatsCollectionThrottlePeriod(String powerComponentName); PackageManager getPackageManager(); ConsumedEnergyRetriever getConsumedEnergyRetriever(); IntSupplier getVoltageSupplier(); @@ -104,8 +105,11 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { private long mLastCallDuration; private long mLastScanDuration; - public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) { - super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(), + MobileRadioPowerStatsCollector(Injector injector) { + super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)), + injector.getUidResolver(), injector.getClock()); mInjector = injector; } diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java index 81d7c2fa2880..07d78f8ce4d0 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java @@ -64,29 +64,30 @@ class MobileRadioPowerStatsLayout extends PowerStatsLayout { } void addDeviceMobileActivity() { - mDeviceSleepTimePosition = addDeviceSection(1); - mDeviceIdleTimePosition = addDeviceSection(1); - mDeviceScanTimePosition = addDeviceSection(1); - mDeviceCallTimePosition = addDeviceSection(1); + mDeviceSleepTimePosition = addDeviceSection(1, "sleep"); + mDeviceIdleTimePosition = addDeviceSection(1, "idle"); + mDeviceScanTimePosition = addDeviceSection(1, "scan"); + mDeviceCallTimePosition = addDeviceSection(1, "call", FLAG_OPTIONAL); } void addStateStats() { - mStateRxTimePosition = addStateSection(1); + mStateRxTimePosition = addStateSection(1, "rx"); mStateTxTimesCount = ModemActivityInfo.getNumTxPowerLevels(); - mStateTxTimesPosition = addStateSection(mStateTxTimesCount); + mStateTxTimesPosition = addStateSection(mStateTxTimesCount, "tx"); } void addUidNetworkStats() { - mUidRxBytesPosition = addUidSection(1); - mUidTxBytesPosition = addUidSection(1); - mUidRxPacketsPosition = addUidSection(1); - mUidTxPacketsPosition = addUidSection(1); + mUidRxPacketsPosition = addUidSection(1, "rx-pkts"); + mUidRxBytesPosition = addUidSection(1, "rx-B"); + mUidTxPacketsPosition = addUidSection(1, "tx-pkts"); + mUidTxBytesPosition = addUidSection(1, "tx-B"); } @Override public void addDeviceSectionPowerEstimate() { super.addDeviceSectionPowerEstimate(); - mDeviceCallPowerPosition = addDeviceSection(1); + // Printed as part of the PhoneCallPowerStatsProcessor + mDeviceCallPowerPosition = addDeviceSection(1, "call-power", FLAG_HIDDEN); } public void setDeviceSleepTime(long[] stats, long durationMillis) { diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java index c97c64bafcba..eebed2f21946 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java @@ -398,37 +398,4 @@ public class MobileRadioPowerStatsProcessor extends PowerStatsProcessor { } } } - - @Override - String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - return "idle: " + mStatsLayout.getDeviceIdleTime(stats) - + " sleep: " + mStatsLayout.getDeviceSleepTime(stats) - + " scan: " + mStatsLayout.getDeviceScanTime(stats) - + " power: " + mStatsLayout.getDevicePowerEstimate(stats); - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - StringBuilder sb = new StringBuilder(); - sb.append(descriptor.getStateLabel(key)); - sb.append(" rx: ").append(mStatsLayout.getStateRxTime(stats)); - sb.append(" tx: "); - for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) { - if (txLevel != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getStateTxTime(stats, txLevel)); - } - return sb.toString(); - } - - @Override - String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - return "rx: " + mStatsLayout.getUidRxPackets(stats) - + " tx: " + mStatsLayout.getUidTxPackets(stats) - + " power: " + mStatsLayout.getUidPowerEstimate(stats); - } } diff --git a/services/core/java/com/android/server/power/stats/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java index 6c4a2b6e6359..a8222811c341 100644 --- a/services/core/java/com/android/server/power/stats/MultiStateStats.java +++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java @@ -28,10 +28,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.io.PrintWriter; import java.util.Arrays; import java.util.function.Consumer; -import java.util.function.Function; /** * Maintains multidimensional multi-state stats. States could be something like on-battery (0,1), @@ -287,6 +285,14 @@ public class MultiStateStats { mCounter = new LongArrayMultiStateCounter(factory.mSerialStateCount, dimensionCount); } + public int getDimensionCount() { + return mFactory.mDimensionCount; + } + + public States[] getStates() { + return mFactory.mStates; + } + /** * Copies time-in-state and timestamps from the supplied prototype. Does not * copy accumulated counts. @@ -343,11 +349,6 @@ public class MultiStateStats { mTracking = false; } - @Override - public String toString() { - return mCounter.toString(); - } - /** * Stores contents in an XML doc. */ @@ -451,10 +452,9 @@ public class MultiStateStats { return true; } - /** - * Prints the accumulated stats, one line of every combination of states that has data. - */ - public void dump(PrintWriter pw, Function<long[], String> statsFormatter) { + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); long[] values = new long[mCounter.getArrayLength()]; States.forEachTrackedStateCombination(mFactory.mStates, states -> { mCounter.getCounts(values, mFactory.getSerialState(states)); @@ -469,18 +469,24 @@ public class MultiStateStats { return; } - StringBuilder sb = new StringBuilder(); + if (!sb.isEmpty()) { + sb.append("\n"); + } + + sb.append("("); + boolean first = true; for (int i = 0; i < states.length; i++) { if (mFactory.mStates[i].mTracked) { - if (sb.length() != 0) { + if (!first) { sb.append(" "); } + first = false; sb.append(mFactory.mStates[i].mLabels[states[i]]); } } - sb.append(" "); - sb.append(statsFormatter.apply(values)); - pw.println(sb); + sb.append(") "); + sb.append(Arrays.toString(values)); }); + return sb.toString(); } } diff --git a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java index 62b653f61373..5c545fd073b2 100644 --- a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java @@ -76,21 +76,4 @@ public class PhoneCallPowerStatsProcessor extends PowerStatsProcessor { stats.setDeviceStats(states, mTmpDeviceStats); }); } - - @Override - String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - return "power: " + mStatsLayout.getDevicePowerEstimate(stats); - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - // Unsupported for this power component - return null; - } - - @Override - String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - // Unsupported for this power component - return null; - } } diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 6d58307dbefa..052873312d5c 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -436,36 +436,76 @@ class PowerComponentAggregatedPowerStats { void dumpDevice(IndentingPrintWriter ipw) { if (mDeviceStats != null) { - ipw.println(mPowerStatsDescriptor.name); - ipw.increaseIndent(); - mDeviceStats.dump(ipw, stats -> - mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats)); - ipw.decreaseIndent(); + dumpMultiStateStats(ipw, mDeviceStats, mPowerStatsDescriptor.name, null, + mPowerStatsDescriptor.getDeviceStatsFormatter()); } if (mStateStats.size() != 0) { ipw.increaseIndent(); - ipw.println(mPowerStatsDescriptor.name + " states"); - ipw.increaseIndent(); + String header = mPowerStatsDescriptor.name + " states"; + PowerStats.PowerStatsFormatter formatter = + mPowerStatsDescriptor.getStateStatsFormatter(); for (int i = 0; i < mStateStats.size(); i++) { int key = mStateStats.keyAt(i); + String stateLabel = mPowerStatsDescriptor.getStateLabel(key); MultiStateStats stateStats = mStateStats.valueAt(i); - stateStats.dump(ipw, stats -> - mConfig.getProcessor().stateStatsToString(mPowerStatsDescriptor, key, - stats)); + dumpMultiStateStats(ipw, stateStats, header, stateLabel, formatter); } ipw.decreaseIndent(); - ipw.decreaseIndent(); } } void dumpUid(IndentingPrintWriter ipw, int uid) { UidStats uidStats = mUidStats.get(uid); if (uidStats != null && uidStats.stats != null) { - ipw.println(mPowerStatsDescriptor.name); - ipw.increaseIndent(); - uidStats.stats.dump(ipw, stats -> - mConfig.getProcessor().uidStatsToString(mPowerStatsDescriptor, stats)); + dumpMultiStateStats(ipw, uidStats.stats, mPowerStatsDescriptor.name, null, + mPowerStatsDescriptor.getUidStatsFormatter()); + } + } + + private void dumpMultiStateStats(IndentingPrintWriter ipw, MultiStateStats stats, + String header, String additionalLabel, + PowerStats.PowerStatsFormatter statsFormatter) { + boolean[] firstLine = new boolean[]{true}; + long[] values = new long[stats.getDimensionCount()]; + MultiStateStats.States[] stateInfo = stats.getStates(); + MultiStateStats.States.forEachTrackedStateCombination(stateInfo, states -> { + stats.getStats(values, states); + boolean nonZero = false; + for (long value : values) { + if (value != 0) { + nonZero = true; + break; + } + } + if (!nonZero) { + return; + } + + if (firstLine[0]) { + ipw.println(header); + ipw.increaseIndent(); + } + firstLine[0] = false; + StringBuilder sb = new StringBuilder(); + sb.append("("); + boolean first = true; + for (int i = 0; i < states.length; i++) { + if (stateInfo[i].isTracked()) { + if (!first) { + sb.append(" "); + } + first = false; + sb.append(stateInfo[i].getLabels()[states[i]]); + } + } + if (additionalLabel != null) { + sb.append(" ").append(additionalLabel); + } + sb.append(") ").append(statsFormatter.format(values)); + ipw.println(sb); + }); + if (!firstLine[0]) { ipw.decreaseIndent(); } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java index aa96409e85e9..58efd94bb82c 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java @@ -33,13 +33,20 @@ public class PowerStatsLayout { private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; private static final String EXTRA_UID_POWER_POSITION = "up"; - protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; protected static final int UNSUPPORTED = -1; + protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; + protected static final int FLAG_OPTIONAL = 1; + protected static final int FLAG_HIDDEN = 2; + protected static final int FLAG_FORMAT_AS_POWER = 4; private int mDeviceStatsArrayLength; private int mStateStatsArrayLength; private int mUidStatsArrayLength; + private StringBuilder mDeviceFormat = new StringBuilder(); + private StringBuilder mStateFormat = new StringBuilder(); + private StringBuilder mUidFormat = new StringBuilder(); + protected int mDeviceDurationPosition = UNSUPPORTED; private int mDeviceEnergyConsumerPosition; private int mDeviceEnergyConsumerCount; @@ -65,29 +72,71 @@ public class PowerStatsLayout { return mUidStatsArrayLength; } - protected int addDeviceSection(int length) { + /** + * @param label should not contain either spaces or colons + */ + private void appendFormat(StringBuilder sb, int position, int length, String label, + int flags) { + if ((flags & FLAG_HIDDEN) != 0) { + return; + } + + if (!sb.isEmpty()) { + sb.append(' '); + } + + sb.append(label).append(':'); + sb.append(position); + if (length != 1) { + sb.append('[').append(length).append(']'); + } + if ((flags & FLAG_FORMAT_AS_POWER) != 0) { + sb.append('p'); + } + if ((flags & FLAG_OPTIONAL) != 0) { + sb.append('?'); + } + } + + protected int addDeviceSection(int length, String label, int flags) { int position = mDeviceStatsArrayLength; mDeviceStatsArrayLength += length; + appendFormat(mDeviceFormat, position, length, label, flags); return position; } - protected int addStateSection(int length) { + protected int addDeviceSection(int length, String label) { + return addDeviceSection(length, label, 0); + } + + protected int addStateSection(int length, String label, int flags) { int position = mStateStatsArrayLength; mStateStatsArrayLength += length; + appendFormat(mStateFormat, position, length, label, flags); return position; } - protected int addUidSection(int length) { + protected int addStateSection(int length, String label) { + return addStateSection(length, label, 0); + } + + + protected int addUidSection(int length, String label, int flags) { int position = mUidStatsArrayLength; mUidStatsArrayLength += length; + appendFormat(mUidFormat, position, length, label, flags); return position; } + protected int addUidSection(int length, String label) { + return addUidSection(length, label, 0); + } + /** * Declare that the stats array has a section capturing usage duration */ public void addDeviceSectionUsageDuration() { - mDeviceDurationPosition = addDeviceSection(1); + mDeviceDurationPosition = addDeviceSection(1, "usage", FLAG_OPTIONAL); } /** @@ -109,7 +158,7 @@ public class PowerStatsLayout { * PowerStatsService. */ public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { - mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount); + mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy"); mDeviceEnergyConsumerCount = energyConsumerCount; } @@ -137,7 +186,8 @@ public class PowerStatsLayout { * Declare that the stats array has a section capturing a power estimate */ public void addDeviceSectionPowerEstimate() { - mDevicePowerEstimatePosition = addDeviceSection(1); + mDevicePowerEstimatePosition = addDeviceSection(1, "power", + FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL); } /** @@ -159,7 +209,7 @@ public class PowerStatsLayout { * Declare that the UID stats array has a section capturing a power estimate */ public void addUidSectionPowerEstimate() { - mUidPowerEstimatePosition = addUidSection(1); + mUidPowerEstimatePosition = addUidSection(1, "power", FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL); } /** @@ -195,6 +245,9 @@ public class PowerStatsLayout { mDeviceEnergyConsumerCount); extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); + extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString()); + extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString()); + extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, mUidFormat.toString()); } /** diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java index 0d5c5422b45c..2fd0b9a9b001 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java @@ -19,8 +19,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Log; -import com.android.internal.os.PowerStats; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -47,12 +45,6 @@ abstract class PowerStatsProcessor { abstract void finish(PowerComponentAggregatedPowerStats stats); - abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats); - - abstract String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats); - - abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats); - protected static class PowerEstimationPlan { private final AggregatedPowerStatsConfig.PowerComponent mConfig; public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>(); diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java index 632105352ad2..bd04199fc227 100644 --- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java @@ -56,6 +56,7 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { Handler getHandler(); Clock getClock(); PowerStatsUidResolver getUidResolver(); + long getPowerStatsCollectionThrottlePeriod(String powerComponentName); PackageManager getPackageManager(); ConsumedEnergyRetriever getConsumedEnergyRetriever(); IntSupplier getVoltageSupplier(); @@ -92,9 +93,11 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>(); private long mLastWifiActiveDuration; - public WifiPowerStatsCollector(Injector injector, long throttlePeriodMs) { - super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(), - injector.getClock()); + WifiPowerStatsCollector(Injector injector) { + super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_WIFI)), + injector.getUidResolver(), injector.getClock()); mInjector = injector; } diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java index 0fa6ec65c4bc..e2e822690c55 100644 --- a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java @@ -65,28 +65,28 @@ public class WifiPowerStatsLayout extends PowerStatsLayout { mPowerReportingSupported = powerReportingSupported; if (mPowerReportingSupported) { mDeviceActiveTimePosition = UNSPECIFIED; - mDeviceRxTimePosition = addDeviceSection(1); - mDeviceTxTimePosition = addDeviceSection(1); - mDeviceIdleTimePosition = addDeviceSection(1); - mDeviceScanTimePosition = addDeviceSection(1); + mDeviceRxTimePosition = addDeviceSection(1, "rx"); + mDeviceTxTimePosition = addDeviceSection(1, "tx"); + mDeviceIdleTimePosition = addDeviceSection(1, "idle"); + mDeviceScanTimePosition = addDeviceSection(1, "scan"); } else { - mDeviceActiveTimePosition = addDeviceSection(1); + mDeviceActiveTimePosition = addDeviceSection(1, "rx-tx"); mDeviceRxTimePosition = UNSPECIFIED; mDeviceTxTimePosition = UNSPECIFIED; mDeviceIdleTimePosition = UNSPECIFIED; mDeviceScanTimePosition = UNSPECIFIED; } - mDeviceBasicScanTimePosition = addDeviceSection(1); - mDeviceBatchedScanTimePosition = addDeviceSection(1); + mDeviceBasicScanTimePosition = addDeviceSection(1, "basic-scan", FLAG_OPTIONAL); + mDeviceBatchedScanTimePosition = addDeviceSection(1, "batched-scan", FLAG_OPTIONAL); } void addUidNetworkStats() { - mUidRxBytesPosition = addUidSection(1); - mUidTxBytesPosition = addUidSection(1); - mUidRxPacketsPosition = addUidSection(1); - mUidTxPacketsPosition = addUidSection(1); - mUidScanTimePosition = addUidSection(1); - mUidBatchScanTimePosition = addUidSection(1); + mUidRxPacketsPosition = addUidSection(1, "rx-pkts"); + mUidRxBytesPosition = addUidSection(1, "rx-B"); + mUidTxPacketsPosition = addUidSection(1, "tx-pkts"); + mUidTxBytesPosition = addUidSection(1, "tx-B"); + mUidScanTimePosition = addUidSection(1, "scan", FLAG_OPTIONAL); + mUidBatchScanTimePosition = addUidSection(1, "batched-scan", FLAG_OPTIONAL); } public boolean isPowerReportingSupported() { diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java index 5e9cc4092029..a4a2e183f86b 100644 --- a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java @@ -389,37 +389,4 @@ public class WifiPowerStatsProcessor extends PowerStatsProcessor { } } } - - @Override - String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - if (mHasWifiPowerController) { - return "rx: " + mStatsLayout.getDeviceRxTime(stats) - + " tx: " + mStatsLayout.getDeviceTxTime(stats) - + " scan: " + mStatsLayout.getDeviceScanTime(stats) - + " idle: " + mStatsLayout.getDeviceIdleTime(stats) - + " power: " + mStatsLayout.getDevicePowerEstimate(stats); - } else { - return "active: " + mStatsLayout.getDeviceActiveTime(stats) - + " scan: " + mStatsLayout.getDeviceBasicScanTime(stats) - + " batched-scan: " + mStatsLayout.getDeviceBatchedScanTime(stats) - + " power: " + mStatsLayout.getDevicePowerEstimate(stats); - } - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - // Unsupported for this power component - return null; - } - - @Override - String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - return "rx: " + mStatsLayout.getUidRxPackets(stats) - + " tx: " + mStatsLayout.getUidTxPackets(stats) - + " scan: " + mStatsLayout.getUidScanTime(stats) - + " batched-scan: " + mStatsLayout.getUidBatchedScanTime(stats) - + " power: " + mStatsLayout.getUidPowerEstimate(stats); - } } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index e3e478d5ce9f..c1b825b3f8d1 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -49,10 +49,13 @@ import static android.util.MathUtils.constrain; import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; -import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; -import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; -import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; -import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__QS_SHORTCUT_TYPE__QUICK_SETTINGS; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_BUTTON; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_FLOATING_MENU; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_GESTURE; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO; @@ -61,7 +64,6 @@ import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STA import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller; -import static com.android.server.stats.Flags.statsPullNetworkStatsManagerInitOrderFix; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines; @@ -431,12 +433,6 @@ public class StatsPullAtomService extends SystemService { public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = addMobileBytesTransferByProcStatePuller(); - /** - * Whether or not to enable the mNetworkStatsManager initialization order fix - */ - private static final boolean ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX = - statsPullNetworkStatsManagerInitOrderFix(); - // Puller locks private final Object mDataBytesTransferLock = new Object(); private final Object mBluetoothBytesTransferLock = new Object(); @@ -799,7 +795,7 @@ public class StatsPullAtomService extends SystemService { case FrameworkStatsLog.KEYSTORE2_CRASH_STATS: return pullKeystoreAtoms(atomTag, data); case FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS: - return pullAccessibilityShortcutStatsLocked(atomTag, data); + return pullAccessibilityShortcutStatsLocked(data); case FrameworkStatsLog.ACCESSIBILITY_FLOATING_MENU_STATS: return pullAccessibilityFloatingMenuStatsLocked(atomTag, data); case FrameworkStatsLog.MEDIA_CAPABILITIES: @@ -840,9 +836,7 @@ public class StatsPullAtomService extends SystemService { registerEventListeners(); }); } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { - if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) { - initNetworkStatsManager(); - } + initNetworkStatsManager(); BackgroundThread.getHandler().post(() -> { // Network stats related pullers can only be initialized after service is ready. initAndRegisterNetworkStatsPullers(); @@ -863,9 +857,6 @@ public class StatsPullAtomService extends SystemService { mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager); mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class); - if (!ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) { - initNetworkStatsManager(); - } // Initialize DiskIO mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(); @@ -1047,10 +1038,8 @@ public class StatsPullAtomService extends SystemService { */ @NonNull private NetworkStatsManager getNetworkStatsManager() { - if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) { - if (mNetworkStatsManager == null) { - throw new IllegalStateException("NetworkStatsManager is not ready"); - } + if (mNetworkStatsManager == null) { + throw new IllegalStateException("NetworkStatsManager is not ready"); } return mNetworkStatsManager; } @@ -4774,7 +4763,10 @@ public class StatsPullAtomService extends SystemService { } } - int pullAccessibilityShortcutStatsLocked(int atomTag, List<StatsEvent> pulledData) { + /** + * Pulls ACCESSIBILITY_SHORTCUT_STATS atom + */ + int pullAccessibilityShortcutStatsLocked(List<StatsEvent> pulledData) { UserManager userManager = mContext.getSystemService(UserManager.class); if (userManager == null) { return StatsManager.PULL_SKIP; @@ -4782,10 +4774,6 @@ public class StatsPullAtomService extends SystemService { final long token = Binder.clearCallingIdentity(); try { final ContentResolver resolver = mContext.getContentResolver(); - final int hardware_shortcut_type = - FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY; - final int triple_tap_shortcut = - FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP; for (UserInfo userInfo : userManager.getUsers()) { final int userId = userInfo.getUserHandle().getIdentifier(); @@ -4803,15 +4791,22 @@ public class StatsPullAtomService extends SystemService { final int hardware_shortcut_service_num = countAccessibilityServices( hardware_shortcut_list); + final String qs_shortcut_list = Settings.Secure.getStringForUser(resolver, + Settings.Secure.ACCESSIBILITY_QS_TARGETS, userId); + final boolean qs_shortcut_enabled = !TextUtils.isEmpty(qs_shortcut_list); + // only allow magnification to use it for now final int triple_tap_service_num = Settings.Secure.getIntForUser(resolver, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId); - - pulledData.add( - FrameworkStatsLog.buildStatsEvent(atomTag, - software_shortcut_type, software_shortcut_service_num, - hardware_shortcut_type, hardware_shortcut_service_num, - triple_tap_shortcut, triple_tap_service_num)); + pulledData.add(FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS, + software_shortcut_type, software_shortcut_service_num, + ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY, + hardware_shortcut_service_num, + ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP, + triple_tap_service_num, + ACCESSIBILITY_SHORTCUT_STATS__QS_SHORTCUT_TYPE__QUICK_SETTINGS, + qs_shortcut_enabled)); } } } catch (RuntimeException e) { @@ -5150,16 +5145,19 @@ public class StatsPullAtomService extends SystemService { Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId); final String hardware_shortcut_list = Settings.Secure.getStringForUser(resolver, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId); + final String qs_shortcut_list = Settings.Secure.getStringForUser(resolver, + Settings.Secure.ACCESSIBILITY_QS_TARGETS, userId); final boolean hardware_shortcut_dialog_shown = Settings.Secure.getIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId) == 1; final boolean software_shortcut_enabled = !TextUtils.isEmpty(software_shortcut_list); final boolean hardware_shortcut_enabled = hardware_shortcut_dialog_shown && !TextUtils.isEmpty(hardware_shortcut_list); + final boolean qs_shortcut_enabled = !TextUtils.isEmpty(qs_shortcut_list); final boolean triple_tap_shortcut_enabled = Settings.Secure.getIntForUser(resolver, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId) == 1; return software_shortcut_enabled || hardware_shortcut_enabled - || triple_tap_shortcut_enabled; + || triple_tap_shortcut_enabled || qs_shortcut_enabled; } private boolean isAccessibilityFloatingMenuUser(Context context, @UserIdInt int userId) { @@ -5176,13 +5174,13 @@ public class StatsPullAtomService extends SystemService { private int convertToAccessibilityShortcutType(int shortcutType) { switch (shortcutType) { case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR: - return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; + return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_BUTTON; case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU: - return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; + return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_FLOATING_MENU; case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE: - return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; + return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_GESTURE; default: - return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; + return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE; } } diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig index c479c6d11164..6faa2737ac30 100644 --- a/services/core/java/com/android/server/stats/stats_flags.aconfig +++ b/services/core/java/com/android/server/stats/stats_flags.aconfig @@ -8,11 +8,3 @@ flag { bug: "309512867" is_fixed_read_only: true } - -flag { - name: "stats_pull_network_stats_manager_init_order_fix" - namespace: "statsd" - description: "Fix the mNetworkStatsManager initialization order" - bug: "331989853" - is_fixed_read_only: true -} diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index f6afc52fd8d8..3393d3e049e1 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -150,7 +150,11 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { @Override public void onWindowInfosChanged(InputWindowHandle[] windowHandles, DisplayInfo[] displayInfos) { - mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos)); + if (com.android.server.accessibility.Flags.removeOnWindowInfosChangedHandler()) { + onWindowInfosChangedInternal(windowHandles, displayInfos); + } else { + mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos)); + } } private void onWindowInfosChangedInternal(InputWindowHandle[] windowHandles, diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index edf09f14e873..76e7f535c60f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -339,7 +339,6 @@ import android.service.contentcapture.ActivityEvent; import android.service.dreams.DreamActivity; import android.service.voice.IVoiceInteractionSession; import android.util.ArraySet; -import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.MergedConfiguration; @@ -2123,14 +2122,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mWmService.mFlags.mInsetsDecoupledConfiguration) { // When the stable configuration is the default behavior, override for the legacy apps // without forward override flag. - mResolveConfigHint.mUseOverrideInsetsForStableBounds = + mResolveConfigHint.mUseOverrideInsetsForConfig = !info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED) && !info.isChangeEnabled( OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION); } else { // When the stable configuration is not the default behavior, forward overriding the // listed apps. - mResolveConfigHint.mUseOverrideInsetsForStableBounds = + mResolveConfigHint.mUseOverrideInsetsForConfig = info.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION); } @@ -3272,8 +3271,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mOccludesParent = occludesParent; setMainWindowOpaque(occludesParent); - if (changed && task != null && !occludesParent) { - getRootTask().convertActivityToTranslucent(this); + if (changed && task != null) { + if (!occludesParent) { + getRootTask().convertActivityToTranslucent(this); + } else { + getRootTask().convertActivityFromTranslucent(this); + } } // Always ensure visibility if this activity doesn't occlude parent, so the // {@link #returningOptions} of the activity under this one can be applied in @@ -4266,6 +4269,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A getTaskFragment().cleanUpActivityReferences(this); clearLastParentBeforePip(); + // Abort and reset state if the scence transition is playing. + final Task rootTask = getRootTask(); + if (rootTask != null) { + rootTask.abortTranslucentActivityWaiting(this); + } + // Clean up the splash screen if it was still displayed. cleanUpSplashScreen(); @@ -5672,6 +5681,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (mTransitionController.inFinishingTransition(this)) { mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION; } + } else { + mTransitionChangeFlags &= ~FLAG_IS_OCCLUDED; } return; } @@ -6519,8 +6530,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // and the token could be null. return; } - if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) { - r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r); + if (r.mDisplayContent.mActivityRefresher != null) { + r.mDisplayContent.mActivityRefresher.onActivityRefreshed(r); } } @@ -8480,7 +8491,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mCompatDisplayInsets = new CompatDisplayInsets( mDisplayContent, this, letterboxedContainerBounds, - mResolveConfigHint.mUseOverrideInsetsForStableBounds); + mResolveConfigHint.mUseOverrideInsetsForConfig); } private void clearSizeCompatModeAttributes() { @@ -8560,8 +8571,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int parentWindowingMode = newParentConfiguration.windowConfiguration.getWindowingMode(); - applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig); - // Bubble activities should always fill their parent and should not be letterboxed. final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble() && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW @@ -8661,6 +8670,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } + applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig); + logAppCompatState(); } @@ -8679,14 +8690,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mDisplayContent == null) { return; } - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); int rotation = newParentConfiguration.windowConfiguration.getRotation(); if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) { rotation = mDisplayContent.getRotation(); } - if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds - || getCompatDisplayInsets() != null || isFloating(parentWindowingMode) - || rotation == ROTATION_UNDEFINED) { + if (!mResolveConfigHint.mUseOverrideInsetsForConfig + || getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets() + || isFloating(parentWindowingMode) || rotation == ROTATION_UNDEFINED) { // If the insets configuration decoupled logic is not enabled for the app, or the app // already has a compat override, or the context doesn't contain enough info to // calculate the override, skip the override. @@ -8703,53 +8713,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Override starts here. - final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - final int dw = rotated ? mDisplayContent.mBaseDisplayHeight - : mDisplayContent.mBaseDisplayWidth; - final int dh = rotated ? mDisplayContent.mBaseDisplayWidth - : mDisplayContent.mBaseDisplayHeight; - final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy() - .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets; - // This should be the only place override the configuration for ActivityRecord. Override - // the value if not calculated yet. - Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - if (outAppBounds == null || outAppBounds.isEmpty()) { - inOutConfig.windowConfiguration.setAppBounds(parentBounds); - outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - outAppBounds.inset(nonDecorInsets); - } - float density = inOutConfig.densityDpi; - if (density == Configuration.DENSITY_DPI_UNDEFINED) { - density = newParentConfiguration.densityDpi; - } - density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; - if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f); - inOutConfig.screenWidthDp = overrideScreenWidthDp; - } - if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - final int overrideScreenHeightDp = (int) (outAppBounds.height() / density + 0.5f); - inOutConfig.screenHeightDp = overrideScreenHeightDp; - } - if (inOutConfig.smallestScreenWidthDp - == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED - && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { - // For the case of PIP transition and multi-window environment, the - // smallestScreenWidthDp is handled already. Override only if the app is in - // fullscreen. - final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo()); - mDisplayContent.computeSizeRanges(info, rotated, dw, dh, - mDisplayContent.getDisplayMetrics().density, - inOutConfig, true /* overrideConfig */); - } - - // It's possible that screen size will be considered in different orientation with or - // without considering the system bar insets. Override orientation as well. - if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { - inOutConfig.orientation = - (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) - ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; - } + computeConfigByResolveHint(inOutConfig, newParentConfiguration); } private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig, @@ -9005,7 +8969,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mDisplayContent == null) { return true; } - if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds) { + if (!mResolveConfigHint.mUseOverrideInsetsForConfig) { // No insets should be considered any more. return true; } @@ -9024,7 +8988,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Task task = getTask(); task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */, outStableBounds /* outStableBounds */, parentBounds /* bounds */, di, - mResolveConfigHint.mUseOverrideInsetsForStableBounds); + mResolveConfigHint.mUseOverrideInsetsForConfig); final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width() ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; // If orientation does not match the orientation with insets applied, then a @@ -9081,7 +9045,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A getResolvedOverrideConfiguration().windowConfiguration.getBounds(); final int stableBoundsOrientation = stableBounds.width() > stableBounds.height() ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; - final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForStableBounds + final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig ? stableBoundsOrientation : newParentConfig.orientation; // If the activity requires a different orientation (either by override or activityInfo), @@ -9106,7 +9070,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } - final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForStableBounds + final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForConfig ? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds(); // TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app // bounds or stable bounds to unify aspect ratio logic. @@ -10032,7 +9996,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo); } - notifyDisplayCompatPolicyAboutConfigurationChange( + notifyActivityRefresherAboutConfigurationChange( mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig); return true; } @@ -10099,18 +10063,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo); } - notifyDisplayCompatPolicyAboutConfigurationChange( + notifyActivityRefresherAboutConfigurationChange( mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig); return true; } - private void notifyDisplayCompatPolicyAboutConfigurationChange( + private void notifyActivityRefresherAboutConfigurationChange( Configuration newConfig, Configuration lastReportedConfig) { - if (mDisplayContent.mDisplayRotationCompatPolicy == null + if (mDisplayContent.mActivityRefresher == null || !shouldBeResumed(/* activeActivity */ null)) { return; } - mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging( + mDisplayContent.mActivityRefresher.onActivityConfigurationChanging( this, newConfig, lastReportedConfig); } diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java new file mode 100644 index 000000000000..23a97089fd60 --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivityRefresher.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.wm; + +import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; + +import android.annotation.NonNull; +import android.app.servertransaction.RefreshCallbackItem; +import android.app.servertransaction.ResumeActivityItem; +import android.content.res.Configuration; +import android.os.Handler; +import android.os.RemoteException; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; + +/** + * Class that refreshes the activity (through stop/pause -> resume) based on configuration change. + * + * <p>This class queries all of its {@link Evaluator}s and restarts the activity if any of them + * return {@code true} in {@link Evaluator#shouldRefreshActivity}. {@link ActivityRefresher} cycles + * through either stop or pause and then resume, based on the global config and per-app override. + */ +class ActivityRefresher { + // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The + // client process may not always report the event back to the server, such as process is + // crashed or got killed. + private static final long REFRESH_CALLBACK_TIMEOUT_MS = 2000L; + + @NonNull private final WindowManagerService mWmService; + @NonNull private final Handler mHandler; + @NonNull private final ArrayList<Evaluator> mEvaluators = new ArrayList<>(); + + ActivityRefresher(@NonNull WindowManagerService wmService, @NonNull Handler handler) { + mWmService = wmService; + mHandler = handler; + } + + void addEvaluator(@NonNull Evaluator evaluator) { + mEvaluators.add(evaluator); + } + + void removeEvaluator(@NonNull Evaluator evaluator) { + mEvaluators.remove(evaluator); + } + + /** + * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle. + * This allows to clear cached values in apps (e.g. display or camera rotation) that influence + * camera preview and can lead to sideways or stretching issues persisting even after force + * rotation. + */ + void onActivityConfigurationChanging(@NonNull ActivityRecord activity, + @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { + if (!shouldRefreshActivity(activity, newConfig, lastReportedConfig)) { + return; + } + + final boolean cycleThroughStop = + mWmService.mLetterboxConfiguration + .isCameraCompatRefreshCycleThroughStopEnabled() + && !activity.mLetterboxUiController + .shouldRefreshActivityViaPauseForCameraCompat(); + + activity.mLetterboxUiController.setIsRefreshRequested(true); + ProtoLog.v(WM_DEBUG_STATES, + "Refreshing activity for freeform camera compatibility treatment, " + + "activityRecord=%s", activity); + final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain( + activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE); + final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain( + activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false); + try { + activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems( + activity.app.getThread(), refreshCallbackItem, resumeActivityItem); + mHandler.postDelayed(() -> { + synchronized (mWmService.mGlobalLock) { + onActivityRefreshed(activity); + } + }, REFRESH_CALLBACK_TIMEOUT_MS); + } catch (RemoteException e) { + activity.mLetterboxUiController.setIsRefreshRequested(false); + } + } + + boolean isActivityRefreshing(@NonNull ActivityRecord activity) { + return activity.mLetterboxUiController.isRefreshRequested(); + } + + void onActivityRefreshed(@NonNull ActivityRecord activity) { + // TODO(b/333060789): can we tell that refresh did not happen by observing the activity + // state? + activity.mLetterboxUiController.setIsRefreshRequested(false); + } + + private boolean shouldRefreshActivity(@NonNull ActivityRecord activity, + @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { + return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled() + && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat() + && ArrayUtils.find(mEvaluators.toArray(), evaluator -> + ((Evaluator) evaluator) + .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null; + } + + /** + * Interface for classes that would like to refresh the recently updated activity, based on the + * configuration change. + */ + interface Evaluator { + boolean shouldRefreshActivity(@NonNull ActivityRecord activity, + @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig); + } +} diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java index 3609837f417b..ed07afd2eab5 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java @@ -30,10 +30,12 @@ class ActivitySnapshotCache extends SnapshotCache<ActivityRecord> { @Override void putSnapshot(ActivityRecord ar, TaskSnapshot snapshot) { final int hasCode = System.identityHashCode(ar); + snapshot.addReference(TaskSnapshot.REFERENCE_CACHE); synchronized (mLock) { final CacheEntry entry = mRunningCache.get(hasCode); if (entry != null) { mAppIdMap.remove(entry.topApp); + entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE); } mAppIdMap.put(ar, hasCode); mRunningCache.put(hasCode, new CacheEntry(snapshot, ar)); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 330336760413..08aeedebf77d 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1768,6 +1768,7 @@ class ActivityStarter { if (!avoidMoveToFront() && (mService.mHomeProcess == null || mService.mHomeProcess.mUid != realCallingUid) && (prevTopTask != null && prevTopTask.isActivityTypeHomeOrRecents()) + && !targetTask.isActivityTypeHomeOrRecents() && r.mTransitionController.isTransientHide(targetTask)) { mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY; } @@ -2113,7 +2114,6 @@ class ActivityStarter { if (hostTask == null || targetTask != hostTask) { return EMBEDDING_DISALLOWED_NEW_TASK; } - return taskFragment.isAllowedToEmbedActivity(starting); } @@ -2167,7 +2167,7 @@ class ActivityStarter { // We don't need to start a new activity, and the client said not to do anything // if that is the case, so this is it! And for paranoia, make sure we have // correctly resumed the top activity. - if (!mMovedToFront && mDoResume) { + if (!mMovedToFront && mDoResume && !avoidMoveToFront()) { ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetRootTask, targetTaskTop); mTargetRootTask.moveToFront("intentActivityFound"); @@ -2196,7 +2196,7 @@ class ActivityStarter { if (mMovedToFront) { // We moved the task to front, use starting window to hide initial drawn delay. targetTaskTop.showStartingWindow(true /* taskSwitch */); - } else if (mDoResume) { + } else if (mDoResume && !avoidMoveToFront()) { // Make sure the root task and its belonging display are moved to topmost. mTargetRootTask.moveToFront("intentActivityFound"); } @@ -2961,23 +2961,9 @@ class ActivityStarter { sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult); } } else { - TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null; + TaskFragment candidateTf = mAddingToTaskFragment; if (candidateTf == null) { - // Puts the activity on the top-most non-isolated navigation TF, unless the - // activity is launched from the same TF. - final TaskFragment sourceTaskFragment = - mSourceRecord != null ? mSourceRecord.getTaskFragment() : null; - final ActivityRecord top = task.getActivity(r -> { - if (!r.canBeTopRunning()) { - return false; - } - final TaskFragment taskFragment = r.getTaskFragment(); - return !taskFragment.isIsolatedNav() || (sourceTaskFragment != null - && sourceTaskFragment == taskFragment); - }); - if (top != null) { - candidateTf = top.getTaskFragment(); - } + candidateTf = findCandidateTaskFragment(task); } if (candidateTf != null && candidateTf.isEmbedded() && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) { @@ -2995,6 +2981,50 @@ class ActivityStarter { } /** + * Finds a candidate TaskFragment in {@code task} to launch activity, or returns {@code null} + * if there's no such a TaskFragment. + */ + @Nullable + private TaskFragment findCandidateTaskFragment(@NonNull Task task) { + final TaskFragment sourceTaskFragment = + mSourceRecord != null ? mSourceRecord.getTaskFragment() : null; + for (int i = task.getChildCount() - 1; i >= 0; --i) { + final WindowContainer<?> wc = task.getChildAt(i); + final ActivityRecord activity = wc.asActivityRecord(); + if (activity != null) { + if (activity.finishing) { + continue; + } + // Early return if the top child is an Activity. + return null; + } + final TaskFragment taskFragment = wc.asTaskFragment(); + if (taskFragment == null || taskFragment.isRemovalRequested()) { + // Skip if the TaskFragment is going to be finished. + continue; + } + if (taskFragment.getActivity(ActivityRecord::canBeTopRunning) == null) { + // Skip if there's no activity in this TF can be top running. + continue; + } + if (taskFragment.isIsolatedNav()) { + // Stop here if we reach an isolated navigated TF. + return null; + } + if (sourceTaskFragment != null && sourceTaskFragment == taskFragment) { + // Choose the taskFragment launched from even if it's pinned. + return taskFragment; + } + if (taskFragment.isPinned()) { + // Skip the pinned TaskFragment. + continue; + } + return taskFragment; + } + return null; + } + + /** * Notifies the client side that {@link #mStartActivity} cannot be embedded to * {@code taskFragment}. */ diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 237003a5fa10..3aa63af014c8 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -7418,7 +7418,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { FEATURE_LEANBACK); final boolean isArc = arcFeature != null && arcFeature.version >= 0; final boolean isTv = tvFeature != null && tvFeature.version >= 0; - sIsPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false) + sIsPip2ExperimentEnabled = SystemProperties.getBoolean( + "persist.wm_shell.pip2", false) || (Flags.enablePip2Implementation() && !isArc && !isTv); } return sIsPip2ExperimentEnabled; diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index eb1f3b402364..f7910b08b1e2 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityOptions.BackgroundActivityStartMode; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -105,6 +106,7 @@ public class BackgroundActivityStartController { static final String AUTO_OPT_IN_NOT_PENDING_INTENT = "notPendingIntent"; static final String AUTO_OPT_IN_CALL_FOR_RESULT = "callForResult"; static final String AUTO_OPT_IN_SAME_UID = "sameUid"; + static final String AUTO_OPT_IN_COMPAT = "compatibility"; /** If enabled the creator will not allow BAL on its behalf by default. */ @ChangeId @@ -303,6 +305,10 @@ public class BackgroundActivityStartController { } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) { mAutoOptInReason = AUTO_OPT_IN_SAME_UID; mAutoOptInCaller = false; + } else if (realCallerBackgroundActivityStartMode + == MODE_BACKGROUND_ACTIVITY_START_COMPAT) { + mAutoOptInReason = AUTO_OPT_IN_COMPAT; + mAutoOptInCaller = false; } else { mAutoOptInReason = null; mAutoOptInCaller = false; @@ -1695,6 +1701,7 @@ public class BackgroundActivityStartController { return false; } if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts() + && state.mResultForRealCaller != null && state.mResultForRealCaller.getRawCode() == BAL_ALLOW_VISIBLE_WINDOW) { return false; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a5853c013c7b..e49cb386ac49 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -478,6 +478,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; @Nullable final CameraStateMonitor mCameraStateMonitor; + @Nullable + final ActivityRefresher mActivityRefresher; DisplayFrames mDisplayFrames; final DisplayUpdater mDisplayUpdater; @@ -1233,13 +1235,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime(); if (shouldCreateDisplayRotationCompatPolicy) { mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH); + mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH); mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy( - this, mWmService.mH, mCameraStateMonitor); + this, mCameraStateMonitor, mActivityRefresher); mCameraStateMonitor.startListeningToCameraState(); } else { // These are to satisfy the `final` check. mCameraStateMonitor = null; + mActivityRefresher = null; mDisplayRotationCompatPolicy = null; } @@ -5009,7 +5013,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // This should be called after the insets have been dispatched to clients and we have // committed finish drawing windows. - mInsetsStateController.getImeSourceProvider().checkShowImePostLayout(); + mInsetsStateController.getImeSourceProvider().checkAndStartShowImePostLayout(); mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent; if (!inTransition() && !mDisplayRotation.isRotatingSeamlessly()) { diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index eacf9a3fa759..e0cc064fcacc 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -18,8 +18,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; -import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; @@ -32,19 +30,14 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.view.Display.TYPE_INTERNAL; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; -import android.app.servertransaction.RefreshCallbackItem; -import android.app.servertransaction.ResumeActivityItem; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.os.Handler; -import android.os.RemoteException; import android.widget.Toast; import com.android.internal.R; @@ -64,48 +57,38 @@ import com.android.server.UiThread; * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}. */ // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path -class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener { - - // Delay for updating display rotation after Camera connection is closed. Needed to avoid - // rotation flickering when an app is flipping between front and rear cameras or when size - // compat mode is restarted. - // TODO(b/263114289): Consider associating this delay with a specific activity so that if - // the new non-camera activity started on top of the camer one we can rotate faster. - private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000; - // Delay for updating display rotation after Camera connection is opened. This delay is - // selected to be long enough to avoid conflicts with transitions on the app's side. - // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app - // is flipping between front and rear cameras (in case requested orientation changes at - // runtime at the same time) or when size compat mode is restarted. - private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS = - CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2; - // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The - // client process may not always report the event back to the server, such as process is - // crashed or got killed. - private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000; +final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener, + ActivityRefresher.Evaluator { + @NonNull private final DisplayContent mDisplayContent; + @NonNull private final WindowManagerService mWmService; + @NonNull private final CameraStateMonitor mCameraStateMonitor; - private final Handler mHandler; + @NonNull + private final ActivityRefresher mActivityRefresher; @ScreenOrientation private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET; - DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler, - @NonNull CameraStateMonitor cameraStateMonitor) { + DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, + @NonNull CameraStateMonitor cameraStateMonitor, + @NonNull ActivityRefresher activityRefresher) { // This constructor is called from DisplayContent constructor. Don't use any fields in // DisplayContent here since they aren't guaranteed to be set. - mHandler = handler; mDisplayContent = displayContent; mWmService = displayContent.mWmService; mCameraStateMonitor = cameraStateMonitor; mCameraStateMonitor.addCameraStateListener(this); + mActivityRefresher = activityRefresher; + mActivityRefresher.addEvaluator(this); } /** Releases camera state listener. */ void dispose() { mCameraStateMonitor.removeCameraStateListener(this); + mActivityRefresher.removeEvaluator(this); } /** @@ -169,47 +152,6 @@ class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStat } /** - * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle. - * This allows to clear cached values in apps (e.g. display or camera rotation) that influence - * camera preview and can lead to sideways or stretching issues persisting even after force - * rotation. - */ - void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig, - Configuration lastReportedConfig) { - if (!isTreatmentEnabledForDisplay() - || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled() - || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) { - return; - } - boolean cycleThroughStop = - mWmService.mLetterboxConfiguration - .isCameraCompatRefreshCycleThroughStopEnabled() - && !activity.mLetterboxUiController - .shouldRefreshActivityViaPauseForCameraCompat(); - try { - activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true); - ProtoLog.v(WM_DEBUG_STATES, - "Refreshing activity for camera compatibility treatment, " - + "activityRecord=%s", activity); - final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain( - activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE); - final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain( - activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false); - activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems( - activity.app.getThread(), refreshCallbackItem, resumeActivityItem); - mHandler.postDelayed( - () -> onActivityRefreshed(activity), - REFRESH_CALLBACK_TIMEOUT_MS); - } catch (RemoteException e) { - activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false); - } - } - - void onActivityRefreshed(@NonNull ActivityRecord activity) { - activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false); - } - - /** * Notifies that animation in {@link ScreenRotationAnimation} has finished. * * <p>This class uses this signal as a trigger for notifying the user about forced rotation @@ -276,14 +218,16 @@ class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStat // Refreshing only when configuration changes after rotation or camera split screen aspect ratio // treatment is enabled - private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig, - Configuration lastReportedConfig) { + @Override + public boolean shouldRefreshActivity(@NonNull ActivityRecord activity, + @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { final boolean displayRotationChanged = (newConfig.windowConfiguration.getDisplayRotation() != lastReportedConfig.windowConfiguration.getDisplayRotation()); - return (displayRotationChanged - || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed()) + return isTreatmentEnabledForDisplay() && isTreatmentEnabledForActivity(activity) - && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat(); + && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat() + && (displayRotationChanged + || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed()); } /** @@ -310,7 +254,6 @@ class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStat && activity.mLetterboxUiController.shouldForceRotateForCameraCompat(); } - /** * Whether camera compat treatment is applicable for the given activity. * @@ -429,6 +372,6 @@ class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStat || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) { return false; } - return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested(); + return mActivityRefresher.isActivityRefreshing(topActivity); } } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 092ff3dd07a5..e03ff6881bd8 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -24,7 +24,6 @@ import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME; import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER; -import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN; import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS; import android.annotation.NonNull; @@ -52,19 +51,26 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { private static final String TAG = ImeInsetsSourceProvider.class.getSimpleName(); - /** The token tracking the current IME request or {@code null} otherwise. */ + /** The token tracking the show IME request, non-null only while a show request is pending. */ + @Nullable + private ImeTracker.Token mStatsToken; + /** The target that requested to show the IME, non-null only while a show request is pending. */ @Nullable - private ImeTracker.Token mImeRequesterStatsToken; private InsetsControlTarget mImeRequester; - private Runnable mShowImeRunner; - private boolean mIsImeLayoutDrawn; + /** @see #isImeShowing() */ private boolean mImeShowing; + /** The latest received insets source. */ private final InsetsSource mLastSource = new InsetsSource(ID_IME, WindowInsets.Type.ime()); /** @see #setFrozen(boolean) */ private boolean mFrozen; - /** @see #setServerVisible(boolean) */ + /** + * The server visibility of the source provider's window container. This is out of sync with + * {@link InsetsSourceProvider#mServerVisible} while {@link #mFrozen} is {@code true}. + * + * @see #setServerVisible + */ private boolean mServerVisible; ImeInsetsSourceProvider(@NonNull InsetsSource source, @@ -73,6 +79,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { super(source, stateController, displayContent); } + @Nullable @Override InsetsSourceControl getControl(InsetsControlTarget target) { final InsetsSourceControl control = super.getControl(target); @@ -124,9 +131,9 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { /** * Freeze IME insets source state when required. * - * When setting {@param frozen} as {@code true}, the IME insets provider will freeze the + * <p>When setting {@param frozen} as {@code true}, the IME insets provider will freeze the * current IME insets state and pending the IME insets state update until setting - * {@param frozen} as {@code false}. + * {@param frozen} as {@code false}.</p> */ void setFrozen(boolean frozen) { if (mFrozen == frozen) { @@ -223,27 +230,29 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { /** * Called from {@link WindowManagerInternal#showImePostLayout} * when {@link android.inputmethodservice.InputMethodService} requests to show IME - * on {@param imeTarget}. + * on the given control target. * - * @param imeTarget imeTarget on which IME request is coming from. + * @param imeTarget the control target on which the IME request is coming from. * @param statsToken the token tracking the current IME request. */ - void scheduleShowImePostLayout(InsetsControlTarget imeTarget, + void scheduleShowImePostLayout(@NonNull InsetsControlTarget imeTarget, @NonNull ImeTracker.Token statsToken) { - if (mImeRequesterStatsToken != null) { - // Cancel the pre-existing stats token, if any. - // Log state on pre-existing request cancel. - logShowImePostLayoutState(false /* aborted */); - ImeTracker.forLogging().onCancelled( - mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); - } - mImeRequesterStatsToken = statsToken; - boolean targetChanged = isTargetChangedWithinActivity(imeTarget); + if (mImeRequester == null) { + // Start tracing only on initial scheduled show IME request, to record end-to-end time. + Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); + } else { + // We already have a scheduled show IME request, cancel the previous statsToken and + // continue with the new one. + logIsScheduledAndReadyToShowIme(false /* aborted */); + ImeTracker.forLogging().onCancelled(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); + } + final boolean targetChanged = isTargetChangedWithinActivity(imeTarget); mImeRequester = imeTarget; + mStatsToken = statsToken; if (targetChanged) { // target changed, check if new target can show IME. ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord"); - checkShowImePostLayout(); + checkAndStartShowImePostLayout(); // if IME cannot be shown at this time, it is scheduled to be shown. // once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match, // it will be shown. @@ -252,79 +261,58 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null ? mImeRequester : mImeRequester.getWindow().getName()); - mShowImeRunner = () -> { - ImeTracker.forLogging().onProgress(mImeRequesterStatsToken, - ImeTracker.PHASE_WM_SHOW_IME_RUNNER); - ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); - // Target should still be the same. - if (isReadyToShowIme()) { - ImeTracker.forLogging().onProgress(mImeRequesterStatsToken, - ImeTracker.PHASE_WM_SHOW_IME_READY); - final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); - - ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", - target.getWindow() != null ? target.getWindow().getName() : ""); - setImeShowing(true); - target.showInsets(WindowInsets.Type.ime(), true /* fromIme */, - mImeRequesterStatsToken); - Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); - if (target != mImeRequester && mImeRequester != null) { - ProtoLog.w(WM_DEBUG_IME, - "showInsets(ime) was requested by different window: %s ", - (mImeRequester.getWindow() != null - ? mImeRequester.getWindow().getName() : "")); - } - } else { - ImeTracker.forLogging().onFailed(mImeRequesterStatsToken, - ImeTracker.PHASE_WM_SHOW_IME_READY); - } - // Clear token here so we don't report an error in abortShowImePostLayout(). - mImeRequesterStatsToken = null; - abortShowImePostLayout(); - }; mDisplayContent.mWmService.requestTraversal(); } - void checkShowImePostLayout() { - if (mWindowContainer == null) { + /** + * Checks whether there is a previously scheduled show IME request and we are ready to show, + * in which case also start handling the request. + */ + void checkAndStartShowImePostLayout() { + if (!isScheduledAndReadyToShowIme()) { + // This can later become ready, so we don't want to cancel the pending request here. return; } - WindowState windowState = mWindowContainer.asWindowState(); - if (windowState == null) { - throw new IllegalArgumentException("IME insets must be provided by a window."); - } - // check if IME is drawn - if (mIsImeLayoutDrawn - || (isReadyToShowIme() - && windowState.isDrawn() - && !windowState.mGivenInsetsPending)) { - mIsImeLayoutDrawn = true; - // show IME if InputMethodService requested it to be shown. - if (mShowImeRunner != null) { - mShowImeRunner.run(); - } + + ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); + ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); + + final InsetsControlTarget target = getControlTarget(); + + ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", + target.getWindow() != null ? target.getWindow().getName() : ""); + setImeShowing(true); + target.showInsets(WindowInsets.Type.ime(), true /* fromIme */, mStatsToken); + Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); + if (target != mImeRequester) { + ProtoLog.w(WM_DEBUG_IME, "showInsets(ime) was requested by different window: %s ", + (mImeRequester.getWindow() != null ? mImeRequester.getWindow().getName() : "")); } + resetShowImePostLayout(); } - /** - * Abort any pending request to show IME post layout. - */ + /** Aborts the previously scheduled show IME request. */ void abortShowImePostLayout() { - ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout"); - if (mImeRequesterStatsToken != null) { - // Log state on abort. - logShowImePostLayoutState(true /* aborted */); - ImeTracker.forLogging().onFailed( - mImeRequesterStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT); - mImeRequesterStatsToken = null; + if (mImeRequester == null) { + return; } + ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout"); + Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); + logIsScheduledAndReadyToShowIme(true /* aborted */); + ImeTracker.forLogging().onFailed( + mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT); + resetShowImePostLayout(); + } + + /** Resets the state of the previously scheduled show IME request. */ + private void resetShowImePostLayout() { mImeRequester = null; - mIsImeLayoutDrawn = false; - mShowImeRunner = null; + mStatsToken = null; } + /** Checks whether there is a previously scheduled show IME request and we are ready to show. */ @VisibleForTesting - boolean isReadyToShowIme() { + boolean isScheduledAndReadyToShowIme() { // IMMS#mLastImeTargetWindow always considers focused window as // IME target, however DisplayContent#computeImeTarget() can compute // a different IME target. @@ -334,32 +322,47 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // Also, if imeTarget is closing, it would be considered as outdated target. // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of // actual IME target. + if (mImeRequester == null) { + // No show IME request previously scheduled. + return false; + } + if (!mServerVisible || mFrozen) { + // The window container is not available and considered visible. + // If frozen, the server visibility is not set until unfrozen. + return false; + } + if (mWindowContainer == null) { + // No window container set. + return false; + } + final WindowState windowState = mWindowContainer.asWindowState(); + if (windowState == null) { + throw new IllegalArgumentException("IME insets must be provided by a window."); + } + if (!windowState.isDrawn() || windowState.mGivenInsetsPending) { + // The window is not drawn, or it has pending insets. + return false; + } final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); - if (dcTarget == null || mImeRequester == null) { - // Not ready to show if there is no IME layering target, or no IME requester. + if (dcTarget == null) { + // No IME layering target. return false; } final InsetsControlTarget controlTarget = getControlTarget(); if (controlTarget == null) { - // Not ready to show if there is no IME control target. + // No IME control target. return false; } if (controlTarget != mDisplayContent.getImeTarget(IME_TARGET_CONTROL)) { - // Not ready to show if control target does not match the one in DisplayContent. - return false; - } - if (!mServerVisible || mFrozen) { - // Not ready to show if the window container is not available and considered visible. - // If frozen, the server visibility is not set until unfrozen. + // The control target does not match the one in DisplayContent. return false; } if (mStateController.hasPendingControls(controlTarget)) { - // Not ready to show if control target has pending controls. + // The control target has pending controls. return false; } if (getLeash(controlTarget) == null) { - // Not ready to show if control target has no source control leash (or leash is not - // ready for dispatching). + // The control target has no source control leash (or it is not ready for dispatching). return false; } @@ -371,51 +374,44 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { || isAboveImeLayeringTarget(mImeRequester, dcTarget) || isImeFallbackTarget(mImeRequester) || isImeInputTarget(mImeRequester) - || sameAsImeControlTarget(); + || sameAsImeControlTarget(mImeRequester); } /** - * Logs the current state required for showImePostLayout to be triggered. + * Logs the current state that can be checked by {@link #isScheduledAndReadyToShowIme}. * - * @param aborted whether the showImePostLayout was aborted or cancelled. + * @param aborted whether the scheduled show IME request was aborted or cancelled. */ - private void logShowImePostLayoutState(boolean aborted) { + private void logIsScheduledAndReadyToShowIme(boolean aborted) { final var windowState = mWindowContainer != null ? mWindowContainer.asWindowState() : null; final var dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); final var controlTarget = getControlTarget(); final var sb = new StringBuilder(); sb.append("showImePostLayout ").append(aborted ? "aborted" : "cancelled"); - sb.append(", mWindowContainer is: "); - sb.append(mWindowContainer != null ? "non-null" : "null"); + sb.append(", isScheduledAndReadyToShowIme: ").append(isScheduledAndReadyToShowIme()); + sb.append(", mImeRequester: ").append(mImeRequester); + sb.append(", serverVisible: ").append(mServerVisible); + sb.append(", frozen: ").append(mFrozen); + sb.append(", mWindowContainer is: ").append(mWindowContainer != null ? "non-null" : "null"); sb.append(", windowState: ").append(windowState); if (windowState != null) { - sb.append(", windowState.isDrawn(): "); - sb.append(windowState.isDrawn()); - sb.append(", windowState.mGivenInsetsPending: "); - sb.append(windowState.mGivenInsetsPending); + sb.append(", isDrawn: ").append(windowState.isDrawn()); + sb.append(", mGivenInsetsPending: ").append(windowState.mGivenInsetsPending); } - sb.append(", mIsImeLayoutDrawn: ").append(mIsImeLayoutDrawn); - sb.append(", mShowImeRunner: ").append(mShowImeRunner); - sb.append(", mImeRequester: ").append(mImeRequester); sb.append(", dcTarget: ").append(dcTarget); sb.append(", controlTarget: ").append(controlTarget); - sb.append("\n"); - sb.append("isReadyToShowIme(): ").append(isReadyToShowIme()); if (mImeRequester != null && dcTarget != null && controlTarget != null) { - sb.append(", controlTarget == DisplayContent.controlTarget: "); + sb.append("\n"); + sb.append("controlTarget == DisplayContent.controlTarget: "); sb.append(controlTarget == mDisplayContent.getImeTarget(IME_TARGET_CONTROL)); sb.append(", hasPendingControls: "); sb.append(mStateController.hasPendingControls(controlTarget)); - sb.append(", serverVisible: "); - sb.append(mServerVisible); - sb.append(", frozen: "); - sb.append(mFrozen); - sb.append(", leash is: "); - sb.append(getLeash(controlTarget) != null ? "non-null" : "null"); - sb.append(", control is: "); - sb.append(mControl != null ? "non-null" : "null"); - sb.append(", mIsLeashReadyForDispatching: "); - sb.append(mIsLeashReadyForDispatching); + final boolean hasLeash = getLeash(controlTarget) != null; + sb.append(", leash is: ").append(hasLeash ? "non-null" : "null"); + if (!hasLeash) { + sb.append(", control is: ").append(mControl != null ? "non-null" : "null"); + sb.append(", mIsLeashReadyForDispatching: ").append(mIsLeashReadyForDispatching); + } sb.append(", isImeLayeringTarget: "); sb.append(isImeLayeringTarget(mImeRequester, dcTarget)); sb.append(", isAboveImeLayeringTarget: "); @@ -425,7 +421,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { sb.append(", isImeInputTarget: "); sb.append(isImeInputTarget(mImeRequester)); sb.append(", sameAsImeControlTarget: "); - sb.append(sameAsImeControlTarget()); + sb.append(sameAsImeControlTarget(mImeRequester)); } Slog.d(TAG, sb.toString()); } @@ -445,19 +441,18 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer; } - private boolean isImeFallbackTarget(InsetsControlTarget target) { + private boolean isImeFallbackTarget(@NonNull InsetsControlTarget target) { return target == mDisplayContent.getImeFallback(); } - private boolean isImeInputTarget(InsetsControlTarget target) { + private boolean isImeInputTarget(@NonNull InsetsControlTarget target) { return target == mDisplayContent.getImeInputTarget(); } - private boolean sameAsImeControlTarget() { - final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); - return target == mImeRequester - && (mImeRequester.getWindow() == null - || !isImeTargetWindowClosing(mImeRequester.getWindow())); + private boolean sameAsImeControlTarget(@NonNull InsetsControlTarget target) { + final InsetsControlTarget controlTarget = getControlTarget(); + return controlTarget == target + && (target.getWindow() == null || !isImeTargetWindowClosing(target.getWindow())); } private static boolean isImeTargetWindowClosing(@NonNull WindowState win) { @@ -467,16 +462,15 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { || win.mActivityRecord.willCloseOrEnterPip()); } - private boolean isTargetChangedWithinActivity(InsetsControlTarget target) { + private boolean isTargetChangedWithinActivity(@NonNull InsetsControlTarget target) { // We don't consider the target out of the activity. - if (target == null || target.getWindow() == null) { + if (target.getWindow() == null) { return false; } return mImeRequester != target - && mImeRequester != null && mShowImeRunner != null + && mImeRequester != null && mImeRequester.getWindow() != null - && mImeRequester.getWindow().mActivityRecord - == target.getWindow().mActivityRecord; + && mImeRequester.getWindow().mActivityRecord == target.getWindow().mActivityRecord; } // --------------------------------------------------------------------------------------- @@ -509,7 +503,6 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (imeRequesterWindow != null) { imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel); } - proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 4400ed23a1b7..2288fe998b58 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -198,7 +198,7 @@ class InsetsSourceProvider { if (mControllable) { mWindowContainer.setControllableInsetProvider(this); if (mPendingControlTarget != mControlTarget) { - updateControlForTarget(mPendingControlTarget, true /* force */); + mStateController.notifyControlTargetChanged(mPendingControlTarget, this); } } } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index dfee16440518..7a1f57bea680 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -389,7 +389,7 @@ class InsetsStateController { newControlTargets.clear(); // Check for and try to run the scheduled show IME request (if it exists), as we // now applied the surface transaction and notified the target of the new control. - getImeSourceProvider().checkShowImePostLayout(); + getImeSourceProvider().checkAndStartShowImePostLayout(); }); } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 57827c567b5b..16d7b4fb6eed 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -260,7 +260,7 @@ final class LetterboxUiController { // Whether activity "refresh" was requested but not finished in // ActivityRecord#activityResumedLocked following the camera compat force rotation in // DisplayRotationCompatPolicy. - private boolean mIsRefreshAfterRotationRequested; + private boolean mIsRefreshRequested; @NonNull private final OptProp mIgnoreRequestedOrientationOptProp; @@ -571,15 +571,14 @@ final class LetterboxUiController { } /** - * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked} - * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}. + * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}. */ - boolean isRefreshAfterRotationRequested() { - return mIsRefreshAfterRotationRequested; + boolean isRefreshRequested() { + return mIsRefreshRequested; } - void setIsRefreshAfterRotationRequested(boolean isRequested) { - mIsRefreshAfterRotationRequested = isRequested; + void setIsRefreshRequested(boolean isRequested) { + mIsRefreshRequested = isRequested; } boolean isOverrideRespectRequestedOrientationEnabled() { @@ -1068,7 +1067,7 @@ final class LetterboxUiController { * thin letteboxing */ boolean allowVerticalReachabilityForThinLetterbox() { - if (!Flags.disableThinLetterboxingReachability()) { + if (!Flags.disableThinLetterboxingPolicy()) { return true; } // When the flag is enabled we allow vertical reachability only if the @@ -1081,7 +1080,7 @@ final class LetterboxUiController { * thin letteboxing */ boolean allowHorizontalReachabilityForThinLetterbox() { - if (!Flags.disableThinLetterboxingReachability()) { + if (!Flags.disableThinLetterboxingPolicy()) { return true; } // When the flag is enabled we allow horizontal reachability only if the diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java index 498182dab9c3..3606a34e23e0 100644 --- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java +++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java @@ -41,8 +41,12 @@ class PerfettoTransitionTracer implements TransitionTracer { PerfettoTransitionTracer() { Producer.init(InitArguments.DEFAULTS); - mDataSource.register( - new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .build(); + mDataSource.register(params); } /** diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java index 86804360f6f4..1e6ee7dc318f 100644 --- a/services/core/java/com/android/server/wm/SnapshotCache.java +++ b/services/core/java/com/android/server/wm/SnapshotCache.java @@ -92,6 +92,7 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { if (entry != null) { mAppIdMap.remove(entry.topApp); mRunningCache.remove(id); + entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE); } } } diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java index 357897127f3a..42ca7b44287e 100644 --- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java +++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java @@ -253,6 +253,7 @@ class SnapshotPersistQueue { PersistInfoProvider provider) { super(provider, userId); mId = id; + snapshot.addReference(TaskSnapshot.REFERENCE_PERSIST); mSnapshot = snapshot; } @@ -289,6 +290,7 @@ class SnapshotPersistQueue { if (failed) { deleteSnapshot(mId, mUserId, mPersistInfoProvider); } + mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8defec3dbeab..a555388ab233 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -297,6 +297,10 @@ class Task extends TaskFragment { ActivityRecord mTranslucentActivityWaiting = null; ArrayList<ActivityRecord> mUndrawnActivitiesBelowTopTranslucent = new ArrayList<>(); + // The topmost Activity that was converted to translucent for scene transition, which should + // be converted from translucent once the transition is completed, or the app died. + private ActivityRecord mPendingConvertFromTranslucentActivity = null; + /** * Set when we know we are going to be calling updateConfiguration() * soon, so want to skip intermediate config checks. @@ -4988,6 +4992,27 @@ class Task extends TaskFragment { } } + void abortTranslucentActivityWaiting(@NonNull ActivityRecord r) { + if (r != mTranslucentActivityWaiting && r != mPendingConvertFromTranslucentActivity) { + return; + } + + if (mTranslucentActivityWaiting != null) { + if (!mTranslucentActivityWaiting.finishing) { + mTranslucentActivityWaiting.setOccludesParent(true); + } + mTranslucentActivityWaiting = null; + } + if (mPendingConvertFromTranslucentActivity != null) { + if (!mPendingConvertFromTranslucentActivity.finishing) { + mPendingConvertFromTranslucentActivity.setOccludesParent(true); + } + mPendingConvertFromTranslucentActivity = null; + } + mUndrawnActivitiesBelowTopTranslucent.clear(); + mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG); + } + void checkTranslucentActivityWaiting(ActivityRecord top) { if (mTranslucentActivityWaiting != top) { mUndrawnActivitiesBelowTopTranslucent.clear(); @@ -5002,10 +5027,19 @@ class Task extends TaskFragment { void convertActivityToTranslucent(ActivityRecord r) { mTranslucentActivityWaiting = r; + mPendingConvertFromTranslucentActivity = r; mUndrawnActivitiesBelowTopTranslucent.clear(); mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT); } + void convertActivityFromTranslucent(ActivityRecord r) { + if (r != mPendingConvertFromTranslucentActivity) { + Slog.e(TAG, "convertFromTranslucent expects " + mPendingConvertFromTranslucentActivity + + " but is " + r); + } + mPendingConvertFromTranslucentActivity = null; + } + /** * Called as activities below the top translucent activity are redrawn. When the last one is * redrawn notify the top activity by calling diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java index 21e7a8d63773..586f3c35c0c4 100644 --- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java @@ -247,6 +247,7 @@ class TaskChangeNotificationController { break; case NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG: forAllRemoteListeners(mNotifyTaskSnapshotChanged, msg); + ((TaskSnapshot) msg.obj).removeReference(TaskSnapshot.REFERENCE_BROADCAST); break; case NOTIFY_BACK_PRESSED_ON_TASK_ROOT: forAllRemoteListeners(mNotifyBackPressedOnTaskRoot, msg); @@ -485,6 +486,7 @@ class TaskChangeNotificationController { * Notify listeners that the snapshot of a task has changed. */ void notifyTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { + snapshot.addReference(TaskSnapshot.REFERENCE_BROADCAST); final Message msg = mHandler.obtainMessage(NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG, taskId, 0, snapshot); forAllLocalListeners(mNotifyTaskSnapshotChanged, msg); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 6a7f60b3447d..26e4eaaedfe2 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -40,6 +40,8 @@ import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static android.os.UserHandle.USER_NULL; import static android.view.Display.INVALID_DISPLAY; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; @@ -354,14 +356,21 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** * Whether the activity navigation should be isolated. That is, Activities cannot be launched - * on an isolated TaskFragment, unless the activity is launched from an Activity in the same - * isolated TaskFragment, or explicitly requested to be launched to. - * <p> - * Note that only an embedded TaskFragment can be isolated. + * on an isolated TaskFragment unless explicitly requested to be launched to. */ private boolean mIsolatedNav; /** + * Whether the TaskFragment to be pinned. + * <p> + * If a TaskFragment is pinned, the TaskFragment should be the top-most TaskFragment among other + * sibling TaskFragments. Any newly launched and embeddable activity should not be placed in the + * pinned TaskFragment, unless the activity is launched from the pinned TaskFragment or + * explicitly requested to. Non-embeddable activities are not restricted to. + */ + private boolean mPinned; + + /** * Whether the TaskFragment should move to bottom of task when any activity below it is * launched in clear top mode. */ @@ -515,6 +524,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { } /** + * Sets whether this TaskFragment {@link #isPinned()}. + * <p> + * Note that this is no-op if the TaskFragment is not {@link #isEmbedded() embedded}. + */ + void setPinned(boolean pinned) { + if (!isEmbedded()) { + return; + } + mPinned = pinned; + } + + /** * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true}, * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions * will wait until the TaskFragment becomes non-empty or other conditions are met. Default @@ -532,6 +553,15 @@ class TaskFragment extends WindowContainer<WindowContainer> { return isEmbedded() && mIsolatedNav; } + /** + * Indicates whether this TaskFragment is pinned. + * + * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED + */ + boolean isPinned() { + return isEmbedded() && mPinned; + } + TaskFragment getAdjacentTaskFragment() { return mAdjacentTaskFragment; } @@ -564,7 +594,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { } void setResumedActivity(ActivityRecord r, String reason) { - warnForNonLeafTaskFragment("setResumedActivity"); if (mResumedActivity == r) { return; } @@ -850,15 +879,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return parentTaskFragment != null ? parentTaskFragment.getOrganizedTaskFragment() : null; } - /** - * Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}. - */ - private void warnForNonLeafTaskFragment(String func) { - if (!isLeafTaskFragment()) { - Slog.w(TAG, func + " on non-leaf task fragment " + this); - } - } - boolean hasDirectChildActivities() { for (int i = mChildren.size() - 1; i >= 0; --i) { if (mChildren.get(i).asActivityRecord() != null) { @@ -935,7 +955,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state, String reason) { - warnForNonLeafTaskFragment("onActivityStateChanged"); if (record == mResumedActivity && state != RESUMED) { setResumedActivity(null, reason + " - onActivityStateChanged"); } @@ -965,7 +984,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { * @return {@code true} if the process of the pausing activity is died. */ boolean handleAppDied(WindowProcessController app) { - warnForNonLeafTaskFragment("handleAppDied"); boolean isPausingDied = false; if (mPausingActivity != null && mPausingActivity.app == app) { ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s", @@ -2206,7 +2224,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { static class ConfigOverrideHint { @Nullable DisplayInfo mTmpOverrideDisplayInfo; @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets; - boolean mUseOverrideInsetsForStableBounds; + boolean mUseOverrideInsetsForConfig; } void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @@ -2239,11 +2257,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) { DisplayInfo overrideDisplayInfo = null; ActivityRecord.CompatDisplayInsets compatInsets = null; - boolean useOverrideInsetsForStableBounds = false; + boolean useOverrideInsetsForConfig = false; if (overrideHint != null) { overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo; compatInsets = overrideHint.mTmpCompatInsets; - useOverrideInsetsForStableBounds = overrideHint.mUseOverrideInsetsForStableBounds; + useOverrideInsetsForConfig = overrideHint.mUseOverrideInsetsForConfig; if (overrideDisplayInfo != null) { // Make sure the screen related configs can be computed by the provided // display info. @@ -2307,6 +2325,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { } } + boolean insetsOverrideApplied = false; if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) { @@ -2323,7 +2342,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { // The non decor inset are areas that could never be removed in Honeycomb. See // {@link WindowManagerPolicy#getNonDecorInsetsLw}. calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di, - useOverrideInsetsForStableBounds); + useOverrideInsetsForConfig); } else { // Apply the given non-decor and stable insets to calculate the corresponding bounds // for screen size of configuration. @@ -2340,8 +2359,21 @@ class TaskFragment extends WindowContainer<WindowContainer> { intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds, compatInsets.mStableInsets[rotation]); outAppBounds.set(mTmpNonDecorBounds); + } else if (useOverrideInsetsForConfig) { + final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); + final int dw = rotated ? mDisplayContent.mBaseDisplayHeight + : mDisplayContent.mBaseDisplayWidth; + final int dh = rotated ? mDisplayContent.mBaseDisplayWidth + : mDisplayContent.mBaseDisplayHeight; + final DisplayPolicy.DecorInsets.Info decorInsets = mDisplayContent + .getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh); + mTmpStableBounds.set(outAppBounds); + mTmpStableBounds.inset(decorInsets.mOverrideConfigInsets); + outAppBounds.inset(decorInsets.mOverrideNonDecorInsets); + mTmpNonDecorBounds.set(outAppBounds); + // Record the override apply to avoid duplicated check. + insetsOverrideApplied = true; } else { - // Set to app bounds because it excludes decor insets. mTmpNonDecorBounds.set(outAppBounds); mTmpStableBounds.set(outAppBounds); } @@ -2383,6 +2415,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { // from the parent task would result in applications loaded wrong resource. inOutConfig.smallestScreenWidthDp = Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp); + } else if (insetsOverrideApplied) { + // The smallest width should also consider insets. If the insets are overridden, + // use the overridden value. + inOutConfig.smallestScreenWidthDp = + Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp); } // otherwise, it will just inherit } @@ -2895,6 +2932,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { return !mCreatedByOrganizer || mIsRemovalRequested; } + /** + * Returns whether this TaskFragment is going to be removed. + */ + boolean isRemovalRequested() { + return mIsRemovalRequested; + } + @Override void removeChild(WindowContainer child) { removeChild(child, true /* removeSelfIfPossible */); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java index b69ac1bb2795..64b9df59990b 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java @@ -35,9 +35,11 @@ class TaskSnapshotCache extends SnapshotCache<Task> { void putSnapshot(Task task, TaskSnapshot snapshot) { synchronized (mLock) { + snapshot.addReference(TaskSnapshot.REFERENCE_CACHE); final CacheEntry entry = mRunningCache.get(task.mTaskId); if (entry != null) { mAppIdMap.remove(entry.topApp); + entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE); } final ActivityRecord top = task.getTopMostActivity(); mAppIdMap.put(top, task.mTaskId); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e02e5bef688c..b6035519fdba 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8302,7 +8302,6 @@ public class WindowManagerService extends IWindowManager.Stub ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET); - Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget(); imeTarget = controlTarget.getWindow(); // If InsetsControlTarget doesn't have a window, it's using remoteControlTarget diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 90e7bd7b99e6..99c47360418b 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -39,6 +39,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOO import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION; import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED; import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS; import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN; @@ -1627,6 +1628,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } break; } + case OP_TYPE_SET_PINNED: { + final boolean pinned = operation.getBooleanValue(); + taskFragment.setPinned(pinned); + break; + } } return effects; } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 74ca9ad687ea..97f1e19e35b1 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -122,7 +122,6 @@ static struct { jmethodID interceptMotionBeforeQueueingNonInteractive; jmethodID interceptKeyBeforeDispatching; jmethodID dispatchUnhandledKey; - jmethodID onPointerDisplayIdChanged; jmethodID onPointerDownOutsideFocus; jmethodID getVirtualKeyQuietTimeMillis; jmethodID getExcludedDeviceNames; @@ -786,12 +785,6 @@ void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId poin } // release lock mInputManager->getReader().requestRefreshConfiguration( InputReaderConfiguration::Change::DISPLAY_INFO); - - // Notify the system. - JNIEnv* env = jniEnv(); - env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId, - position.x, position.y); - checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged"); } void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState, @@ -2933,9 +2926,6 @@ int register_android_server_InputManager(JNIEnv* env) { "dispatchUnhandledKey", "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;"); - GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged", - "(IFF)V"); - GET_METHOD_ID(gServiceClassInfo.notifyStickyModifierStateChanged, clazz, "notifyStickyModifierStateChanged", "(II)V"); diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 6143f1dd5b1c..610b502f2a07 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -746,6 +746,20 @@ minOccurs="0" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> + <!-- list of supported modes for low power. Each point corresponds to one mode. + Mode format is : first = refreshRate, second = vsyncRate. E.g. : + <lowPowerSupportedModes> + <point> + <first>60</first> // refreshRate + <second>60</second> //vsyncRate + </point> + .... + </lowPowerSupportedModes> + --> + <xs:element type="nonNegativeFloatToFloatMap" name="lowPowerSupportedModes" minOccurs="0"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> </xs:complexType> <xs:complexType name="refreshRateZoneProfiles"> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 45ec8f250efa..203a6d99dba1 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -360,6 +360,7 @@ package com.android.server.display.config { method public final java.math.BigInteger getDefaultRefreshRateInHbmHdr(); method public final java.math.BigInteger getDefaultRefreshRateInHbmSunlight(); method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs(); + method @Nullable public final com.android.server.display.config.NonNegativeFloatToFloatMap getLowPowerSupportedModes(); method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs(); method public final com.android.server.display.config.RefreshRateZoneProfiles getRefreshRateZoneProfiles(); method public final void setDefaultPeakRefreshRate(java.math.BigInteger); @@ -367,6 +368,7 @@ package com.android.server.display.config { method public final void setDefaultRefreshRateInHbmHdr(java.math.BigInteger); method public final void setDefaultRefreshRateInHbmSunlight(java.math.BigInteger); method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig); + method public final void setLowPowerSupportedModes(@Nullable com.android.server.display.config.NonNegativeFloatToFloatMap); method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig); method public final void setRefreshRateZoneProfiles(com.android.server.display.config.RefreshRateZoneProfiles); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index d114337f9c6c..d733762e90e5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -217,7 +217,7 @@ final class DevicePolicyEngine { <V> void setLocalPolicy( @NonNull PolicyDefinition<V> policyDefinition, @NonNull EnforcingAdmin enforcingAdmin, - @Nullable PolicyValue<V> value, + @NonNull PolicyValue<V> value, int userId, boolean skipEnforcePolicy) { Objects.requireNonNull(policyDefinition); @@ -313,6 +313,7 @@ final class DevicePolicyEngine { } updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin); write(); + applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId); } // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values @@ -400,7 +401,7 @@ final class DevicePolicyEngine { * else remove the policy from child. */ private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition, - EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) { + EnforcingAdmin enforcingAdmin, @Nullable PolicyValue<V> value, int userId) { if (policyDefinition.isInheritable()) { Binder.withCleanCallingIdentity(() -> { List<UserInfo> userInfos = mUserManager.getProfiles(userId); @@ -1742,14 +1743,17 @@ final class DevicePolicyEngine { } } - <V> void reapplyAllPoliciesLocked() { + <V> void reapplyAllPoliciesOnBootLocked() { for (PolicyKey policy : mGlobalPolicies.keySet()) { PolicyState<?> policyState = mGlobalPolicies.get(policy); // Policy definition and value will always be of the same type PolicyDefinition<V> policyDefinition = (PolicyDefinition<V>) policyState.getPolicyDefinition(); - PolicyValue<V> policyValue = (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); - enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL); + if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) { + PolicyValue<V> policyValue = + (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); + enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL); + } } for (int i = 0; i < mLocalPolicies.size(); i++) { int userId = mLocalPolicies.keyAt(i); @@ -1758,10 +1762,11 @@ final class DevicePolicyEngine { // Policy definition and value will always be of the same type PolicyDefinition<V> policyDefinition = (PolicyDefinition<V>) policyState.getPolicyDefinition(); - PolicyValue<V> policyValue = - (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); - enforcePolicy(policyDefinition, policyValue, userId); - + if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) { + PolicyValue<V> policyValue = + (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); + enforcePolicy(policyDefinition, policyValue, userId); + } } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2b93d2132a8f..85d2a0df4fdc 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3351,7 +3351,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { break; case SystemService.PHASE_SYSTEM_SERVICES_READY: synchronized (getLockObject()) { - mDevicePolicyEngine.reapplyAllPoliciesLocked(); + mDevicePolicyEngine.reapplyAllPoliciesOnBootLocked(); } break; case SystemService.PHASE_ACTIVITY_MANAGER_READY: @@ -11443,7 +11443,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } setBackwardsCompatibleAppRestrictions( caller, packageName, restrictions, caller.getUserHandle()); - } else if (Flags.dmrhCanSetAppRestriction()) { + } else if (Flags.dmrhSetAppRestrictions()) { final boolean isRoleHolder; if (who != null) { // DO or PO @@ -11484,10 +11484,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { new BundlePolicyValue(restrictions), affectedUserId); } - Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); - changeIntent.setPackage(packageName); - changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId)); } else { mInjector.binderWithCleanCallingIdentity(() -> { mUserManager.setApplicationRestrictions(packageName, restrictions, @@ -12845,7 +12841,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return Bundle.EMPTY; } return policies.get(enforcingAdmin).getValue(); - } else if (Flags.dmrhCanSetAppRestriction()) { + } else if (Flags.dmrhSetAppRestrictions()) { final boolean isRoleHolder; if (who != null) { // Caller is DO or PO. They cannot call this on parent @@ -15770,8 +15766,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), userId); List<Bundle> restrictions = new ArrayList<>(); - for (EnforcingAdmin admin : policies.keySet()) { - restrictions.add(policies.get(admin).getValue()); + for (PolicyValue<Bundle> policyValue: policies.values()) { + Bundle value = policyValue.getValue(); + // Probably not necessary since setApplicationRestrictions only sets non-empty + // Bundle, but just in case. + if (value != null && !value.isEmpty()) { + restrictions.add(value); + } } return mInjector.binderWithCleanCallingIdentity(() -> { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java index 1000bfa5f6c9..cbd28475bc26 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java @@ -52,7 +52,18 @@ class EnterpriseSpecificIdCalculator { EnterpriseSpecificIdCalculator(Context context) { TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class); Preconditions.checkState(telephonyService != null, "Unable to access telephony service"); - mImei = telephonyService.getImei(0); + + String imei; + try { + imei = telephonyService.getImei(0); + } catch (UnsupportedOperationException doesNotSupportGms) { + // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM. + // However that runs the risk of changing a device's existing ESID if on these devices + // telephonyService.getImei() actually returns non-null even when the device does not + // declare FEATURE_TELEPHONY_GSM. + imei = null; + } + mImei = imei; String meid; try { meid = telephonyService.getMeid(0); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 8d980b5031e2..8bec3847d8ca 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -51,6 +51,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; final class PolicyDefinition<V> { @@ -82,6 +83,10 @@ final class PolicyDefinition<V> { // them. private static final int POLICY_FLAG_USER_RESTRICTION_POLICY = 1 << 4; + // Only invoke the policy enforcer callback when the policy value changes, and do not invoke the + // callback in other cases such as device reboots. + private static final int POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED = 1 << 5; + private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>( List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true))); @@ -231,11 +236,11 @@ final class PolicyDefinition<V> { // Don't need to take in a resolution mechanism since its never used, but might // need some refactoring to not always assume a non-null mechanism. new MostRecent<>(), - POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY, - // Application restrictions are now stored and retrieved from DPMS, so no - // enforcing is required, however DPMS calls into UM to set restrictions for - // backwards compatibility. - (Bundle value, Context context, Integer userId, PolicyKey policyKey) -> true, + // Only invoke the enforcement callback during policy change and not other state + POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE + | POLICY_FLAG_NON_COEXISTABLE_POLICY + | POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED, + PolicyEnforcerCallbacks::setApplicationRestrictions, new BundlePolicySerializer()); /** @@ -581,6 +586,10 @@ final class PolicyDefinition<V> { return (mPolicyFlags & POLICY_FLAG_USER_RESTRICTION_POLICY) != 0; } + boolean shouldSkipEnforcementIfNotChanged() { + return (mPolicyFlags & POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED) != 0; + } + @Nullable PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) { return mResolutionMechanism.resolve(adminsPolicy); @@ -610,7 +619,7 @@ final class PolicyDefinition<V> { * {@link Object#equals} implementation. */ private PolicyDefinition( - PolicyKey key, + @NonNull PolicyKey key, ResolutionMechanism<V> resolutionMechanism, QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback, PolicySerializer<V> policySerializer) { @@ -622,11 +631,12 @@ final class PolicyDefinition<V> { * {@link Object#equals} and {@link Object#hashCode()} implementation. */ private PolicyDefinition( - PolicyKey policyKey, + @NonNull PolicyKey policyKey, ResolutionMechanism<V> resolutionMechanism, int policyFlags, QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback, PolicySerializer<V> policySerializer) { + Objects.requireNonNull(policyKey); mPolicyKey = policyKey; mResolutionMechanism = resolutionMechanism; mPolicyFlags = policyFlags; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 09eef451c547..04d277e6e667 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -37,11 +37,13 @@ import android.app.admin.flags.Flags; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Binder; +import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -172,6 +174,29 @@ final class PolicyEnforcerCallbacks { return true; } + + /** + * Application restrictions are stored and retrieved from DPMS, so no enforcing (aka pushing + * it to UMS) is required. Only need to send broadcast to the target user here as we rely on + * the inheritable policy propagation logic in PolicyEngine to apply this policy to multiple + * profiles. The broadcast should only be sent when an application restriction is set, so we + * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback + * when the policy is set, and not during system boot or other situations. + */ + static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId, + PolicyKey policyKey) { + Binder.withCleanCallingIdentity(() -> { + PackagePolicyKey key = (PackagePolicyKey) policyKey; + String packageName = key.getPackageName(); + Objects.requireNonNull(packageName); + Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); + changeIntent.setPackage(packageName); + changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId)); + }); + return true; + } + private static class BlockingCallback { private final CountDownLatch mLatch = new CountDownLatch(1); private final AtomicReference<Boolean> mValue = new AtomicReference<>(); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index 1d225ba09bbd..221a99102daa 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -36,9 +36,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static java.util.Objects.requireNonNull; + import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -72,7 +73,10 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe super.setUp(); mVisibilityApplier = (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier(); - mInputMethodManagerService.setAttachedClientForTesting(mock(ClientState.class)); + synchronized (ImfLock.class) { + mInputMethodManagerService.setAttachedClientForTesting(requireNonNull( + mInputMethodManagerService.getClientStateLocked(mMockInputMethodClient))); + } } @Test diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java new file mode 100644 index 000000000000..50804da5a293 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1; +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2; +import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo; +import static com.android.server.inputmethod.TestUtils.createFakeSubtypes; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import android.os.Parcel; +import android.os.Parcelable; +import android.view.inputmethod.InputMethodInfo; + +import androidx.annotation.NonNull; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Objects; + +public final class InputMethodInfoUtilsTest { + + @Test + public void testMarshalSameObject() { + final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3)); + final byte[] buf = InputMethodInfoUtils.marshal(imi); + + assertArrayEquals("The same value must be returned when called multiple times", + buf, InputMethodInfoUtils.marshal(imi)); + assertArrayEquals("The same value must be returned when called multiple times", + buf, InputMethodInfoUtils.marshal(imi)); + } + + @Test + public void testMarshalDifferentObjects() { + final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(0)); + + assertFalse("Different inputs must yield different byte patterns", Arrays.equals( + InputMethodInfoUtils.marshal(imi1), InputMethodInfoUtils.marshal(imi2))); + } + + @NonNull + private static <T> T readTypedObject(byte[] data, @NonNull Parcelable.Creator<T> creator) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + parcel.unmarshall(data, 0, data.length); + parcel.setDataPosition(0); + return Objects.requireNonNull(parcel.readTypedObject(creator)); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } + + @Test + public void testUnmarshalSameObject() { + final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3)); + final var cloned = readTypedObject(InputMethodInfoUtils.marshal(imi), + InputMethodInfo.CREATOR); + assertEquals(imi.getPackageName(), cloned.getPackageName()); + assertEquals(imi.getSubtypeCount(), cloned.getSubtypeCount()); + } +} diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index cff22654e30c..3b25cb13e66c 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,6 +46,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.view.InputChannel; import android.view.inputmethod.EditorInfo; import android.window.ImeOnBackInvokedDispatcher; @@ -53,6 +55,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.compat.IPlatformCompat; import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.IInputMethodClient; +import com.android.internal.inputmethod.IInputMethodSession; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputBindResult; @@ -104,6 +107,7 @@ public class InputMethodManagerServiceTestBase { @Mock protected UserManagerInternal mMockUserManagerInternal; @Mock protected InputMethodBindingController mMockInputMethodBindingController; @Mock protected IInputMethodClient mMockInputMethodClient; + @Mock protected IInputMethodSession mMockInputMethodSession; @Mock protected IBinder mWindowToken; @Mock protected IRemoteInputConnection mMockRemoteInputConnection; @Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection; @@ -123,6 +127,7 @@ public class InputMethodManagerServiceTestBase { protected IInputMethodInvoker mMockInputMethodInvoker; protected InputMethodManagerService mInputMethodManagerService; protected ServiceThread mServiceThread; + protected ServiceThread mPackageMonitorThread; protected boolean mIsLargeScreen; private InputManagerGlobal.TestSession mInputManagerGlobalSession; @@ -218,10 +223,17 @@ public class InputMethodManagerServiceTestBase { mServiceThread = new ServiceThread( - "TestServiceThread", - Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */ - false); - mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread, + "immstest1", + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); + mPackageMonitorThread = + new ServiceThread( + "immstest2", + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); + mInputMethodManagerService = new InputMethodManagerService(mContext, + InputMethodManagerService.shouldEnableExperimentalConcurrentMultiUserMode(mContext), + mServiceThread, mPackageMonitorThread, unusedUserId -> mMockInputMethodBindingController); spyOn(mInputMethodManagerService); @@ -246,6 +258,7 @@ public class InputMethodManagerServiceTestBase { // Call InputMethodManagerService#addClient() as a preparation to start interacting with it. mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0); + createSessionForClient(mMockInputMethodClient); } @After @@ -254,6 +267,10 @@ public class InputMethodManagerServiceTestBase { mInputMethodManagerService.mInputMethodDeviceConfigs.destroy(); } + if (mPackageMonitorThread != null) { + mPackageMonitorThread.quitSafely(); + } + if (mServiceThread != null) { mServiceThread.quitSafely(); } @@ -295,4 +312,13 @@ public class InputMethodManagerServiceTestBase { .hideSoftInput(any() /* hideInputToken */, notNull() /* statsToken */, anyInt() /* flags */, any() /* resultReceiver */); } + + protected void createSessionForClient(IInputMethodClient client) { + synchronized (ImfLock.class) { + ClientState cs = mInputMethodManagerService.getClientStateLocked(client); + cs.mCurSession = new InputMethodManagerService.SessionState(cs, + mMockInputMethodInvoker, mMockInputMethodSession, mock( + InputChannel.class)); + } + } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java new file mode 100644 index 000000000000..be7042177a35 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1; +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2; +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID3; +import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo; +import static com.android.server.inputmethod.TestUtils.createFakeSubtypes; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.util.ArrayMap; +import android.view.inputmethod.InputMethodInfo; + +import androidx.annotation.NonNull; + +import org.junit.Test; + +public final class InputMethodMapTest { + + @NonNull + private static InputMethodMap toMap(InputMethodInfo... list) { + final ArrayMap<String, InputMethodInfo> map = new ArrayMap<>(); + for (var imi : list) { + map.put(imi.getId(), imi); + } + return InputMethodMap.of(map); + } + + @Test + public void testAreSameSameObject() { + final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3)); + final var map = toMap(imi1, imi2); + assertTrue("Must return true for the same instance", + InputMethodMap.areSame(map, map)); + } + + @Test + public void testAreSameEquivalentObject() { + final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3)); + assertTrue("Must return true for the equivalent instances", + InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1, imi2))); + + assertTrue("Must return true for the equivalent instances", + InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi2, imi1))); + } + + @Test + public void testAreSameDifferentKeys() { + final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3)); + final var imi3 = createFakeInputMethodInfo(TEST_IME_ID3, createFakeSubtypes(3)); + assertFalse("Must return false if keys are different", + InputMethodMap.areSame(toMap(imi1), toMap(imi1, imi2))); + assertFalse("Must return false if keys are different", + InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1))); + assertFalse("Must return false if keys are different", + InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1, imi3))); + } + + @Test + public void testAreSameDifferentValues() { + final var imi1_without_subtypes = + createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0)); + final var imi1_with_subtypes = + createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3)); + assertFalse("Must return false if values are different", + InputMethodMap.areSame(toMap(imi1_without_subtypes), toMap(imi1_with_subtypes))); + assertFalse("Must return false if values are different", + InputMethodMap.areSame( + toMap(imi1_without_subtypes, imi2), + toMap(imi1_with_subtypes, imi2))); + } +} diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java new file mode 100644 index 000000000000..c51ff87fdae5 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Objects; + +public final class TestUtils { + /** + * {@link ComponentName} for fake {@link InputMethodInfo}. + */ + @NonNull + public static final ComponentName TEST_IME_ID1 = Objects.requireNonNull( + ComponentName.unflattenFromString("com.android.test.testime1/.InputMethod")); + + /** + * {@link ComponentName} for fake {@link InputMethodInfo}. + */ + @NonNull + public static final ComponentName TEST_IME_ID2 = Objects.requireNonNull( + ComponentName.unflattenFromString("com.android.test.testime2/.InputMethod")); + + /** + * {@link ComponentName} for fake {@link InputMethodInfo}. + */ + @NonNull + public static final ComponentName TEST_IME_ID3 = Objects.requireNonNull( + ComponentName.unflattenFromString("com.android.test.testime3/.InputMethod")); + + /** + * Creates a list of fake {@link InputMethodSubtype} for unit testing for the given number. + * + * @param count The number of fake {@link InputMethodSubtype} objects + * @return The list of fake {@link InputMethodSubtype} objects + */ + @NonNull + public static ArrayList<InputMethodSubtype> createFakeSubtypes(int count) { + final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(count); + for (int i = 0; i < count; ++i) { + subtypes.add( + new InputMethodSubtype.InputMethodSubtypeBuilder() + .setSubtypeId(i + 0x100) + .setLanguageTag("en-US") + .setSubtypeNameOverride("TestSubtype" + i) + .build()); + } + return subtypes; + } + + /** + * Creates a fake {@link InputMethodInfo} for unit testing. + * + * @param componentName {@link ComponentName} of the fake {@link InputMethodInfo} + * @param subtypes A list of (fake) {@link InputMethodSubtype} + * @return a fake {@link InputMethodInfo} object + */ + @NonNull + public static InputMethodInfo createFakeInputMethodInfo( + @NonNull ComponentName componentName, @NonNull ArrayList<InputMethodSubtype> subtypes) { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = componentName.getPackageName(); + ai.enabled = true; + + final ServiceInfo si = new ServiceInfo(); + si.applicationInfo = ai; + si.enabled = true; + si.packageName = componentName.getPackageName(); + si.name = componentName.getClassName(); + si.exported = true; + si.nonLocalizedLabel = "Fake Label"; + + final ResolveInfo ri = new ResolveInfo(); + ri.serviceInfo = si; + + return new InputMethodInfo(ri, false /* isAuxIme */, null /* settingsActivity */, + subtypes, 0 /* isDefaultResId */, false /* forceDefault */); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index a0a611ff4eb1..46d08b0ce018 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -21,7 +21,6 @@ import static com.android.internal.display.BrightnessSynchronizer.brightnessIntT import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; import static com.android.server.display.config.SensorData.TEMPERATURE_TYPE_SKIN; -import static com.android.server.display.config.SensorData.SupportedMode; import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat; import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat; @@ -58,6 +57,7 @@ import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.HysteresisLevels; import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; import com.android.server.display.config.RefreshRateData; +import com.android.server.display.config.SupportedModeData; import com.android.server.display.config.ThermalStatus; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.feature.flags.Flags; @@ -613,7 +613,7 @@ public final class DisplayDeviceConfigTest { assertEquals(mDisplayDeviceConfig.getProximitySensor().minRefreshRate, 60, SMALL_DELTA); assertEquals(mDisplayDeviceConfig.getProximitySensor().maxRefreshRate, 90, SMALL_DELTA); assertThat(mDisplayDeviceConfig.getProximitySensor().supportedModes).hasSize(2); - SupportedMode mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0); + SupportedModeData mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0); assertEquals(mode.refreshRate, 60, SMALL_DELTA); assertEquals(mode.vsyncRate, 65, SMALL_DELTA); mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(1); @@ -933,6 +933,21 @@ public final class DisplayDeviceConfigTest { assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA); } + @Test + public void testLowPowerSupportedModesFromConfigFile() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + RefreshRateData refreshRateData = mDisplayDeviceConfig.getRefreshRateData(); + assertNotNull(refreshRateData); + assertThat(refreshRateData.lowPowerSupportedModes).hasSize(2); + SupportedModeData supportedModeData = refreshRateData.lowPowerSupportedModes.get(0); + assertThat(supportedModeData.refreshRate).isEqualTo(60); + assertThat(supportedModeData.vsyncRate).isEqualTo(60); + supportedModeData = refreshRateData.lowPowerSupportedModes.get(1); + assertThat(supportedModeData.refreshRate).isEqualTo(60); + assertThat(supportedModeData.vsyncRate).isEqualTo(120); + } + private String getValidLuxThrottling() { return "<luxThrottling>\n" + " <brightnessLimitMap>\n" @@ -1089,6 +1104,19 @@ public final class DisplayDeviceConfigTest { + "</proxSensor>\n"; } + private String getLowPowerConfig() { + return "<lowPowerSupportedModes>\n" + + " <point>\n" + + " <first>60</first>\n" + + " <second>60</second>\n" + + " </point>\n" + + " <point>\n" + + " <first>60</first>\n" + + " <second>120</second>\n" + + " </point>\n" + + "</lowPowerSupportedModes>\n"; + } + private String getHdrBrightnessConfig() { return "<hdrBrightnessConfig>\n" + " <brightnessMap>\n" @@ -1620,6 +1648,7 @@ public final class DisplayDeviceConfigTest { + "</displayBrightnessPoint>\n" + "</blockingZoneThreshold>\n" + "</higherBlockingZoneConfigs>\n" + + getLowPowerConfig() + "</refreshRate>\n" + "<screenOffBrightnessSensorValueToLux>\n" + "<item>-1</item>\n" diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java index a785300e98a3..27f87aae35bb 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java @@ -161,4 +161,20 @@ public class DisplayTransformManagerTest { .isEqualTo(Integer.toString(testPropertyValue)); } + @Test + public void daltonizer_defaultValues() { + synchronized (mDtm.mDaltonizerModeLock) { + assertThat(mDtm.mDaltonizerMode).isEqualTo(-1); + assertThat(mDtm.mDaltonizerLevel).isEqualTo(-1); + } + } + + @Test + public void setDaltonizerMode_newValues_valuesUpdated() { + mDtm.setDaltonizerMode(0, 0); + synchronized (mDtm.mDaltonizerModeLock) { + assertThat(mDtm.mDaltonizerMode).isEqualTo(0); + assertThat(mDtm.mDaltonizerLevel).isEqualTo(0); + } + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index cd1e9e85afb5..714b423fae70 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -131,7 +131,8 @@ public class DisplayModeDirectorTest { /* defaultRefreshRate= */ 0, /* defaultPeakRefreshRate= */ 0, /* defaultRefreshRateInHbmHdr= */ 0, - /* defaultRefreshRateInHbmSunlight= */ 0); + /* defaultRefreshRateInHbmSunlight= */ 0, + /* lowPowerSupportedModes =*/ List.of()); public static Collection<Object[]> getAppRequestedSizeTestCases() { var appRequestedSizeTestCases = Arrays.asList(new Object[][] { @@ -157,7 +158,7 @@ public class DisplayModeDirectorTest { APP_MODE_HIGH_90.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), @@ -169,7 +170,7 @@ public class DisplayModeDirectorTest { APP_MODE_65.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), @@ -181,7 +182,7 @@ public class DisplayModeDirectorTest { APP_MODE_65.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSizeAndPhysicalRefreshRatesRange( 0, 0, LIMIT_MODE_70.getPhysicalWidth(), @@ -197,7 +198,7 @@ public class DisplayModeDirectorTest { APP_MODE_65.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSizeAndPhysicalRefreshRatesRange( 0, 0, LIMIT_MODE_70.getPhysicalWidth(), @@ -213,7 +214,7 @@ public class DisplayModeDirectorTest { APP_MODE_HIGH_90.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSizeAndPhysicalRefreshRatesRange( 0, 0, LIMIT_MODE_70.getPhysicalWidth(), @@ -229,7 +230,7 @@ public class DisplayModeDirectorTest { APP_MODE_HIGH_90.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSizeAndPhysicalRefreshRatesRange( 0, 0, LIMIT_MODE_70.getPhysicalWidth(), @@ -245,7 +246,7 @@ public class DisplayModeDirectorTest { Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forPhysicalRefreshRates( 0, 64.99f))}}); @@ -598,7 +599,7 @@ public class DisplayModeDirectorTest { < Vote.PRIORITY_APP_REQUEST_SIZE); assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH - > Vote.PRIORITY_LOW_POWER_MODE); + > Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE); Display.Mode[] modes = new Display.Mode[4]; modes[0] = new Display.Mode( @@ -676,9 +677,9 @@ public class DisplayModeDirectorTest { @Test public void testLPMHasHigherPriorityThanUser() { - assertTrue(Vote.PRIORITY_LOW_POWER_MODE + assertTrue(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE > Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); - assertTrue(Vote.PRIORITY_LOW_POWER_MODE + assertTrue(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE > Vote.PRIORITY_APP_REQUEST_SIZE); Display.Mode[] modes = new Display.Mode[4]; @@ -700,7 +701,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), appRequestedMode.getPhysicalHeight())); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(2); @@ -715,7 +716,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), appRequestedMode.getPhysicalHeight())); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(4); @@ -730,7 +731,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), appRequestedMode.getPhysicalHeight())); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(2); @@ -745,7 +746,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), appRequestedMode.getPhysicalHeight())); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(4); @@ -906,7 +907,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, Vote.forRenderFrameRates(60, 60)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse(); @@ -946,7 +947,7 @@ public class DisplayModeDirectorTest { votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(30, 90)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); assertThat(director.getModeSwitchingType()) @@ -987,7 +988,7 @@ public class DisplayModeDirectorTest { votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(30, 90)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); assertThat(director.getModeSwitchingType()) @@ -1029,7 +1030,7 @@ public class DisplayModeDirectorTest { votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(30, 90)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); assertThat(director.getModeSwitchingType()) @@ -1900,7 +1901,7 @@ public class DisplayModeDirectorTest { director.start(createMockSensorManager()); SparseArray<Vote> votes = new SparseArray<>(); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 50f)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 50f)); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID_2, votes); @@ -2298,7 +2299,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY)); votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(50); @@ -2311,7 +2312,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY)); votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 90)); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(80); assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(80); @@ -2323,7 +2324,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY)); votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 90)); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90); @@ -2343,7 +2344,7 @@ public class DisplayModeDirectorTest { votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(70)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); @@ -2360,7 +2361,7 @@ public class DisplayModeDirectorTest { votes.clear(); votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(55)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); @@ -2374,7 +2375,7 @@ public class DisplayModeDirectorTest { Vote.forRenderFrameRates(0, 52)); votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(55)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); @@ -2392,7 +2393,7 @@ public class DisplayModeDirectorTest { Vote.forRenderFrameRates(0, 58)); votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(55)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); @@ -2521,7 +2522,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(120, 120)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(120); @@ -2542,7 +2543,7 @@ public class DisplayModeDirectorTest { SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID, votes); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, Vote.forRenderFrameRates(0, 30)); director.injectVotesByDisplay(votesByDisplay); @@ -3168,7 +3169,8 @@ public class DisplayModeDirectorTest { /* defaultRefreshRate= */ 60, /* defaultPeakRefreshRate= */ 65, /* defaultRefreshRateInHbmHdr= */ 65, - /* defaultRefreshRateInHbmSunlight= */ 75); + /* defaultRefreshRateInHbmSunlight= */ 75, + /* lowPowerSupportedModes= */ List.of()); when(displayDeviceConfig.getRefreshRateData()).thenReturn(refreshRateData); when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50); when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55); @@ -3390,9 +3392,10 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> displayListenerCaptor = ArgumentCaptor.forClass(DisplayListener.class); - verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + verify(mInjector, atLeastOnce()).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class)); - DisplayListener displayListener = displayListenerCaptor.getValue(); + // DisplayObserver should register first + DisplayListener displayListener = displayListenerCaptor.getAllValues().get(0); float refreshRate = 60; mInjector.mDisplayInfo.layoutLimitedRefreshRate = @@ -3417,9 +3420,10 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> displayListenerCaptor = ArgumentCaptor.forClass(DisplayListener.class); - verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + verify(mInjector, atLeastOnce()).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class)); - DisplayListener displayListener = displayListenerCaptor.getValue(); + // DisplayObserver should register first + DisplayListener displayListener = displayListenerCaptor.getAllValues().get(0); mInjector.mDisplayInfo.layoutLimitedRefreshRate = new RefreshRateRange(10, 10); mInjector.mDisplayInfoValid = false; diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java index 2d317af3d85d..ee79d196cfd9 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -407,7 +407,8 @@ public class DisplayObserverTest { assertThat(mObserver).isNull(); mObserver = invocation.getArgument(0); return null; - }).when(mInjector).registerDisplayListener(any(), any()); + }).when(mInjector).registerDisplayListener( + any(DisplayModeDirector.DisplayObserver.class), any()); doAnswer(c -> { DisplayInfo info = c.getArgument(1); diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt index 4d910cefdb79..e431c8c3555c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt @@ -27,8 +27,11 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest import com.android.internal.util.test.FakeSettingsProvider import com.android.server.display.DisplayDeviceConfig +import com.android.server.display.config.RefreshRateData +import com.android.server.display.config.SupportedModeData import com.android.server.display.feature.DisplayManagerFlags import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider +import com.android.server.display.mode.SupportedRefreshRatesVote.RefreshRates import com.android.server.testutils.TestHandler import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage @@ -69,6 +72,13 @@ private val RANGES_MIN90_90TO120 = RefreshRateRanges(RANGE_90_INF, RANGE_90_120) private val RANGES_MIN60_60TO90 = RefreshRateRanges(RANGE_60_INF, RANGE_60_90) private val RANGES_MIN90_90TO90 = RefreshRateRanges(RANGE_90_INF, RANGE_90_90) +private val LOW_POWER_GLOBAL_VOTE = Vote.forRenderFrameRates(0f, 60f) +private val LOW_POWER_REFRESH_RATE_DATA = createRefreshRateData( + lowPowerSupportedModes = listOf(SupportedModeData(60f, 60f), SupportedModeData(60f, 240f))) +private val LOW_POWER_EMPTY_REFRESH_RATE_DATA = createRefreshRateData() +private val EXPECTED_SUPPORTED_MODES_VOTE = SupportedRefreshRatesVote( + listOf(RefreshRates(60f, 60f), RefreshRates(60f, 240f))) + @SmallTest @RunWith(TestParameterInjector::class) class SettingsObserverTest { @@ -103,7 +113,7 @@ class SettingsObserverTest { val displayModeDirector = DisplayModeDirector( spyContext, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider) val ddcByDisplay = SparseArray<DisplayDeviceConfig>() - whenever(mockDeviceConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported) + whenever(mockDeviceConfig.refreshRateData).thenReturn(testCase.refreshRateData) ddcByDisplay.put(Display.DEFAULT_DISPLAY, mockDeviceConfig) displayModeDirector.injectDisplayDeviceConfigByDisplay(ddcByDisplay) val settingsObserver = displayModeDirector.SettingsObserver( @@ -113,27 +123,30 @@ class SettingsObserverTest { false, Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE), 1) assertThat(displayModeDirector.getVote(VotesStorage.GLOBAL_ID, - Vote.PRIORITY_LOW_POWER_MODE)).isEqualTo(testCase.expectedVote) + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE)).isEqualTo(testCase.globalVote) + assertThat(displayModeDirector.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_LOW_POWER_MODE_MODES)).isEqualTo(testCase.displayVote) } enum class LowPowerTestCase( - val vrrSupported: Boolean, + val refreshRateData: RefreshRateData, val vsyncLowPowerVoteEnabled: Boolean, val lowPowerModeEnabled: Boolean, - internal val expectedVote: Vote? + internal val globalVote: Vote?, + internal val displayVote: Vote? ) { - ALL_ENABLED(true, true, true, - SupportedRefreshRatesVote(listOf( - SupportedRefreshRatesVote.RefreshRates(60f, 240f), - SupportedRefreshRatesVote.RefreshRates(60f, 60f) - ))), - LOW_POWER_OFF(true, true, false, null), - DVRR_NOT_SUPPORTED_LOW_POWER_ON(false, true, true, - RefreshRateVote.RenderVote(0f, 60f)), - DVRR_NOT_SUPPORTED_LOW_POWER_OFF(false, true, false, null), - VSYNC_VOTE_DISABLED_SUPPORTED_LOW_POWER_ON(true, false, true, - RefreshRateVote.RenderVote(0f, 60f)), - VSYNC_VOTE_DISABLED_LOW_POWER_OFF(true, false, false, null), + ALL_ENABLED(LOW_POWER_REFRESH_RATE_DATA, true, true, + LOW_POWER_GLOBAL_VOTE, EXPECTED_SUPPORTED_MODES_VOTE), + LOW_POWER_OFF(LOW_POWER_REFRESH_RATE_DATA, true, false, + null, null), + EMPTY_REFRESH_LOW_POWER_ON(LOW_POWER_EMPTY_REFRESH_RATE_DATA, true, true, + LOW_POWER_GLOBAL_VOTE, null), + EMPTY_REFRESH__LOW_POWER_OFF(LOW_POWER_EMPTY_REFRESH_RATE_DATA, true, false, + null, null), + VSYNC_VOTE_DISABLED_SUPPORTED_LOW_POWER_ON(LOW_POWER_REFRESH_RATE_DATA, false, true, + LOW_POWER_GLOBAL_VOTE, null), + VSYNC_VOTE_DISABLED_LOW_POWER_OFF(LOW_POWER_REFRESH_RATE_DATA, false, false, + null, null), } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt index 6b90bde188c5..1206e30b9e88 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt @@ -16,6 +16,9 @@ package com.android.server.display.mode +import com.android.server.display.config.RefreshRateData +import com.android.server.display.config.SupportedModeData + internal fun createVotesSummary( isDisplayResolutionRangeVotingEnabled: Boolean = true, supportedModesVoteEnabled: Boolean = true, @@ -24,4 +27,16 @@ internal fun createVotesSummary( ): VoteSummary { return VoteSummary(isDisplayResolutionRangeVotingEnabled, supportedModesVoteEnabled, loggingEnabled, supportsFrameRateOverride) -}
\ No newline at end of file +} + +fun createRefreshRateData( + defaultRefreshRate: Int = 60, + defaultPeakRefreshRate: Int = 60, + defaultRefreshRateInHbmHdr: Int = 60, + defaultRefreshRateInHbmSunlight: Int = 60, + lowPowerSupportedModes: List<SupportedModeData> = emptyList() +): RefreshRateData { + return RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate, + defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight, + lowPowerSupportedModes) +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java index d29bf1abd7a3..3635e9a749e2 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java @@ -19,6 +19,7 @@ package com.android.server.power.stats; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.content.Context; import android.os.BatteryManager; @@ -49,9 +50,9 @@ public class BatteryStatsResetTest { private static final int BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL = 100; private MockClock mMockClock; + private BatteryStatsImpl.BatteryStatsConfig mConfig; private MockBatteryStatsImpl mBatteryStatsImpl; - /** * Battery status. Must be one of the following: * {@link BatteryManager#BATTERY_STATUS_UNKNOWN} @@ -91,8 +92,9 @@ public class BatteryStatsResetTest { @Before public void setUp() throws IOException { + mConfig = mock(BatteryStatsImpl.BatteryStatsConfig.class); mMockClock = new MockClock(); - mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock, + mBatteryStatsImpl = new MockBatteryStatsImpl(mConfig, mMockClock, Files.createTempDirectory("BatteryStatsResetTest").toFile()); mBatteryStatsImpl.onSystemReady(mock(Context.class)); @@ -110,10 +112,7 @@ public class BatteryStatsResetTest { @Test public void testResetOnUnplug_highBatteryLevel() { - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugHighBatteryLevel(true) - .build()); + when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(true); long expectedResetTimeUs = 0; @@ -137,10 +136,7 @@ public class BatteryStatsResetTest { assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs); // disable high battery level reset on unplug. - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugHighBatteryLevel(false) - .build()); + when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false); dischargeToLevel(60); @@ -153,10 +149,7 @@ public class BatteryStatsResetTest { @Test public void testResetOnUnplug_significantCharge() { - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugAfterSignificantCharge(true) - .build()); + when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(true); long expectedResetTimeUs = 0; unplugBattery(); @@ -186,10 +179,7 @@ public class BatteryStatsResetTest { assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs); // disable reset on unplug after significant charge. - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugAfterSignificantCharge(false) - .build()); + when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false); // Battery level dropped below 20%. dischargeToLevel(15); @@ -256,11 +246,9 @@ public class BatteryStatsResetTest { @Test public void testResetWhilePluggedIn_longPlugIn() { // disable high battery level reset on unplug. - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugHighBatteryLevel(false) - .setResetOnUnplugAfterSignificantCharge(false) - .build()); + when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false); + when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false); + long expectedResetTimeUs = 0; plugBattery(BatteryManager.BATTERY_PLUGGED_USB); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 2d7cb2245c0a..6edfedee9e5b 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -98,10 +98,12 @@ public class BatteryUsageStatsRule implements TestRule { mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000}); mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000}); mBatteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU, - 10000) - .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, - 10000); + .setPowerStatsThrottlePeriodMillis( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_CPU), 10000) + .setPowerStatsThrottlePeriodMillis( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO), 10000); } private void initBatteryStats() { @@ -290,7 +292,8 @@ public class BatteryUsageStatsRule implements TestRule { public BatteryUsageStatsRule setPowerStatsThrottlePeriodMillis(int powerComponent, long throttleMs) { - mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(powerComponent, throttleMs); + mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis( + BatteryConsumer.powerComponentIdToString(powerComponent), throttleMs); return this; } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java index 4e3e80f8ff10..d1105a4a9077 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java @@ -32,6 +32,7 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.platform.test.ravenwood.RavenwoodRule; +import android.util.IndentingPrintWriter; import android.util.SparseArray; import androidx.test.filters.SmallTest; @@ -51,6 +52,7 @@ import org.mockito.MockitoAnnotations; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.StringWriter; import java.util.function.IntSupplier; @RunWith(AndroidJUnit4.class) @@ -127,6 +129,11 @@ public class CpuPowerStatsCollectorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public int getDefaultCpuPowerBrackets() { return mDefaultCpuPowerBrackets; } @@ -363,6 +370,36 @@ public class CpuPowerStatsCollectorTest { .isEqualTo(528); } + @Test + public void dump() { + mockCpuScalingPolicies(1); + mockPowerProfile(); + mockEnergyConsumers(); + + CpuPowerStatsCollector collector = createCollector(8, 0); + collector.collectStats(); // Establish baseline + + mockKernelCpuStats(new long[]{1111, 2222, 3333}, + new SparseArray<>() {{ + put(UID_1, new long[]{100, 200}); + put(UID_2, new long[]{100, 150}); + put(ISOLATED_UID, new long[]{200, 450}); + }}, 0, 1234); + + PowerStats powerStats = collector.collectStats(); + + StringWriter sw = new StringWriter(); + IndentingPrintWriter pw = new IndentingPrintWriter(sw); + powerStats.dump(pw); + pw.flush(); + String dump = sw.toString(); + + assertThat(dump).contains("duration=1234"); + assertThat(dump).contains("steps: [1111, 2222, 3333]"); + assertThat(dump).contains("UID 42: time: [100, 200]"); + assertThat(dump).contains("UID 99: time: [300, 600]"); + } + private void mockCpuScalingPolicies(int clusterCount) { SparseArray<int[]> cpus = new SparseArray<>(); SparseArray<int[]> freqs = new SparseArray<>(); @@ -386,8 +423,8 @@ public class CpuPowerStatsCollectorTest { private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { CpuPowerStatsCollector collector = new CpuPowerStatsCollector( - new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer), - 0); + new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer) + ); collector.addConsumer(stats -> mCollectedStats = stats); collector.setEnabled(true); return collector; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java index 70c40f5052f0..644ae4717eb1 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; +import android.os.UserHandle; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -46,7 +47,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; @@ -92,9 +95,10 @@ public class CpuPowerStatsCollectorValidationTest { long duration = 0; long[] stats = null; - String[] cpuStatsDump = dumpCpuStats(); + List<String> cpuStatsDump = dumpCpuStats(); Pattern durationPattern = Pattern.compile("duration=([0-9]*)"); - Pattern uidPattern = Pattern.compile("UID " + mTestPkgUid + ": \\[([0-9,\\s]*)]"); + Pattern uidPattern = Pattern.compile( + "UID " + UserHandle.formatUid(mTestPkgUid) + ": time: [\\[]?([0-9,\\s]*)[]]?"); for (String line : cpuStatsDump) { Matcher durationMatcher = durationPattern.matcher(line); if (durationMatcher.find()) { @@ -119,15 +123,23 @@ public class CpuPowerStatsCollectorValidationTest { assertThat(total).isAtLeast((long) (WORK_DURATION_MS * 0.8)); } - private String[] dumpCpuStats() throws Exception { + private List<String> dumpCpuStats() throws Exception { + ArrayList<String> cpuStats = new ArrayList<>(); String dump = executeCmdSilent("dumpsys batterystats --sample"); String[] lines = dump.split("\n"); + boolean inCpuSection = false; for (int i = 0; i < lines.length; i++) { - if (lines[i].startsWith("CpuPowerStatsCollector")) { - return Arrays.copyOfRange(lines, i + 1, lines.length); + if (!inCpuSection) { + if (lines[i].startsWith("CpuPowerStatsCollector")) { + inCpuSection = true; + } + } else if (lines[i].startsWith(" ")) { + cpuStats.add(lines[i]); + } else { + break; } } - return new String[0]; + return cpuStats; } private void doSomeWork() throws Exception { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java index f93c4da3d8d0..0275319a40e2 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java @@ -123,6 +123,11 @@ public class MobileRadioPowerStatsCollectorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -337,16 +342,18 @@ public class MobileRadioPowerStatsCollectorTest { pw.flush(); String dump = sw.toString(); assertThat(dump).contains("duration=100"); + assertThat(dump).contains("sleep: 200 idle: 300 scan: 60000 call: 40000 energy: " + + ((64321 - 10000) * 1000 / 3500)); + assertThat(dump).contains("(LTE) rx: 7000 tx: [8000, 9000, 1000, 2000, 3000]"); + assertThat(dump).contains("(NR MMWAVE) rx: 6000 tx: [1000, 2000, 3000, 4000, 5000]"); + assertThat(dump).contains( + "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000"); assertThat(dump).contains( - "stats=[200, 300, 60000, 40000, " + ((64321 - 10000) * 1000 / 3500) + ", 0, 0, 0]"); - assertThat(dump).contains("state LTE: [7000, 8000, 9000, 1000, 2000, 3000]"); - assertThat(dump).contains("state NR MMWAVE: [6000, 1000, 2000, 3000, 4000, 5000]"); - assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 0]"); - assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 0]"); + "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000"); } private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable { - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); collector.setEnabled(true); when(mConsumedEnergyRetriever.getEnergyConsumerIds( diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java index 4ac7ad8d07ff..29ef3b6551a6 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java @@ -114,6 +114,11 @@ public class MobileRadioPowerStatsProcessorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -186,7 +191,7 @@ public class MobileRadioPowerStatsProcessorTest { aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty ModemActivityInfo. @@ -305,7 +310,100 @@ public class MobileRadioPowerStatsProcessorTest { } @Test - public void measuredEnergyModel() { + public void energyConsumerModel() { + PowerComponentAggregatedPowerStats aggregatedStats = + prepareAggregatedStats_energyConsumerModel(); + + MobileRadioPowerStatsLayout statsLayout = + new MobileRadioPowerStatsLayout( + aggregatedStats.getPowerStatsDescriptor()); + + // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh + double totalPower = 0; + long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength]; + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.671837); + totalPower += statsLayout.getDevicePowerEstimate(deviceStats); + assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.022494); + totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); + + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(2.01596); + totalPower += statsLayout.getDevicePowerEstimate(deviceStats); + assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.067484); + totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); + + // These estimates are supposed to add up to the measured energy, 2.77778 mAh + assertThat(totalPower).isWithin(PRECISION).of(2.77778); + + double uidPower1 = 0; + long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength]; + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.198236); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.198236); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.396473); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + double uidPower2 = 0; + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.066078); + uidPower2 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.198236); + uidPower2 += statsLayout.getUidPowerEstimate(uidStats); + + // Total power attributed to apps is significantly less than the grand total, + // because we only attribute TX/RX to apps but not maintaining a connection with the cell. + assertThat(uidPower1 + uidPower2) + .isWithin(PRECISION).of(1.057259); + + // 3/4 of total packets were sent by APP_UID so 75% of total RX/TX power is attributed to it + assertThat(uidPower1 / (uidPower1 + uidPower2)) + .isWithin(PRECISION).of(0.75); + } + + @Test + public void test_toString() { + PowerComponentAggregatedPowerStats stats = prepareAggregatedStats_energyConsumerModel(); + String string = stats.toString(); + assertThat(string).contains("(pwr-other scr-on)" + + " sleep: 500 idle: 750 scan: 1388 call: 50 energy: 2500000 power: 0.672"); + assertThat(string).contains("(pwr-other scr-other)" + + " sleep: 1500 idle: 2250 scan: 4166 call: 150 energy: 7500000 power: 2.02"); + assertThat(string).contains("(pwr-other scr-on other)" + + " rx: 150 tx: [25, 50, 75, 100, 125]"); + assertThat(string).contains("(pwr-other scr-other other)" + + " rx: 450 tx: [75, 150, 225, 300, 375]"); + assertThat(string).contains("(pwr-other scr-on fg)" + + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198"); + assertThat(string).contains("(pwr-other scr-other bg)" + + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198"); + assertThat(string).contains("(pwr-other scr-other fgs)" + + " rx-pkts: 750 rx-B: 5000 tx-pkts: 150 tx-B: 10000 power: 0.396"); + } + + private PowerComponentAggregatedPowerStats prepareAggregatedStats_energyConsumerModel() { // PowerStats hardware is available when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO)) .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID}); @@ -332,7 +430,7 @@ public class MobileRadioPowerStatsProcessorTest { aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty ModemActivityInfo. @@ -378,74 +476,7 @@ public class MobileRadioPowerStatsProcessorTest { aggregatedStats.addPowerStats(powerStats, 10_000); processor.finish(aggregatedStats); - - MobileRadioPowerStatsLayout statsLayout = - new MobileRadioPowerStatsLayout( - aggregatedStats.getPowerStatsDescriptor()); - - // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh - double totalPower = 0; - long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength]; - aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); - assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) - .isWithin(PRECISION).of(0.671837); - totalPower += statsLayout.getDevicePowerEstimate(deviceStats); - assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) - .isWithin(PRECISION).of(0.022494); - totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); - - aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); - assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) - .isWithin(PRECISION).of(2.01596); - totalPower += statsLayout.getDevicePowerEstimate(deviceStats); - assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) - .isWithin(PRECISION).of(0.067484); - totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); - - // These estimates are supposed to add up to the measured energy, 2.77778 mAh - assertThat(totalPower).isWithin(PRECISION).of(2.77778); - - double uidPower1 = 0; - long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength]; - aggregatedStats.getUidStats(uidStats, APP_UID, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.198236); - uidPower1 += statsLayout.getUidPowerEstimate(uidStats); - - aggregatedStats.getUidStats(uidStats, APP_UID, - states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.198236); - uidPower1 += statsLayout.getUidPowerEstimate(uidStats); - - aggregatedStats.getUidStats(uidStats, APP_UID, - states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.396473); - uidPower1 += statsLayout.getUidPowerEstimate(uidStats); - - double uidPower2 = 0; - aggregatedStats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.066078); - uidPower2 += statsLayout.getUidPowerEstimate(uidStats); - - aggregatedStats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.198236); - uidPower2 += statsLayout.getUidPowerEstimate(uidStats); - - // Total power attributed to apps is significantly less than the grand total, - // because we only attribute TX/RX to apps but not maintaining a connection with the cell. - assertThat(uidPower1 + uidPower2) - .isWithin(PRECISION).of(1.057259); - - // 3/4 of total packets were sent by APP_UID so 75% of total RX/TX power is attributed to it - assertThat(uidPower1 / (uidPower1 + uidPower2)) - .isWithin(PRECISION).of(0.75); + return aggregatedStats; } private int[] states(int... states) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 1d48975c086e..2c03f9d1a9aa 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -72,6 +72,11 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { this(DEFAULT_CONFIG, clock, historyDirectory, handler, new PowerStatsUidResolver()); } + MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory) { + this(config, clock, historyDirectory, new Handler(Looper.getMainLooper()), + new PowerStatsUidResolver()); + } + MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory, Handler handler, PowerStatsUidResolver powerStatsUidResolver) { super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler, @@ -137,13 +142,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS; } - public MockBatteryStatsImpl setBatteryStatsConfig(BatteryStatsConfig config) { - synchronized (this) { - mBatteryStatsConfig = config; - } - return this; - } - public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) { mNetworkStats = networkStats; return this; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java index 1b045c532759..ae258cd3c234 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java @@ -29,8 +29,6 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.PrintWriter; -import java.io.StringWriter; import java.util.Arrays; @RunWith(AndroidJUnit4.class) @@ -165,7 +163,7 @@ public class MultiStateStatsTest { } @Test - public void dump() { + public void test_toString() { MultiStateStats.Factory factory = makeFactory(true, true, false); MultiStateStats multiStateStats = factory.create(); multiStateStats.setState(0 /* batteryState */, 0 /* off */, 1000); @@ -175,13 +173,10 @@ public class MultiStateStatsTest { multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_BACKGROUND, 3000); multiStateStats.increment(new long[]{100, 200}, 5000); - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw, true); - multiStateStats.dump(pw, Arrays::toString); - assertThat(sw.toString()).isEqualTo( - "plugged-in fg [25, 50]\n" - + "on-battery fg [25, 50]\n" - + "on-battery bg [50, 100]\n" + assertThat(multiStateStats.toString()).isEqualTo( + "(plugged-in fg) [25, 50]\n" + + "(on-battery fg) [25, 50]\n" + + "(on-battery bg) [50, 100]" ); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java index dadcf3f3871e..69d655bc35c0 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java @@ -98,6 +98,11 @@ public class PhoneCallPowerStatsProcessorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -175,7 +180,7 @@ public class PhoneCallPowerStatsProcessorTest { aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0); aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0); - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty ModemActivityInfo. diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java index 8b1d423abd21..a280cfe176a3 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java @@ -138,6 +138,11 @@ public class WifiPowerStatsCollectorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -304,16 +309,20 @@ public class WifiPowerStatsCollectorTest { String dump = sw.toString(); assertThat(dump).contains("duration=7500"); assertThat(dump).contains( - "stats=[6000, 1000, 300, 200, 634, 945, " + ((64321 - 10000) * 1000 / 3500) - + ", 0, 0]"); - assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 400, 600, 0]"); - assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 234, 345, 0]"); + "rx: 6000 tx: 1000 idle: 300 scan: 200 basic-scan: 634 batched-scan: 945" + + " energy: " + ((64321 - 10000) * 1000 / 3500)); + assertThat(dump).contains( + "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000" + + " scan: 400 batched-scan: 600"); + assertThat(dump).contains( + "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000" + + " scan: 234 batched-scan: 345"); } private PowerStats collectPowerStats(boolean hasPowerReporting) { when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(hasPowerReporting); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); collector.setEnabled(true); when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI)) diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java index 257a1a67f7b0..3ceaf357150e 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java @@ -142,6 +142,11 @@ public class WifiPowerStatsProcessorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -195,7 +200,7 @@ public class WifiPowerStatsProcessorTest { PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty WifiActivityEnergyInfo. @@ -307,7 +312,7 @@ public class WifiPowerStatsProcessorTest { PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty WifiActivityEnergyInfo. @@ -420,7 +425,7 @@ public class WifiPowerStatsProcessorTest { PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); collector.setEnabled(true); // Establish a baseline diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index 27c522d68119..b56af87ee020 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -25,6 +25,13 @@ value="/data/local/tmp/cts/content/broken_shortcut.xml" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <option name="force-skip-system-props" value="true" /> + <option name="set-global-setting" key="verifier_engprod" value="1" /> + <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" /> + <option name="restore-settings" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index cb4fc753aa4d..ca15aa2a9d52 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -30,7 +30,9 @@ import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULL import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -42,6 +44,7 @@ import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -102,6 +105,7 @@ import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutT import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.accessibility.util.ShortcutUtils; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.content.PackageMonitor; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; import com.android.server.accessibility.magnification.FullScreenMagnificationController; @@ -1620,6 +1624,67 @@ public class AccessibilityManagerServiceTest { .containsExactlyElementsIn(Set.of(daltonizerTile)); } + @Test + @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX) + public void onHandleForceStop_dontDoIt_packageEnabled_returnsTrue() { + setupShortcutTargetServices(); + AccessibilityUserState userState = mA11yms.getCurrentUserState(); + userState.mEnabledServices.addAll( + userState.mInstalledServices.stream().map( + (AccessibilityServiceInfo::getComponentName)).toList()); + String[] packages = userState.mEnabledServices.stream().map( + ComponentName::getPackageName).toList().toArray(new String[0]); + + PackageMonitor monitor = spy(mA11yms.getPackageMonitor()); + when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM); + mA11yms.setPackageMonitor(monitor); + + assertTrue(mA11yms.getPackageMonitor().onHandleForceStop( + new Intent(), + packages, + UserHandle.USER_SYSTEM, + false + )); + } + + @Test + @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX) + public void onHandleForceStop_doIt_packageEnabled_returnsFalse() { + setupShortcutTargetServices(); + AccessibilityUserState userState = mA11yms.getCurrentUserState(); + userState.mEnabledServices.addAll( + userState.mInstalledServices.stream().map( + (AccessibilityServiceInfo::getComponentName)).toList()); + String[] packages = userState.mEnabledServices.stream().map( + ComponentName::getPackageName).toList().toArray(new String[0]); + + PackageMonitor monitor = spy(mA11yms.getPackageMonitor()); + when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM); + mA11yms.setPackageMonitor(monitor); + + assertFalse(mA11yms.getPackageMonitor().onHandleForceStop( + new Intent(), + packages, + UserHandle.USER_SYSTEM, + true + )); + } + + @Test + @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX) + public void onHandleForceStop_dontDoIt_packageNotEnabled_returnsFalse() { + PackageMonitor monitor = spy(mA11yms.getPackageMonitor()); + when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM); + mA11yms.setPackageMonitor(monitor); + + assertFalse(mA11yms.getPackageMonitor().onHandleForceStop( + new Intent(), + new String[]{ "FOO", "BAR"}, + UserHandle.USER_SYSTEM, + false + )); + } + private static AccessibilityServiceInfo mockAccessibilityServiceInfo( ComponentName componentName) { return mockAccessibilityServiceInfo( @@ -1630,7 +1695,7 @@ public class AccessibilityManagerServiceTest { ComponentName componentName, boolean isSystemApp, boolean isAlwaysOnService) { AccessibilityServiceInfo accessibilityServiceInfo = - Mockito.spy(new AccessibilityServiceInfo()); + spy(new AccessibilityServiceInfo()); accessibilityServiceInfo.setComponentName(componentName); ResolveInfo mockResolveInfo = Mockito.mock(ResolveInfo.class); when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index be5e2623ac20..c1ae85252ffc 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -24,8 +24,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.os.Looper; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -72,6 +74,11 @@ public class ActiveSourceActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index 5be3c8e4671c..a5f7bb117e7d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -22,8 +22,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; @@ -84,6 +86,11 @@ public class ArcInitiationActionFromAvrTest { protected Looper getServiceLooper() { return mTestLooper.getLooper(); } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index 7845c307c15f..857ee1aa176f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -22,8 +22,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; @@ -90,6 +92,11 @@ public class ArcTerminationActionFromAvrTest { protected Looper getServiceLooper() { return mTestLooper.getLooper(); } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java index 98789ac96e98..6ace9f14757c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java @@ -33,8 +33,10 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; @@ -140,17 +142,8 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { // do nothing } - /** - * Override displayOsd to prevent it from broadcasting an intent, which - * can trigger a SecurityException. - */ @Override - void displayOsd(int messageId) { - // do nothing - } - - @Override - void displayOsd(int messageId, int extra) { + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { // do nothing } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index 9b65762e48ec..2dd593c8cb29 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -18,6 +18,8 @@ package com.android.server.hdmi; import static org.junit.Assert.assertEquals; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; @@ -103,6 +105,11 @@ public class DetectTvSystemAudioModeSupportActionTest { protected Looper getServiceLooper() { return mTestLooper.getLooper(); } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java index 922706e16a75..e669e7c019d6 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java @@ -25,8 +25,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -90,6 +92,11 @@ public class DevicePowerStatusActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java index 68ef80fa62c9..29d20a64e439 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java @@ -30,7 +30,9 @@ import static com.android.server.hdmi.DeviceSelectActionFromPlayback.STATE_WAIT_ import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -123,6 +125,11 @@ public class DeviceSelectActionFromPlaybackTest { boolean isPowerStandby() { return false; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index 26b448a491f2..d32b75bc57da 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -29,7 +29,9 @@ import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_RE import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -132,6 +134,11 @@ public class DeviceSelectActionFromTvTest { boolean isPowerStandby() { return false; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java index a621055a99eb..c7574bdc3f6c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -98,6 +99,8 @@ public class HdmiCecAtomLoggingTest { audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter(); HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java index 30ce9616d9b5..e1e101fc1724 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java @@ -19,6 +19,7 @@ import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING; import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN; import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -30,6 +31,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -90,6 +92,8 @@ public class HdmiCecAtomLoggingTvTest { audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter(); HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index 0870bac6ef38..7ed596ea8bab 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -48,6 +48,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Binder; @@ -110,6 +111,8 @@ public class HdmiCecControllerTest { doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion(); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper()); mNativeWrapper = new FakeNativeWrapper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 55208972895d..5502de8f46e9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -26,7 +26,9 @@ import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -114,6 +116,11 @@ public class HdmiCecLocalDeviceAudioSystemTest { return defVal; } } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.getHdmiCecConfig().setIntValue( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 28da97c58383..8df7d548e21e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -28,7 +28,9 @@ import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -138,6 +140,11 @@ public class HdmiCecLocalDevicePlaybackTest { boolean canGoToStandby() { return true; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index 3dd83125619a..192be2a4200b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -37,7 +37,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.tv.cec.V1_0.Result; @@ -169,6 +171,11 @@ public class HdmiCecLocalDeviceTest { void wakeUp() { mWakeupMessageReceived = true; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.setIoLooper(mTestLooper.getLooper()); mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 4faeea50c1e1..b50684bb7a25 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -26,6 +26,8 @@ import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE; +import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; +import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON; import static com.google.common.truth.Truth.assertThat; @@ -41,7 +43,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -184,17 +188,8 @@ public class HdmiCecLocalDeviceTvTest { return mEarcBlocksArc; } - /** - * Override displayOsd to prevent it from broadcasting an intent, which - * can trigger a SecurityException. - */ @Override - void displayOsd(int messageId) { - // do nothing - } - - @Override - void displayOsd(int messageId, int extra) { + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { // do nothing } }; @@ -1787,9 +1782,17 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); @@ -1798,6 +1801,10 @@ public class HdmiCecLocalDeviceTvTest { mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); mTestLooper.dispatchAll(); + // Assume there was a retry and the action did not finish earlier. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); } @@ -1807,9 +1814,18 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); @@ -1834,8 +1850,18 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); @@ -1854,8 +1880,16 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder() .setLogicalAddress(ADDR_PLAYBACK_1) .setPhysicalAddress(0x1000) @@ -1869,6 +1903,10 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice); mTestLooper.dispatchAll(); + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); mNativeWrapper.clearResultMessages(); mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null); @@ -1881,6 +1919,41 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); } + @Test + public void onAddressAllocated_sendSourceChangingMessage_noRequestActiveSourceMessage() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + HdmiCecMessage setStreamPathFromTv = + HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2000); + + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Even if the device at the end of this path doesn't answer to this message, TV should not + // continue the RequestActiveSourceAction. + mHdmiControlService.sendCecCommand(setStreamPathFromTv); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + // Assume there was a retry and the action did not finish earlier. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); + } @Test public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() { @@ -1907,7 +1980,12 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE); + // Go to standby to invalidate the active source on the local device s.t. the + // TV will send <Active Source> when it selects its internal source. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); mTestLooper.dispatchAll(); mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); @@ -1937,6 +2015,8 @@ public class HdmiCecLocalDeviceTvTest { public void handleStandby_fromActiveSource_standby() { mPowerManager.setInteractive(true); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000, "HdmiCecLocalDeviceTvTest"); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java index c002067ae9e7..9412ee0d4ac7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java @@ -21,8 +21,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -88,6 +90,11 @@ public class HdmiCecPowerStatusControllerTest { boolean isPowerStandby() { return false; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index e1b66b53ecbe..126a65863f59 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -52,6 +52,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -121,6 +122,8 @@ public class HdmiControlServiceTest { audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); mMyLooper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java index 46418026e540..298ff460c9eb 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java @@ -26,8 +26,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -104,6 +106,11 @@ public class OneTouchPlayActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java index 9f0a44ce008a..1d4a72fc30e2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java @@ -25,8 +25,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -78,6 +80,11 @@ public class PowerStatusMonitorActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java index 043db1eb298d..cafe1e7dc197 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java @@ -21,7 +21,9 @@ import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.os.Looper; @@ -115,6 +117,11 @@ public class RequestSadActionTest { boolean isPowerStandbyOrTransient() { return false; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.setIoLooper(mMyLooper); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java index 061e1f90fa58..864a182e8d0d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java @@ -21,7 +21,9 @@ import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_ import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; @@ -72,6 +74,11 @@ public class ResendCecCommandActionPlaybackTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mIsPowerStandby = false; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java index b25ea2c5078c..06709cdd6ac4 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java @@ -21,7 +21,9 @@ import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_ import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; @@ -75,6 +77,11 @@ public class ResendCecCommandActionTvTest { boolean verifyPhysicalAddresses(HdmiCecMessage message) { return true; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mMyLooper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java index f608c235b0be..5163e29b86f1 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java @@ -29,7 +29,9 @@ import static com.android.server.hdmi.RoutingControlAction.STATE_WAIT_FOR_ROUTIN import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -177,6 +179,11 @@ public class RoutingControlActionTest { protected HdmiCecConfig getHdmiCecConfig() { return hdmiCecConfig; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.setIoLooper(mMyLooper); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java index a73f4aa35cf9..e4297effed92 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java @@ -24,12 +24,14 @@ import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; @@ -87,6 +89,8 @@ public class SetAudioVolumeLevelDiscoveryActionTest { audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); mLooper = mTestLooper.getLooper(); mHdmiControlServiceSpy.setIoLooper(mLooper); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java index 02bed229d181..4dcc6a400c1e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java @@ -25,8 +25,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; import android.os.Looper; @@ -82,17 +84,8 @@ public class SystemAudioAutoInitiationActionTest { // do nothing } - /** - * Override displayOsd to prevent it from broadcasting an intent, which - * can trigger a SecurityException. - */ @Override - void displayOsd(int messageId) { - // do nothing - } - - @Override - void displayOsd(int messageId, int extra) { + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { // do nothing } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index df27e7828385..4aa074b1a52b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -23,7 +23,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; @@ -143,6 +145,11 @@ public class SystemAudioInitiationActionFromAvrTest { protected boolean isStandbyMessageReceived() { return mStandbyMessageReceived; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java index 07fb9fc2f509..570256bf43e6 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java @@ -19,9 +19,16 @@ package com.android.server.net; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT; +import static android.net.ConnectivityManager.FIREWALL_RULE_DENY; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.util.DebugUtils.valueToString; import static org.junit.Assert.assertEquals; @@ -51,7 +58,10 @@ import android.os.PermissionEnforcer; import android.os.Process; import android.os.RemoteException; import android.os.test.FakePermissionEnforcer; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import androidx.test.filters.SmallTest; @@ -62,6 +72,7 @@ import com.android.modules.utils.build.SdkLevel; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -84,6 +95,9 @@ public class NetworkManagementServiceTest { @Mock private IBatteryStats.Stub mBatteryStatsService; @Mock private INetd.Stub mNetdService; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + private static final int TEST_UID = 111; @NonNull @@ -254,6 +268,7 @@ public class NetworkManagementServiceTest { } @Test + @DisableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) public void testMeteredNetworkRestrictions() throws RemoteException { // Make sure the mocked netd method returns true. doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean()); @@ -295,6 +310,69 @@ public class NetworkManagementServiceTest { } @Test + @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) + public void testMeteredNetworkRestrictionsByAdminChain() { + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DENY); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DENY); + assertTrue("Should be true since mobile data usage is restricted by admin chain", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DEFAULT); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DEFAULT); + assertFalse("Should be false since mobile data usage is no longer restricted by admin", + mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test + @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) + public void testMeteredNetworkRestrictionsByUserChain() { + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DENY); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DENY); + assertTrue("Should be true since mobile data usage is restricted by user chain", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DEFAULT); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DEFAULT); + assertFalse("Should be false since mobile data usage is no longer restricted by user", + mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test + @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) + public void testDataSaverRestrictionsWithAllowChain() { + mNMService.setDataSaverModeEnabled(true); + verify(mCm).setDataSaverEnabled(true); + + assertTrue("Should be true since data saver is on and the uid is not allowlisted", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW); + assertFalse("Should be false since data saver is on and the uid is allowlisted", + mNMService.isNetworkRestricted(TEST_UID)); + + // remove uid from allowlist and turn datasaver off again + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, + FIREWALL_RULE_DEFAULT); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, + FIREWALL_RULE_DEFAULT); + mNMService.setDataSaverModeEnabled(false); + verify(mCm).setDataSaverEnabled(false); + + assertFalse("Network should not be restricted when data saver is off", + mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test public void testFirewallChains() { final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>(); // Dozable chain diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 4af20a97a9aa..70a003814036 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -24,6 +24,8 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; @@ -52,6 +54,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest.permission; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.KeyguardManager; @@ -67,6 +70,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.AudioAttributes; @@ -93,6 +97,7 @@ import android.view.accessibility.IAccessibilityManagerClient; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.R; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.config.sysui.TestableFlagResolver; import com.android.internal.logging.InstanceIdSequence; @@ -188,6 +193,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { getContext().addMockSystemService(Vibrator.class, mVibrator); getContext().addMockSystemService(PackageManager.class, mPackageManager); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false); + when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST), + anyString())).thenReturn(PERMISSION_DENIED); when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); @@ -210,6 +217,16 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { verify(mAccessibilityService).addClient(any(IAccessibilityManagerClient.class), anyInt()); assertTrue(mAccessibilityManager.isEnabled()); + // Enable LED pulse setting by default + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE, 1); + + Resources resources = spy(getContext().getResources()); + when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(true); + when(resources.getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(true); + when(getContext().getResources()).thenReturn(resources); + // TODO (b/291907312): remove feature flag // Disable feature flags by default. Tests should enable as needed. mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, @@ -239,7 +256,6 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { mAttentionHelper.setKeyguardManager(mKeyguardManager); mAttentionHelper.setScreenOn(false); mAttentionHelper.setInCallStateOffHook(false); - mAttentionHelper.mNotificationPulseEnabled = true; if (Flags.crossAppPoliteNotifications()) { // Capture BroadcastReceiver for avalanche triggers @@ -611,6 +627,14 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt()); } + private void verifyAttentionLights() { + verify(mLight, times(1)).pulse(); + } + + private void verifyNeverAttentionLights() { + verify(mLight, never()).pulse(); + } + // // Tests // @@ -1524,7 +1548,10 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { @Test public void testLightsLightsOffGlobally() { - mAttentionHelper.mNotificationPulseEnabled = false; + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE, 0); + initAttentionHelper(mTestFlagResolver); + NotificationRecord r = getLightsNotification(); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); verifyNeverLights(); @@ -1533,6 +1560,44 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test + public void testLightsLightsResConfigDisabled() { + Resources resources = spy(getContext().getResources()); + when(resources.getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(false); + when(getContext().getResources()).thenReturn(resources); + initAttentionHelper(mTestFlagResolver); + + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsUseAttentionLight() { + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyAttentionLights(); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsUseAttentionLightDisabled() { + Resources resources = spy(getContext().getResources()); + when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(false); + when(resources.getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(true); + when(getContext().getResources()).thenReturn(resources); + initAttentionHelper(mTestFlagResolver); + + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverAttentionLights(); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test public void testLightsDndIntercepted() { NotificationRecord r = getLightsNotification(); r.setSuppressedVisualEffects(SUPPRESSED_EFFECT_LIGHTS); @@ -2303,6 +2368,72 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test + public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + // Trigger avalanche trigger intent + final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", false); + mAvalancheBroadcastReceiver.onReceive(getContext(), intent); + + NotificationRecord r = getBeepyNotification(); + + // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package + when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST), + eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED); + + // Should beep at 100% volume + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); + } + + @Test + public void testBeepVolume_politeNotif_exemptEmergency() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + // NOTIFICATION_COOLDOWN_ALL setting is enabled + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, 1); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package + when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST), + eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 100% volume + NotificationRecord r2 = getBeepyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + assertNotEquals(-1, r2.getLastAudiblyAlertedMs()); + verifyBeepVolume(1.0f); + + // 2nd update should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + assertNotEquals(-1, r2.getLastAudiblyAlertedMs()); + verifyBeepVolume(1.0f); + + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + } + + @Test public void testBeepVolume_politeNotif_applyPerApp() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 200952c05610..e564ba6eb835 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -99,6 +99,7 @@ import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICAT import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; @@ -15638,4 +15639,82 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) .isTrue(); } + + @Test + public void testClearUIJFromUninstallingPackage() throws Exception { + NotificationRecord r = + generateNotificationRecord(mTestNotificationChannel, 0, mUserId, "bar"); + mService.addNotification(r); + + when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt())) + .thenThrow(PackageManager.NameNotFoundException.class); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false); + + mInternalService.cancelNotification(mPkg, mPkg, mUid, 0, r.getSbn().getTag(), + r.getSbn().getId(), mUserId); + + // no exception + } + + @Test + public void testPostFromMissingPackage_throws() throws Exception { + NotificationRecord r = + generateNotificationRecord(mTestNotificationChannel, 0, mUserId, "bar"); + + when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt())) + .thenThrow(PackageManager.NameNotFoundException.class); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false); + + try { + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(), + r.getSbn().getId(), r.getSbn().getNotification(), + r.getSbn().getUserId()); + fail("Allowed to post a notification for an absent package"); + } catch (SecurityException e) { + // yay + } + } + + @Test + public void testGetEffectsSuppressor_noSuppressor() throws Exception { + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true); + assertThat(mBinderService.getEffectsSuppressor()).isNull(); + } + + @Test + public void testGetEffectsSuppressor_suppressorSameApp() throws Exception { + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + mService.isSystemUid = false; + mService.isSystemAppId = false; + mBinderService.requestHintsFromListener(mock(INotificationListener.class), + HINT_HOST_DISABLE_EFFECTS); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true); + assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component); + } + + @Test + public void testGetEffectsSuppressor_suppressorDiffApp() throws Exception { + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + mService.isSystemUid = false; + mService.isSystemAppId = false; + mBinderService.requestHintsFromListener(mock(INotificationListener.class), + HINT_HOST_DISABLE_EFFECTS); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false); + assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(null); + } + + @Test + public void testGetEffectsSuppressor_suppressorDiffAppSystemCaller() throws Exception { + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + mService.isSystemUid = true; + mBinderService.requestHintsFromListener(mock(INotificationListener.class), + HINT_HOST_DISABLE_EFFECTS); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false); + assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java index 37e0818eb083..5787780cef46 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java @@ -24,6 +24,8 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -250,6 +252,7 @@ public class ActivityOptionsTest { case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN: case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN: case ActivityOptions.KEY_TRANSIENT_LAUNCH: + case ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED: case "android:activity.animationFinishedListener": // KEY_ANIMATION_FINISHED_LISTENER case "android:activity.animSpecs": // KEY_ANIM_SPECS @@ -319,7 +322,7 @@ public class ActivityOptionsTest { Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. " + "Please review if the given bundle should be protected with permissions."); } - assertTrue(unknownKeys.isEmpty()); + assertThat(unknownKeys).isEmpty(); } public static class TrampolineActivity extends Activity { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java new file mode 100644 index 000000000000..12ab3e1bdf19 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.servertransaction.RefreshCallbackItem; +import android.app.servertransaction.ResumeActivityItem; +import android.content.ComponentName; +import android.content.res.Configuration; +import android.os.Handler; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link ActivityRefresher}. + * + * <p>Build/Install/Run: + * atest WmTests:ActivityRefresherTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class ActivityRefresherTests extends WindowTestsBase { + private Handler mMockHandler; + private LetterboxConfiguration mLetterboxConfiguration; + + private ActivityRecord mActivity; + private ActivityRefresher mActivityRefresher; + + private ActivityRefresher.Evaluator mEvaluatorFalse = + (activity, newConfig, lastReportedConfig) -> false; + + private ActivityRefresher.Evaluator mEvaluatorTrue = + (activity, newConfig, lastReportedConfig) -> true; + + private final Configuration mNewConfig = new Configuration(); + private final Configuration mOldConfig = new Configuration(); + + @Before + public void setUp() throws Exception { + mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration; + spyOn(mLetterboxConfiguration); + when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + .thenReturn(true); + when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + .thenReturn(true); + when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + .thenReturn(true); + + mMockHandler = mock(Handler.class); + when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }); + + mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler); + } + + @Test + public void testShouldRefreshActivity_refreshDisabled() throws Exception { + when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + .thenReturn(false); + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ false); + } + + @Test + public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception { + configureActivityAndDisplay(); + when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()) + .thenReturn(false); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ false); + } + + @Test + public void testShouldRefreshActivity_noRefreshTriggerers() throws Exception { + configureActivityAndDisplay(); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ false); + } + + @Test + public void testShouldRefreshActivity_refreshTriggerersReturnFalse() throws Exception { + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorFalse); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ false); + } + + @Test + public void testShouldRefreshActivity_anyRefreshTriggerersReturnTrue() throws Exception { + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorFalse); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ true); + } + + @Test + public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() + throws Exception { + mActivityRefresher.addEvaluator(mEvaluatorTrue); + when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + .thenReturn(false); + configureActivityAndDisplay(); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + } + + @Test + public void testOnActivityConfigurationChanging_cycleThroughPauseEnabledForApp() + throws Exception { + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + doReturn(true).when(mActivity.mLetterboxUiController) + .shouldRefreshActivityViaPauseForCameraCompat(); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + } + + @Test + public void testOnActivityRefreshed_setIsRefreshRequestedToFalse() throws Exception { + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + doReturn(true).when(mActivity.mLetterboxUiController) + .shouldRefreshActivityViaPauseForCameraCompat(); + + mActivityRefresher.onActivityRefreshed(mActivity); + + assertActivityRefreshRequested(false); + } + + private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception { + assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true); + } + + private void assertActivityRefreshRequested(boolean refreshRequested, + boolean cycleThroughStop) throws Exception { + verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0)) + .setIsRefreshRequested(true); + + final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token, + cycleThroughStop ? ON_STOP : ON_PAUSE); + final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token, + /* isForward */ false, /* shouldSendCompatFakeFocus */ false); + + verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0)) + .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(), + refreshCallbackItem, resumeActivityItem); + } + + private void configureActivityAndDisplay() { + mActivity = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setDisplay(mDisplayContent) + // Set the component to be that of the test class in order to enable compat changes + .setComponent(ComponentName.createRelative(mContext, + ActivityRefresherTests.class.getName())) + .setWindowingMode(WINDOWING_MODE_FREEFORM) + .build() + .getTopMostActivity(); + + spyOn(mActivity.mLetterboxUiController); + doReturn(true).when( + mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat(); + + doReturn(true).when(mActivity).inFreeformWindowingMode(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index 262ba8bb32f3..c76acd7e1d6b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -91,6 +91,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { private CameraManager mMockCameraManager; private Handler mMockHandler; private LetterboxConfiguration mLetterboxConfiguration; + private ActivityRefresher mActivityRefresher; private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; @@ -132,8 +133,9 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { }); CameraStateMonitor cameraStateMonitor = new CameraStateMonitor(mDisplayContent, mMockHandler); - mDisplayRotationCompatPolicy = - new DisplayRotationCompatPolicy(mDisplayContent, mMockHandler, cameraStateMonitor); + mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler); + mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent, + cameraStateMonitor, mActivityRefresher); // Do not show the real toast. spyOn(mDisplayRotationCompatPolicy); @@ -606,7 +608,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { private void assertActivityRefreshRequested(boolean refreshRequested, boolean cycleThroughStop) throws Exception { verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0)) - .setIsRefreshAfterRotationRequested(true); + .setIsRefreshRequested(true); final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE); @@ -628,7 +630,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { private void callOnActivityConfigurationChanging( ActivityRecord activity, boolean isDisplayRotationChanging) { - mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity, + mActivityRefresher.onActivityConfigurationChanging(activity, /* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0), /* newConfig */ createConfigurationWithDisplayRotation( isDisplayRotationChanging ? ROTATION_90 : ROTATION_0)); diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java index 7380aecbf47b..d8d5729700ca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java @@ -65,7 +65,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { performSurfacePlacementAndWaitForWindowAnimator(); mImeProvider.scheduleShowImePostLayout(appWin, ImeTracker.Token.empty()); - assertTrue(mImeProvider.isReadyToShowIme()); + assertTrue(mImeProvider.isScheduledAndReadyToShowIme()); } /** @@ -84,13 +84,13 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // Schedule (without triggering) after everything is ready. mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); - assertTrue(mImeProvider.isReadyToShowIme()); + assertTrue(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); // Manually trigger the show. - mImeProvider.checkShowImePostLayout(); - // No longer ready as it was already shown. - assertFalse(mImeProvider.isReadyToShowIme()); + mImeProvider.checkAndStartShowImePostLayout(); + // No longer scheduled as it was already shown. + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertTrue(mImeProvider.isImeShowing()); } @@ -104,7 +104,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // Schedule before anything is ready. mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); - assertFalse(mImeProvider.isReadyToShowIme()); + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime"); @@ -115,8 +115,8 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { mDisplayContent.updateImeInputAndControlTarget(target); // Performing surface placement picks up the show scheduled above. performSurfacePlacementAndWaitForWindowAnimator(); - // No longer ready as it was already shown. - assertFalse(mImeProvider.isReadyToShowIme()); + // No longer scheduled as it was already shown. + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertTrue(mImeProvider.isImeShowing()); } @@ -137,19 +137,19 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // Schedule before starting the afterPrepareSurfacesRunnable. mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); - assertFalse(mImeProvider.isReadyToShowIme()); + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); // This tries to pick up the show scheduled above, but must fail as the // afterPrepareSurfacesRunnable was not started yet. mDisplayContent.applySurfaceChangesTransaction(); - assertFalse(mImeProvider.isReadyToShowIme()); + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); // Starting the afterPrepareSurfacesRunnable picks up the show scheduled above. mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - // No longer ready as it was already shown. - assertFalse(mImeProvider.isReadyToShowIme()); + // No longer scheduled as it was already shown. + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertTrue(mImeProvider.isImeShowing()); } @@ -169,7 +169,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // Schedule before surface placement. mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); - assertFalse(mImeProvider.isReadyToShowIme()); + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); // Performing surface placement picks up the show scheduled above, and succeeds. @@ -177,8 +177,8 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // applySurfaceChangesTransaction. Both of them try to trigger the show, // but only the second one can succeed, as it comes after onPostLayout. performSurfacePlacementAndWaitForWindowAnimator(); - // No longer ready as it was already shown. - assertFalse(mImeProvider.isReadyToShowIme()); + // No longer scheduled as it was already shown. + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertTrue(mImeProvider.isImeShowing()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 0e1a1af9bc48..c69faede7580 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -353,6 +353,17 @@ public class InsetsStateControllerTest extends WindowTestsBase { } @Test + public void testControlTargetChangedWhileProviderHasNoWindow() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final InsetsSourceProvider provider = getController().getOrCreateSourceProvider( + ID_STATUS_BAR, statusBars()); + getController().onBarControlTargetChanged(app, null, null, null); + assertNull(getController().getControlsForDispatch(app)); + provider.setWindowContainer(createWindow(null, TYPE_APPLICATION, "statusBar"), null, null); + assertNotNull(getController().getControlsForDispatch(app)); + } + + @Test public void testTransientVisibilityOfFixedRotationState() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 1195c934a6f7..6b17de4c8640 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -1594,7 +1594,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test - @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_REACHABILITY) + @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY) public void testAllowReachabilityForThinLetterboxWithFlagEnabled() { spyOn(mController); doReturn(true).when(mController).isVerticalThinLetterboxed(); @@ -1609,7 +1609,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test - @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_REACHABILITY) + @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY) public void testAllowReachabilityForThinLetterboxWithFlagDisabled() { spyOn(mController); doReturn(true).when(mController).isVerticalThinLetterboxed(); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 8fe45cbb0430..76b4e0052792 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -99,18 +99,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; /** - * Subscription manager provides the mobile subscription information that are associated with the - * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U) - * and below can see all subscriptions as it does today. - * - * <p>For example, if we have - * <ul> - * <li> Subscription 1 associated with personal profile. - * <li> Subscription 2 associated with work profile. - * </ul> - * Then for SDK 35+, if the caller identity is personal profile, then - * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa. - * + * Subscription manager provides the mobile subscription information. */ @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) @@ -1980,17 +1969,7 @@ public class SubscriptionManager { } /** - * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller - * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U) - * and below can see all subscriptions as it does today. - * - * <p>For example, if we have - * <ul> - * <li> Subscription 1 associated with personal profile. - * <li> Subscription 2 associated with work profile. - * </ul> - * Then for SDK 35+, if the caller identity is personal profile, then this will return - * subscription 1 only and vice versa. + * Get the SubscriptionInfo(s) of the currently active SIM(s). * * <p> Returned records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by * {@link SubscriptionInfo#getSubscriptionId}. Beginning with Android SDK 35, this method will @@ -2259,9 +2238,7 @@ public class SubscriptionManager { } /** - * Get the active subscription count associated with the current caller user profile for - * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as - * it does today. + * Get the active subscription count. * * @return The current number of active subscriptions. * diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml index 6f8f008cf85b..955b43a32827 100644 --- a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml +++ b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.android.server.wm.flicker"> - <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/> + <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="35"/> <!-- Read and write traces from external storage --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> @@ -46,6 +46,8 @@ <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> <!-- Allow the test to connect to perfetto trace processor --> <uses-permission android:name="android.permission.INTERNET"/> + <!-- Allow to query for the Launcher TestInfo on SDK 30+ --> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <!-- Allow the test to write directly to /sdcard/ and connect to trace processor --> <application android:requestLegacyExternalStorage="true" android:networkSecurityConfig="@xml/network_security_config" diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml index 1dc103765c34..82de070921f0 100644 --- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml +++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt index cf4edd50040b..67825d2df361 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt @@ -43,6 +43,7 @@ import org.junit.runners.Parameterized * * To run this test: `atest FlickerTestsOther:OpenTrampolineActivityTest` */ +@FlakyTest(bugId = 341209752) @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @@ -168,7 +169,6 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding } } - @FlakyTest(bugId = 290736037) /** Main activity should go from fullscreen to being a split with secondary activity. */ @Test fun mainActivityLayerGoesFromFullscreenToSplit() { @@ -203,7 +203,6 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding } } - @FlakyTest(bugId = 288591571) @Test override fun visibleLayersShownMoreThanOneConsecutiveEntry() { super.visibleLayersShownMoreThanOneConsecutiveEntry() diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt index bc3696b3ed1c..eed9225d3da0 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -205,7 +205,8 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) : it.visibleRegion(ComponentNameMatcher.PIP_CONTENT_OVERLAY) val secondaryVisibleRegion = it.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) - overlayVisibleRegion.coversExactly(secondaryVisibleRegion.region) + // TODO(b/340992001): replace coverAtLeast with coverExactly + overlayVisibleRegion.coversAtLeast(secondaryVisibleRegion.region) } .then() .isInvisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt index fb9258304870..379b45cdf08e 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt @@ -60,14 +60,16 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa testApp.launchViaIntent(wmHelper) testApp.launchSecondaryActivity(wmHelper) secondaryApp.launchViaIntent(wmHelper) - tapl.goHome() - wmHelper - .StateSyncBuilder() - .withAppTransitionIdle() - .withHomeActivityVisible() - .waitForAndVerify() startDisplayBounds = wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + + // Record the displayBounds before `goHome()` in case the launcher is fixed-portrait. + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() } transitions { SplitScreenUtils.enterSplit( @@ -138,10 +140,6 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa check { "ActivityEmbeddingSplitHeight" } .that(leftAELayerRegion.region.bounds.height()) .isEqual(rightAELayerRegion.region.bounds.height()) - check { "SystemSplitHeight" } - .that(rightAELayerRegion.region.bounds.height()) - .isEqual(secondaryAppLayerRegion.region.bounds.height()) - // TODO(b/292283182): Remove this special case handling. check { "ActivityEmbeddingSplitWidth" } .that( abs( @@ -150,14 +148,6 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa ) ) .isLower(2) - check { "SystemSplitWidth" } - .that( - abs( - secondaryAppLayerRegion.region.bounds.width() - - 2 * rightAELayerRegion.region.bounds.width() - ) - ) - .isLower(2) } } @@ -170,15 +160,9 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) val rightAEWindowRegion = visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) - // There's no window for the divider bar. - val secondaryAppLayerRegion = - visibleRegion(ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()) check { "ActivityEmbeddingSplitHeight" } .that(leftAEWindowRegion.region.bounds.height()) .isEqual(rightAEWindowRegion.region.bounds.height()) - check { "SystemSplitHeight" } - .that(rightAEWindowRegion.region.bounds.height()) - .isEqual(secondaryAppLayerRegion.region.bounds.height()) check { "ActivityEmbeddingSplitWidth" } .that( abs( @@ -187,14 +171,6 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa ) ) .isLower(2) - check { "SystemSplitWidth" } - .that( - abs( - secondaryAppLayerRegion.region.bounds.width() - - 2 * rightAEWindowRegion.region.bounds.width() - ) - ) - .isLower(2) } } diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml index 57a58c8377ec..4ffb11ab92ae 100644 --- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml index 2cb86e05f68b..0fa4d07b2eca 100644 --- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml index 2cf85fa38e67..4d9fefbc7d88 100644 --- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml +++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml index b93e1bec21f8..b879c54dcab3 100644 --- a/tests/FlickerTests/IME/AndroidTestTemplate.xml +++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml @@ -82,6 +82,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/IME/OWNERS b/tests/FlickerTests/IME/OWNERS index ae1098d496df..e3a2e674ae7a 100644 --- a/tests/FlickerTests/IME/OWNERS +++ b/tests/FlickerTests/IME/OWNERS @@ -1,3 +1,3 @@ # ime # Bug component: 34867 -include /services/core/java/com/android/server/inputmethod/OWNERS +file:/services/core/java/com/android/server/inputmethod/OWNERS diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml index 9c6a17d37a75..04b312a896b9 100644 --- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml index ecbed28085a2..8acdabc2337d 100644 --- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml index 1eacdfd89384..91ece214aad5 100644 --- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 9198ae184b18..3e500d9c8bd4 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -18,7 +18,10 @@ package="com.android.server.wm.flicker.testapp"> <uses-sdk android:minSdkVersion="29" - android:targetSdkVersion="29"/> + android:targetSdkVersion="35"/> + + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> + <application android:allowBackup="false" android:supportsRtl="true"> <uses-library android:name="androidx.window.extensions" android:required="false"/> @@ -107,7 +110,7 @@ android:immersive="true" android:resizeableActivity="true" android:screenOrientation="portrait" - android:theme="@android:style/Theme.NoTitleBar" + android:theme="@style/OptOutEdgeToEdge.NoTitleBar" android:configChanges="screenSize" android:label="PortraitImmersiveActivity" android:exported="true"> @@ -119,7 +122,7 @@ <activity android:name=".LaunchTransparentActivity" android:resizeableActivity="false" android:screenOrientation="portrait" - android:theme="@android:style/Theme" + android:theme="@style/OptOutEdgeToEdge" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity" android:label="LaunchTransparentActivity" android:exported="true"> @@ -273,7 +276,7 @@ android:exported="true" android:label="MailActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity" - android:theme="@style/Theme.AppCompat.Light"> + android:theme="@style/OptOutEdgeToEdge.AppCompatTheme"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -282,7 +285,7 @@ <activity android:name=".GameActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity" android:immersive="true" - android:theme="@android:style/Theme.NoTitleBar" + android:theme="@style/OptOutEdgeToEdge.NoTitleBar" android:configChanges="screenSize" android:label="GameActivity" android:exported="true"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml index 86c21906163f..917aec1e809d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml @@ -14,66 +14,71 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" android:background="@android:color/holo_orange_light"> - <Button - android:id="@+id/launch_secondary_activity_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchSecondaryActivity" - android:tag="LEFT_TO_RIGHT" - android:text="Launch Secondary Activity" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> - <Button - android:id="@+id/launch_secondary_activity_rtl_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchSecondaryActivity" - android:tag="RIGHT_TO_LEFT" - android:text="Launch Secondary Activity in RTL" /> + <Button + android:id="@+id/launch_secondary_activity_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchSecondaryActivity" + android:tag="LEFT_TO_RIGHT" + android:text="Launch Secondary Activity" /> - <Button - android:id="@+id/launch_secondary_activity_horizontally_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchSecondaryActivity" - android:tag="BOTTOM_TO_TOP" - android:text="Launch Secondary Activity Horizontally" /> + <Button + android:id="@+id/launch_secondary_activity_rtl_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchSecondaryActivity" + android:tag="RIGHT_TO_LEFT" + android:text="Launch Secondary Activity in RTL" /> - <Button - android:id="@+id/launch_placeholder_split_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchPlaceholderSplit" - android:tag="LEFT_TO_RIGHT" - android:text="Launch Placeholder Split" /> + <Button + android:id="@+id/launch_secondary_activity_horizontally_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchSecondaryActivity" + android:tag="BOTTOM_TO_TOP" + android:text="Launch Secondary Activity Horizontally" /> - <Button - android:id="@+id/launch_always_expand_activity_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchAlwaysExpandActivity" - android:text="Launch Always Expand Activity" /> + <Button + android:id="@+id/launch_placeholder_split_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchPlaceholderSplit" + android:tag="LEFT_TO_RIGHT" + android:text="Launch Placeholder Split" /> - <Button - android:id="@+id/launch_placeholder_split_rtl_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchPlaceholderSplit" - android:tag="RIGHT_TO_LEFT" - android:text="Launch Placeholder Split in RTL" /> + <Button + android:id="@+id/launch_always_expand_activity_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchAlwaysExpandActivity" + android:text="Launch Always Expand Activity" /> - <Button - android:id="@+id/launch_trampoline_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchTrampolineActivity" - android:tag="LEFT_TO_RIGHT" - android:text="Launch Trampoline Activity" /> + <Button + android:id="@+id/launch_placeholder_split_rtl_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchPlaceholderSplit" + android:tag="RIGHT_TO_LEFT" + android:text="Launch Placeholder Split in RTL" /> -</LinearLayout> + <Button + android:id="@+id/launch_trampoline_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchTrampolineActivity" + android:tag="LEFT_TO_RIGHT" + android:text="Launch Trampoline Activity" /> + + </LinearLayout> +</ScrollView> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 9b742d96e35b..47d113717ae0 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -16,7 +16,19 @@ --> <resources> - <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault"> + <style name="OptOutEdgeToEdge" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + </style> + + <style name="OptOutEdgeToEdge.NoTitleBar" parent="@android:style/Theme.NoTitleBar"> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + </style> + + <style name="OptOutEdgeToEdge.AppCompatTheme" parent="@style/Theme.AppCompat.Light"> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + </style> + + <style name="DefaultTheme" parent="@style/OptOutEdgeToEdge"> <item name="android:windowBackground">@android:color/darker_gray</item> </style> @@ -32,7 +44,7 @@ <item name="android:windowLayoutInDisplayCutoutMode">never</item> </style> - <style name="DialogTheme" parent="@android:style/Theme.DeviceDefault"> + <style name="DialogTheme" parent="@style/OptOutEdgeToEdge"> <item name="android:windowAnimationStyle">@null</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@null</item> @@ -43,18 +55,18 @@ <item name="android:windowSoftInputMode">stateUnchanged</item> </style> - <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault"> + <style name="TransparentTheme" parent="@style/OptOutEdgeToEdge"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> <item name="android:backgroundDimEnabled">false</item> </style> - <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault"> + <style name="no_starting_window" parent="@style/OptOutEdgeToEdge"> <item name="android:windowDisablePreview">true</item> </style> - <style name="SplashscreenAppTheme" parent="@android:style/Theme.DeviceDefault"> + <style name="SplashscreenAppTheme" parent="@style/OptOutEdgeToEdge"> <!-- Splashscreen Attributes --> <item name="android:windowSplashScreenAnimatedIcon">@drawable/avd_anim</item> <!-- Here we want to match the duration of our AVD --> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java index c92b82b896f2..a86ba5f76374 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java @@ -125,7 +125,7 @@ public class BubbleHelper { .setContentTitle("BubbleChat") .setContentIntent(PendingIntent.getActivity(mContext, 0, new Intent(mContext, LaunchBubbleActivity.class), - PendingIntent.FLAG_UPDATE_CURRENT)) + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE)) .setStyle(new Notification.MessagingStyle(chatBot) .setConversationTitle("BubbleChat") .addMessage("BubbleChat", @@ -140,7 +140,7 @@ public class BubbleHelper { Intent target = new Intent(mContext, BubbleActivity.class); target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id); PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target, - PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); return new Notification.BubbleMetadata.Builder() .setIntent(bubbleIntent) diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java index dea34442464d..37332c9712f5 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java @@ -17,6 +17,9 @@ package com.android.server.wm.flicker.testapp; +import static android.Manifest.permission.POST_NOTIFICATIONS; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import android.app.Activity; import android.app.Person; import android.content.Context; @@ -24,6 +27,7 @@ import android.content.Intent; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.drawable.Icon; +import android.os.Build; import android.os.Bundle; import android.view.View; @@ -36,6 +40,13 @@ public class LaunchBubbleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) { + // POST_NOTIFICATIONS permission required for notification post sdk 33. + requestPermissions(new String[] { POST_NOTIFICATIONS }, 0); + } + addInboxShortcut(getApplicationContext()); mBubbleHelper = BubbleHelper.getInstance(this); setContentView(R.layout.activity_main); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java index a4dd5753539d..d6427abcc65a 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java @@ -16,6 +16,9 @@ package com.android.server.wm.flicker.testapp; +import static android.Manifest.permission.POST_NOTIFICATIONS; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import android.app.Activity; import android.app.Notification; import android.app.NotificationChannel; @@ -23,6 +26,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.view.WindowManager; import android.widget.Button; @@ -34,6 +38,13 @@ public class NotificationActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) { + // POST_NOTIFICATIONS permission required for notification post sdk 33. + requestPermissions(new String[] { POST_NOTIFICATIONS }, 0); + } + WindowManager.LayoutParams p = getWindow().getAttributes(); p.layoutInDisplayCutoutMode = WindowManager.LayoutParams .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java index 1ab8ddbe20e2..27eb5a06451a 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java @@ -198,7 +198,7 @@ public class PipActivity extends Activity { filter.addAction(ACTION_SET_REQUESTED_ORIENTATION); filter.addAction(ACTION_ENTER_PIP); filter.addAction(ACTION_ASPECT_RATIO); - registerReceiver(mBroadcastReceiver, filter); + registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED); handleIntentExtra(getIntent()); } @@ -222,8 +222,8 @@ public class PipActivity extends Activity { private RemoteAction buildRemoteAction(Icon icon, String label, String action) { final Intent intent = new Intent(action); - final PendingIntent pendingIntent = - PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); return new RemoteAction(icon, label, label, pendingIntent); } diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt index 3a2a3be0690d..ae32bdaf80d7 100644 --- a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt +++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt @@ -16,6 +16,8 @@ package android.hardware.input +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.content.ContextWrapper import android.graphics.drawable.Drawable import android.platform.test.annotations.Presubmit @@ -54,16 +56,16 @@ class KeyboardLayoutPreviewTests { } @Test + @EnableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) fun testKeyboardLayoutDrawable_hasCorrectDimensions() { - setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) val drawable = createDrawable()!! assertEquals(WIDTH, drawable.intrinsicWidth) assertEquals(HEIGHT, drawable.intrinsicHeight) } @Test + @DisableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) fun testKeyboardLayoutDrawable_isNull_ifFlagOff() { - setFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) assertNull(createDrawable()) } }
\ No newline at end of file diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt index e2b0c36ae694..bcd56ad0c669 100644 --- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt +++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt @@ -21,6 +21,7 @@ import android.content.ContextWrapper import android.os.Handler import android.os.HandlerExecutor import android.os.test.TestLooper +import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.Presubmit import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent @@ -50,6 +51,10 @@ import kotlin.test.fail */ @Presubmit @RunWith(MockitoJUnitRunner::class) +@EnableFlags( + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, + com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL, +) class StickyModifierStateListenerTest { @get:Rule @@ -67,10 +72,6 @@ class StickyModifierStateListenerTest { @Before fun setUp() { - // Enable Sticky keys feature - rule.enableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG) - rule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL) - context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java index 001a09a0225a..be9fb1b309f6 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java +++ b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java @@ -34,7 +34,7 @@ import perfetto.protos.DataSourceConfigOuterClass; import perfetto.protos.ProtologCommon; import perfetto.protos.ProtologConfig; -public class PerfettoDataSourceTest { +public class ProtologDataSourceTest { @Before public void before() { assumeTrue(android.tracing.Flags.perfettoProtologTracing()); diff --git a/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java new file mode 100644 index 000000000000..d6f3148e64f1 --- /dev/null +++ b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.usb; + +import static com.google.common.truth.Truth.assertThat; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.usb.flags.Flags; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.util.XmlUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.StringReader; + +/** + * Unit tests for {@link android.hardware.usb.DeviceFilter}. + */ +@RunWith(AndroidJUnit4.class) +public class DeviceFilterTest { + + private static final int VID = 10; + private static final int PID = 11; + private static final int CLASS = 12; + private static final int SUBCLASS = 13; + private static final int PROTOCOL = 14; + private static final String MANUFACTURER = "Google"; + private static final String PRODUCT = "Test"; + private static final String SERIAL_NO = "4AL23"; + private static final String INTERFACE_NAME = "MTP"; + + private MockitoSession mStaticMockSession; + + @Before + public void setUp() throws Exception { + mStaticMockSession = ExtendedMockito.mockitoSession() + .mockStatic(Flags.class) + .strictness(Strictness.WARN) + .startMocking(); + + when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(true); + } + + @After + public void tearDown() throws Exception { + mStaticMockSession.finishMocking(); + } + + @Test + public void testConstructorFromValues_interfaceNameIsInitialized() { + DeviceFilter deviceFilter = new DeviceFilter( + VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER, + PRODUCT, SERIAL_NO, INTERFACE_NAME + ); + + verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter); + assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME); + } + + @Test + public void testConstructorFromUsbDevice_interfaceNameIsNull() { + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + when(usbDevice.getVendorId()).thenReturn(VID); + when(usbDevice.getProductId()).thenReturn(PID); + when(usbDevice.getDeviceClass()).thenReturn(CLASS); + when(usbDevice.getDeviceSubclass()).thenReturn(SUBCLASS); + when(usbDevice.getDeviceProtocol()).thenReturn(PROTOCOL); + when(usbDevice.getManufacturerName()).thenReturn(MANUFACTURER); + when(usbDevice.getProductName()).thenReturn(PRODUCT); + when(usbDevice.getSerialNumber()).thenReturn(SERIAL_NO); + + DeviceFilter deviceFilter = new DeviceFilter(usbDevice); + + verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter); + assertThat(deviceFilter.mInterfaceName).isEqualTo(null); + } + + @Test + public void testConstructorFromDeviceFilter_interfaceNameIsInitialized() { + DeviceFilter originalDeviceFilter = new DeviceFilter( + VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER, + PRODUCT, SERIAL_NO, INTERFACE_NAME + ); + + DeviceFilter deviceFilter = new DeviceFilter(originalDeviceFilter); + + verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter); + assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME); + } + + + @Test + public void testReadFromXml_interfaceNamePresent_propertyIsInitialized() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>"); + + assertThat(deviceFilter.mInterfaceName).isEqualTo("MTP"); + } + + @Test + public void testReadFromXml_interfaceNameAbsent_propertyIsNull() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />"); + + assertThat(deviceFilter.mInterfaceName).isEqualTo(null); + } + + @Test + public void testWrite_withInterfaceName() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>"); + XmlSerializer serializer = Mockito.mock(XmlSerializer.class); + + deviceFilter.write(serializer); + + verify(serializer).attribute(null, "interface-name", "MTP"); + } + + @Test + public void testWrite_withoutInterfaceName() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />"); + XmlSerializer serializer = Mockito.mock(XmlSerializer.class); + + deviceFilter.write(serializer); + + verify(serializer, times(0)).attribute(eq(null), eq("interface-name"), any()); + } + + @Test + public void testToString() { + DeviceFilter deviceFilter = new DeviceFilter( + VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER, + PRODUCT, SERIAL_NO, INTERFACE_NAME + ); + + assertThat(deviceFilter.toString()).isEqualTo( + "DeviceFilter[mVendorId=10,mProductId=11,mClass=12,mSubclass=13,mProtocol=14," + + "mManufacturerName=Google,mProductName=Test,mSerialNumber=4AL23," + + "mInterfaceName=MTP]"); + } + + @Test + public void testMatch_interfaceNameMatches_returnTrue() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml( + "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" " + + "interface-name=\"MTP\"/>"); + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + when(usbDevice.getInterfaceCount()).thenReturn(1); + when(usbDevice.getInterface(0)).thenReturn(new UsbInterface( + /* id= */ 0, + /* alternateSetting= */ 0, + /* name= */ "MTP", + /* class= */ 255, + /* subClass= */ 255, + /* protocol= */ 0)); + + assertTrue(deviceFilter.matches(usbDevice)); + } + + @Test + public void testMatch_interfaceNameMismatch_returnFalse() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml( + "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" " + + "interface-name=\"MTP\"/>"); + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + when(usbDevice.getInterfaceCount()).thenReturn(1); + when(usbDevice.getInterface(0)).thenReturn(new UsbInterface( + /* id= */ 0, + /* alternateSetting= */ 0, + /* name= */ "UVC", + /* class= */ 255, + /* subClass= */ 255, + /* protocol= */ 0)); + + assertFalse(deviceFilter.matches(usbDevice)); + } + + @Test + public void testMatch_interfaceNameMismatchFlagDisabled_returnTrue() throws Exception { + when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(false); + DeviceFilter deviceFilter = getDeviceFilterFromXml( + "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" " + + "interface-name=\"MTP\"/>"); + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + when(usbDevice.getInterfaceCount()).thenReturn(1); + when(usbDevice.getInterface(0)).thenReturn(new UsbInterface( + /* id= */ 0, + /* alternateSetting= */ 0, + /* name= */ "UVC", + /* class= */ 255, + /* subClass= */ 255, + /* protocol= */ 0)); + + assertTrue(deviceFilter.matches(usbDevice)); + } + + private void verifyDeviceFilterConfigurationExceptInterfaceName(DeviceFilter deviceFilter) { + assertThat(deviceFilter.mVendorId).isEqualTo(VID); + assertThat(deviceFilter.mProductId).isEqualTo(PID); + assertThat(deviceFilter.mClass).isEqualTo(CLASS); + assertThat(deviceFilter.mSubclass).isEqualTo(SUBCLASS); + assertThat(deviceFilter.mProtocol).isEqualTo(PROTOCOL); + assertThat(deviceFilter.mManufacturerName).isEqualTo(MANUFACTURER); + assertThat(deviceFilter.mProductName).isEqualTo(PRODUCT); + assertThat(deviceFilter.mSerialNumber).isEqualTo(SERIAL_NO); + } + + private DeviceFilter getDeviceFilterFromXml(String xml) throws Exception { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + XmlPullParser parser = factory.newPullParser(); + parser.setInput(new StringReader(xml)); + XmlUtils.nextElement(parser); + + return DeviceFilter.read(parser); + } + +} diff --git a/tools/aapt/Symbol.h b/tools/aapt/Symbol.h index de1d60cbae42..24c3208d9081 100644 --- a/tools/aapt/Symbol.h +++ b/tools/aapt/Symbol.h @@ -40,7 +40,7 @@ struct Symbol { }; /** - * A specific defintion of a symbol, defined with a configuration and a definition site. + * A specific definition of a symbol, defined with a configuration and a definition site. */ struct SymbolDefinition { inline SymbolDefinition(); @@ -92,4 +92,3 @@ bool SymbolDefinition::operator<(const SymbolDefinition& rhs) const { } #endif // AAPT_SYMBOL_H - |