diff options
662 files changed, 17700 insertions, 10825 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index f6c18d62ff1e..7f02cb36dfc1 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -397,18 +397,11 @@ public class JobInfo implements Parcelable { public static final int FLAG_EXPEDITED = 1 << 4; /** - * Whether it's a data transfer job or not. - * - * @hide - */ - public static final int FLAG_DATA_TRANSFER = 1 << 5; - - /** * Whether it's a user initiated job or not. * * @hide */ - public static final int FLAG_USER_INITIATED = 1 << 6; + public static final int FLAG_USER_INITIATED = 1 << 5; /** * @hide @@ -738,13 +731,6 @@ public class JobInfo implements Parcelable { } /** - * @see JobInfo.Builder#setDataTransfer(boolean) - */ - public boolean isDataTransfer() { - return (flags & FLAG_DATA_TRANSFER) != 0; - } - - /** * @see JobInfo.Builder#setUserInitiated(boolean) */ public boolean isUserInitiated() { @@ -1850,39 +1836,6 @@ public class JobInfo implements Parcelable { } /** - * Indicates that this job will be used to transfer data to or from a remote server. The - * system could attempt to run a data transfer job longer than a regular job if the data - * being transferred is potentially very large and can take a long time to complete. - * - * <p> - * You must provide an estimate of the payload size via - * {@link #setEstimatedNetworkBytes(long, long)} when scheduling the job or use - * {@link JobService#updateEstimatedNetworkBytes(JobParameters, long, long)} or - * {@link JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)} - * shortly after the job starts. - * - * <p> - * For user-initiated transfers that must be started immediately, call - * {@link #setUserInitiated(boolean) setUserInitiated(true)}. Otherwise, the system may - * defer the job to a more opportune time. - * - * <p> - * If you want to perform more than one data transfer job, consider enqueuing multiple - * {@link JobWorkItem JobWorkItems} along with {@link #setDataTransfer(boolean)}. - * - * @see JobInfo#isDataTransfer() - */ - @NonNull - public Builder setDataTransfer(boolean dataTransfer) { - if (dataTransfer) { - mFlags |= FLAG_DATA_TRANSFER; - } else { - mFlags &= (~FLAG_DATA_TRANSFER); - } - return this; - } - - /** * Indicates that this job is being scheduled to fulfill an explicit user request. * As such, user-initiated jobs can only be scheduled when the app is in the foreground * or in a state where launching an activity is allowed, as defined @@ -1909,6 +1862,11 @@ public class JobInfo implements Parcelable { * {@link SecurityException}. * * <p> + * In {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, user-initiated jobs can only + * be used for network data transfers. As such, they must specify a required network via + * {@link #setRequiredNetwork(NetworkRequest)} or {@link #setRequiredNetworkType(int)}. + * + * <p> * These jobs will not be subject to quotas and will be started immediately once scheduled * if all constraints are met and the device system health allows for additional tasks. * @@ -2179,10 +2137,6 @@ public class JobInfo implements Parcelable { if (isPeriodic) { throw new IllegalArgumentException("An expedited job cannot be periodic"); } - if ((flags & FLAG_DATA_TRANSFER) != 0) { - throw new IllegalArgumentException( - "An expedited job cannot also be a data transfer job"); - } if (isUserInitiated) { throw new IllegalArgumentException("An expedited job cannot be user-initiated"); } @@ -2202,24 +2156,6 @@ public class JobInfo implements Parcelable { } } - if ((flags & FLAG_DATA_TRANSFER) != 0) { - if (backoffPolicy == BACKOFF_POLICY_LINEAR) { - throw new IllegalArgumentException( - "A data transfer job cannot have a linear backoff policy."); - } - if (hasLateConstraint) { - throw new IllegalArgumentException("A data transfer job cannot have a deadline"); - } - if ((flags & FLAG_PREFETCH) != 0) { - throw new IllegalArgumentException( - "A data transfer job cannot also be a prefetch job"); - } - if (networkRequest == null) { - throw new IllegalArgumentException( - "A data transfer job must specify a valid network type"); - } - } - if (isUserInitiated) { if (hasEarlyConstraint) { throw new IllegalArgumentException("A user-initiated job cannot have a time delay"); @@ -2245,6 +2181,15 @@ public class JobInfo implements Parcelable { throw new IllegalArgumentException( "Can't call addTriggerContentUri() on a user-initiated job"); } + // UIDTs + if (networkRequest == null) { + throw new IllegalArgumentException( + "A user-initaited data transfer job must specify a valid network type"); + } + if (backoffPolicy == BACKOFF_POLICY_LINEAR) { + throw new IllegalArgumentException( + "A user-initiated data transfer job cannot have a linear backoff policy."); + } } } 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 bf3789fa0095..0af191af2a21 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -478,8 +478,6 @@ public class JobSchedulerService extends com.android.server.SystemService case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS: case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS: case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS: - case Constants.KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS: - case Constants.KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS: case Constants.KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS: case Constants.KEY_RUNTIME_USER_INITIATED_LIMIT_MS: case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR: @@ -574,10 +572,6 @@ public class JobSchedulerService extends com.android.server.SystemService "runtime_free_quota_max_limit_ms"; private static final String KEY_RUNTIME_MIN_GUARANTEE_MS = "runtime_min_guarantee_ms"; private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms"; - private static final String KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = - "runtime_min_data_transfer_guarantee_ms"; - private static final String KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS = - "runtime_data_transfer_limit_ms"; private static final String KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = "runtime_min_user_initiated_guarantee_ms"; private static final String KEY_RUNTIME_USER_INITIATED_LIMIT_MS = @@ -616,10 +610,6 @@ public class JobSchedulerService extends com.android.server.SystemService public static final long DEFAULT_RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS; @VisibleForTesting public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS; - public static final long DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = - DEFAULT_RUNTIME_MIN_GUARANTEE_MS; - public static final long DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS = - DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS); public static final long DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS = @@ -739,18 +729,6 @@ public class JobSchedulerService extends com.android.server.SystemService public long RUNTIME_MIN_EJ_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS; /** - * The minimum amount of time we try to guarantee normal data transfer jobs will run for. - */ - public long RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = - DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS; - - /** - * The maximum amount of time we will let a normal data transfer job run for. This will only - * apply if there are no other limits that apply to the specific data transfer job. - */ - public long RUNTIME_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS; - - /** * The minimum amount of time we try to guarantee normal user-initiated jobs will run for. */ public long RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = @@ -885,8 +863,6 @@ public class JobSchedulerService extends com.android.server.SystemService KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR, - KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS, KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS, KEY_RUNTIME_USER_INITIATED_LIMIT_MS, KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS, @@ -904,17 +880,6 @@ public class JobSchedulerService extends com.android.server.SystemService properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)); // Make sure min runtime is at least as long as regular jobs. - RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS, - properties.getLong( - KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS)); - // Max limit should be at least the min guarantee AND the free quota. - RUNTIME_DATA_TRANSFER_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, - Math.max(RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - properties.getLong( - KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS, - DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS))); - // Make sure min runtime is at least as long as regular jobs. RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS, properties.getLong( KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS, @@ -993,10 +958,6 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println(); pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS) .println(); - pw.print(KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS).println(); - pw.print(KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS, - RUNTIME_DATA_TRANSFER_LIMIT_MS).println(); pw.print(KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS, RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS).println(); pw.print(KEY_RUNTIME_USER_INITIATED_LIMIT_MS, @@ -3291,7 +3252,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (job.shouldTreatAsUserInitiatedJob() && checkRunUserInitiatedJobsPermission( job.getSourceUid(), job.getSourcePackageName())) { - if (job.getJob().isDataTransfer()) { + if (job.getJob().getRequiredNetwork() != null) { // UI+DT final long estimatedTransferTimeMs = mConnectivityController.getEstimatedTransferTimeMs(job); if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) { @@ -3308,9 +3269,6 @@ public class JobSchedulerService extends com.android.server.SystemService )); } return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS; - } else if (job.getJob().isDataTransfer()) { - // For now, don't increase a bg data transfer's minimum guarantee. - return mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS; } else if (job.shouldTreatAsExpeditedJob()) { // Don't guarantee RESTRICTED jobs more than 5 minutes. return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX @@ -3328,7 +3286,7 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean allowLongerJob = job.shouldTreatAsUserInitiatedJob() && checkRunUserInitiatedJobsPermission( job.getSourceUid(), job.getSourcePackageName()); - if (job.getJob().isDataTransfer() && allowLongerJob) { // UI+DT + if (job.getJob().getRequiredNetwork() != null && allowLongerJob) { // UI+DT return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS; } if (allowLongerJob) { // UI with LRJ permission @@ -3337,9 +3295,6 @@ public class JobSchedulerService extends com.android.server.SystemService if (job.shouldTreatAsUserInitiatedJob()) { return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; } - if (job.getJob().isDataTransfer()) { - return mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS; - } return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, mConstants.USE_TARE_POLICY ? mTareController.getMaxJobExecutionTimeMsLocked(job) 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 29ab45533667..e60ed4ade9b7 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -727,8 +727,11 @@ public final class JobServiceContext implements ServiceConnection { // Exception-throwing-can down the road to JobParameters.completeWork >:( return true; } - mService.mJobs.touchJob(mRunningJob); - return mRunningJob.completeWorkLocked(workId); + if (mRunningJob.completeWorkLocked(workId)) { + mService.mJobs.touchJob(mRunningJob); + return true; + } + return false; } } finally { Binder.restoreCallingIdentity(ident); 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 1971a11ca98a..537a67039a82 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 @@ -828,6 +828,10 @@ public final class JobStatus { } } + /** + * Returns {@code true} if the JobWorkItem queue was updated, + * and {@code false} if nothing changed. + */ public boolean completeWorkLocked(int workId) { if (executingWork != null) { final int N = executingWork.size(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java index 9ada8dc3ef32..47b7e13446b8 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java @@ -24,6 +24,7 @@ import android.util.Log; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.server.JobSchedulerBackgroundThread; import com.android.server.am.ActivityManagerService; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; @@ -50,7 +51,7 @@ public final class CarIdlenessTracker extends BroadcastReceiver implements Idlen public static final String ACTION_UNFORCE_IDLE = "com.android.server.jobscheduler.UNFORCE_IDLE"; // After construction, mutations of idle/screen-on state will only happen - // on the main looper thread, either in onReceive() or in an alarm callback. + // on the JobScheduler thread, either in onReceive() or in an alarm callback. private boolean mIdle; private boolean mGarageModeOn; private boolean mForced; @@ -90,7 +91,7 @@ public final class CarIdlenessTracker extends BroadcastReceiver implements Idlen filter.addAction(ACTION_UNFORCE_IDLE); filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE); - context.registerReceiver(this, filter); + context.registerReceiver(this, filter, null, JobSchedulerBackgroundThread.getHandler()); } @Override diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java index 140cca679e14..15d67662130f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java @@ -31,6 +31,7 @@ import android.util.Log; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.server.JobSchedulerBackgroundThread; import com.android.server.am.ActivityManagerService; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; @@ -47,8 +48,8 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id private AlarmManager mAlarm; private PowerManager mPowerManager; - // After construction, mutations of idle/screen-on state will only happen - // on the main looper thread, either in onReceive() or in an alarm callback. + // After construction, mutations of idle/screen-on/projection states will only happen + // on the JobScheduler thread, either in onReceive(), in an alarm callback, or in on.*Changed. private long mInactivityIdleThreshold; private long mIdleWindowSlop; private boolean mIdle; @@ -101,12 +102,10 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id filter.addAction(Intent.ACTION_DOCK_IDLE); filter.addAction(Intent.ACTION_DOCK_ACTIVE); - context.registerReceiver(this, filter); + context.registerReceiver(this, filter, null, JobSchedulerBackgroundThread.getHandler()); - // TODO(b/172579710): Move the callbacks off the main executor and on to - // JobSchedulerBackgroundThread.getExecutor() once synchronization is fixed in this class. context.getSystemService(UiModeManager.class).addOnProjectionStateChangedListener( - UiModeManager.PROJECTION_TYPE_ALL, context.getMainExecutor(), + UiModeManager.PROJECTION_TYPE_ALL, JobSchedulerBackgroundThread.getExecutor(), mOnProjectionStateChangedListener); } @@ -226,7 +225,8 @@ public final class DeviceIdlenessTracker extends BroadcastReceiver implements Id Slog.v(TAG, "Scheduling idle : " + reason + " now:" + nowElapsed + " when=" + when); } mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, - when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null); + when, mIdleWindowSlop, "JS idleness", + JobSchedulerBackgroundThread.getExecutor(), mIdleAlarmListener); } } diff --git a/core/api/current.txt b/core/api/current.txt index 9f3ceb3209de..2db3eae5fab6 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6784,7 +6784,7 @@ package android.app { method public boolean areNotificationsEnabled(); method public boolean areNotificationsPaused(); method public boolean canNotifyAsPackage(@NonNull String); - method public boolean canSendFullScreenIntent(); + method public boolean canUseFullScreenIntent(); method public void cancel(int); method public void cancel(@Nullable String, int); method public void cancelAll(); @@ -8733,7 +8733,6 @@ package android.app.job { method public long getTriggerContentMaxDelay(); method public long getTriggerContentUpdateDelay(); method @Nullable public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris(); - method public boolean isDataTransfer(); method public boolean isExpedited(); method public boolean isImportantWhileForeground(); method public boolean isPeriodic(); @@ -8770,7 +8769,6 @@ package android.app.job { method public android.app.job.JobInfo build(); method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int); method public android.app.job.JobInfo.Builder setClipData(@Nullable android.content.ClipData, int); - method @NonNull public android.app.job.JobInfo.Builder setDataTransfer(boolean); method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long, long); method @NonNull public android.app.job.JobInfo.Builder setExpedited(boolean); method public android.app.job.JobInfo.Builder setExtras(@NonNull android.os.PersistableBundle); @@ -9576,8 +9574,6 @@ package android.companion.virtual { public final class VirtualDeviceManager { method @NonNull public java.util.List<android.companion.virtual.VirtualDevice> getVirtualDevices(); - field public static final int DEVICE_ID_DEFAULT = 0; // 0x0 - field public static final int DEVICE_ID_INVALID = -1; // 0xffffffff } } @@ -10354,6 +10350,8 @@ package android.content { field public static final int CONTEXT_RESTRICTED = 4; // 0x4 field public static final String CREDENTIAL_SERVICE = "credential"; field public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps"; + field public static final int DEVICE_ID_DEFAULT = 0; // 0x0 + field public static final int DEVICE_ID_INVALID = -1; // 0xffffffff field public static final String DEVICE_LOCK_SERVICE = "device_lock"; field public static final String DEVICE_POLICY_SERVICE = "device_policy"; field public static final String DISPLAY_HASH_SERVICE = "display_hash"; @@ -12448,7 +12446,7 @@ package android.content.pm { method @Nullable public abstract android.graphics.drawable.Drawable getActivityBanner(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException; @@ -12458,7 +12456,7 @@ package android.content.pm { method public abstract int getApplicationEnabledSetting(@NonNull String); method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull android.content.pm.ApplicationInfo); method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo); method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo); @@ -12470,10 +12468,10 @@ package android.content.pm { method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo); method public void getGroupOfPlatformPermission(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.String>); method @NonNull public android.content.pm.InstallSourceInfo getInstallSourceInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); + method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags); method @NonNull public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int); - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); + method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); method @NonNull public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(@NonNull android.content.pm.PackageManager.PackageInfoFlags); method @Deprecated @Nullable public abstract String getInstallerPackageName(@NonNull String); method @NonNull public abstract byte[] getInstantAppCookie(); @@ -12485,20 +12483,20 @@ package android.content.pm { method @NonNull public java.util.Set<java.lang.String> getMimeGroup(@NonNull String); method @NonNull public android.content.pm.ModuleInfo getModuleInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract String getNameForUid(int); - method @Deprecated @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int); + method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int); method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags); method public abstract int[] getPackageGids(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public int[] getPackageGids(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.content.pm.PackageInstaller getPackageInstaller(); - method @Deprecated public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public int getPackageUid(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract String[] getPackagesForUid(int); - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int); + method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int); method @NonNull public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], @NonNull android.content.pm.PackageManager.PackageInfoFlags); method @NonNull public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PermissionInfo getPermissionInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -12507,17 +12505,17 @@ package android.content.pm { method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int); method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo, @Nullable android.content.res.Configuration) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int); + method @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int); method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(@NonNull android.content.pm.PackageManager.PackageInfoFlags); method @Nullable public android.os.Bundle getSuspendedPackageAppExtras(); method public boolean getSyntheticAppDetailsActivityEnabled(@NonNull String); @@ -12546,18 +12544,18 @@ package android.content.pm { method public abstract boolean isSafeMode(); method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String); method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String); - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int); method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags); - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int); + method @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int); method @NonNull public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, @NonNull android.content.pm.PackageManager.ComponentInfoFlags); method @NonNull public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(@NonNull String, int); - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int); method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags); - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int); method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable java.util.List<android.content.Intent>, @NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags); - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int); method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags); - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int); method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags); method @NonNull public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(@Nullable String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryProviderProperty(@NonNull String); @@ -12568,11 +12566,11 @@ package android.content.pm { method public abstract void removePermission(@NonNull String); method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int); method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int); + method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int); method @Nullable public android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags); - method @Deprecated @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int); + method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int); method @Nullable public android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, @NonNull android.content.pm.PackageManager.ComponentInfoFlags); - method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int); + method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int); method @Nullable public android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags); method public abstract void setApplicationCategoryHint(@NonNull String, int); method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int); @@ -19370,7 +19368,7 @@ package android.hardware.camera2.params { public final class OutputConfiguration implements android.os.Parcelable { ctor public OutputConfiguration(@NonNull android.view.Surface); ctor public OutputConfiguration(int, @NonNull android.view.Surface); - ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>); + ctor public <T> OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>); method public void addSensorPixelModeUsed(int); method public void addSurface(@NonNull android.view.Surface); method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader); @@ -19529,9 +19527,9 @@ package android.hardware.display { public final class DisplayManager { method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int); - method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int); method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler); - method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, float, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback); + method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig); + method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback); method public android.view.Display getDisplay(int); method public android.view.Display[] getDisplays(); method public android.view.Display[] getDisplays(String); @@ -19586,6 +19584,30 @@ package android.hardware.display { method public void onStopped(); } + public final class VirtualDisplayConfig implements android.os.Parcelable { + method public int describeContents(); + method public int getDensityDpi(); + method @NonNull public java.util.List<java.lang.String> getDisplayCategories(); + method public int getFlags(); + method public int getHeight(); + method @NonNull public String getName(); + method public float getRequestedRefreshRate(); + method @Nullable public android.view.Surface getSurface(); + method public int getWidth(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.display.VirtualDisplayConfig> CREATOR; + } + + public static final class VirtualDisplayConfig.Builder { + ctor public VirtualDisplayConfig.Builder(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int); + method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder addDisplayCategory(@NonNull String); + method @NonNull public android.hardware.display.VirtualDisplayConfig build(); + method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCategories(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setFlags(int); + method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setRequestedRefreshRate(@FloatRange(from=0.0f) float); + method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setSurface(@Nullable android.view.Surface); + } + } package android.hardware.fingerprint { @@ -42066,6 +42088,7 @@ package android.telecom { } public final class CallControl { + method public void answer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); method @NonNull public android.os.ParcelUuid getCallId(); method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 47d9ab6d4b8f..3307a4fab922 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -588,6 +588,7 @@ package android.app { method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int); method @RequiresPermission(value="android.permission.WATCH_APPOPS", conditional=true) public void startWatchingNoted(@NonNull String[], @NonNull android.app.AppOpsManager.OnOpNotedListener); + method @RequiresPermission(value="android.permission.WATCH_APPOPS", conditional=true) public void startWatchingNoted(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpNotedListener); method public void stopWatchingNoted(@NonNull android.app.AppOpsManager.OnOpNotedListener); field public static final int HISTORY_FLAGS_ALL = 3; // 0x3 field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1 @@ -3216,8 +3217,8 @@ package android.companion.virtual { method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); method @NonNull public android.content.Context createContext(); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback); - method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback); - method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull java.util.List<java.lang.String>, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback); + method @Deprecated @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback); + method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull android.hardware.display.VirtualDisplayConfig, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.input.VirtualDpadConfig); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.input.VirtualKeyboardConfig); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); @@ -3346,10 +3347,15 @@ package android.companion.virtual.sensor { public interface VirtualSensorCallback { method public void onConfigurationChanged(@NonNull android.companion.virtual.sensor.VirtualSensor, boolean, @NonNull java.time.Duration, @NonNull java.time.Duration); + method public default void onDirectChannelConfigured(@IntRange(from=1) int, @NonNull android.companion.virtual.sensor.VirtualSensor, int, @IntRange(from=1) int); + method public default void onDirectChannelCreated(@IntRange(from=1) int, @NonNull android.os.SharedMemory); + method public default void onDirectChannelDestroyed(@IntRange(from=1) int); } public final class VirtualSensorConfig implements android.os.Parcelable { method public int describeContents(); + method public int getDirectChannelTypesSupported(); + method public int getHighestDirectReportRateLevel(); method @NonNull public String getName(); method public int getType(); method @Nullable public String getVendor(); @@ -3360,6 +3366,8 @@ package android.companion.virtual.sensor { public static final class VirtualSensorConfig.Builder { ctor public VirtualSensorConfig.Builder(int, @NonNull String); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build(); + method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setDirectChannelTypesSupported(int); + method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setHighestDirectReportRateLevel(int); method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String); } @@ -3864,15 +3872,15 @@ package android.content.pm { method @NonNull public boolean canUserUninstall(@NonNull String, @NonNull android.os.UserHandle); method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_METADATA) public android.os.PersistableBundle getAppMetadata(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.dex.ArtManager getArtManager(); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, int); + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, int); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags); method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract String getDefaultBrowserPackageNameAsUser(int); method @Nullable @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public CharSequence getHarmfulAppWarning(@NonNull String); method @Nullable public String getIncidentReportApproverPackageName(); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(@NonNull android.content.pm.PackageManager.PackageInfoFlags, int); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract android.graphics.drawable.Drawable getInstantAppIcon(String); method @Nullable public abstract android.content.ComponentName getInstantAppInstallerComponent(); @@ -3886,13 +3894,13 @@ package android.content.pm { method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @Deprecated public abstract int installExistingPackage(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public abstract int installExistingPackage(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle); + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle); method public abstract void registerDexModule(@NonNull String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback); method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener); @@ -10022,7 +10030,7 @@ package android.net.wifi.sharedconnectivity.app { method public int describeContents(); method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo(); method public int getNetworkSource(); - method @NonNull public int[] getSecurityTypes(); + method @NonNull public java.util.Set<java.lang.Integer> getSecurityTypes(); method @NonNull public String getSsid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.KnownNetwork> CREATOR; @@ -10032,10 +10040,10 @@ package android.net.wifi.sharedconnectivity.app { public static final class KnownNetwork.Builder { ctor public KnownNetwork.Builder(); + method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder addSecurityType(int); method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork build(); method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo); method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkSource(int); - method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSecurityTypes(@NonNull int[]); method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSsid(@NonNull String); } @@ -10075,6 +10083,11 @@ package android.net.wifi.sharedconnectivity.app { method public boolean connectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork); method public boolean disconnectTetherNetwork(@NonNull android.net.wifi.sharedconnectivity.app.TetherNetwork); method public boolean forgetKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork); + method @Nullable public android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus getKnownNetworkConnectionStatus(); + method @NonNull public java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork> getKnownNetworks(); + method @Nullable public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState getSettingsState(); + method @Nullable public android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus getTetherNetworkConnectionStatus(); + method @NonNull public java.util.List<android.net.wifi.sharedconnectivity.app.TetherNetwork> getTetherNetworks(); method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback); method public boolean unregisterCallback(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback); } @@ -10099,7 +10112,7 @@ package android.net.wifi.sharedconnectivity.app { method public long getDeviceId(); method @NonNull public android.net.wifi.sharedconnectivity.app.DeviceInfo getDeviceInfo(); method @Nullable public String getHotspotBssid(); - method @Nullable public int[] getHotspotSecurityTypes(); + method @NonNull public java.util.Set<java.lang.Integer> getHotspotSecurityTypes(); method @Nullable public String getHotspotSsid(); method @NonNull public String getNetworkName(); method public int getNetworkType(); @@ -10113,11 +10126,11 @@ package android.net.wifi.sharedconnectivity.app { public static final class TetherNetwork.Builder { ctor public TetherNetwork.Builder(); + method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder addHotspotSecurityType(int); method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork build(); method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceId(long); method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setDeviceInfo(@NonNull android.net.wifi.sharedconnectivity.app.DeviceInfo); method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotBssid(@NonNull String); - method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSecurityTypes(@NonNull int[]); method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setHotspotSsid(@NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkName(@NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.TetherNetwork.Builder setNetworkType(int); @@ -17277,9 +17290,9 @@ package android.view.accessibility { public final class AccessibilityManager { method public int getAccessibilityWindowId(@Nullable android.os.IBinder); method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut(); - method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean registerDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy); + method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_ACCESSIBILITY, android.Manifest.permission.CREATE_VIRTUAL_DEVICE}) public boolean registerDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy); method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int); - method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean unregisterDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy); + method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_ACCESSIBILITY, android.Manifest.permission.CREATE_VIRTUAL_DEVICE}) public boolean unregisterDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy); method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 640506dace91..43fa6e262a14 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -145,7 +145,8 @@ package android.app { method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle(); field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6 - field public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8 + field @Deprecated public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8 + field public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 8; // 0x8 field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4 field public static final int PROCESS_STATE_TOP = 2; // 0x2 field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff @@ -930,7 +931,7 @@ package android.content.pm { method @Nullable public String getDefaultTextClassifierPackageName(); method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public android.os.IBinder getHoldLockToken(); method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle); - method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int); + method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int); method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags, int); method @Nullable public abstract String[] getNamesForUids(int[]); method @NonNull public String getPermissionControllerPackageName(); @@ -2180,6 +2181,7 @@ package android.os { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser(); + method public int getDisplayIdAssignedToUser(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean); @@ -3546,6 +3548,8 @@ package android.view.inputmethod { public final class InputMethodInfo implements android.os.Parcelable { ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String); ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int); + field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8 + field public static final int MAX_IMES_PER_PACKAGE = 20; // 0x14 } public final class InputMethodManager { diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 6422865c043a..3615435b7d75 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -198,6 +198,26 @@ import java.util.function.Consumer; * possible for a node to contain outdated information because the window content may change at any * time. * </p> + * <h3>Drawing Accessibility Overlays</h3> + * <p>Accessibility services can draw overlays on top of existing screen contents. + * Accessibility overlays can be used to visually highlight items on the screen + * e.g. indicate the current item with accessibility focus. + * Overlays can also offer the user a way to interact with the service directly and quickly + * customize the service's behavior.</p> + * <p>Accessibility overlays can be attached to a particular window or to the display itself. + * Attaching an overlay to a window allows the overly to move, grow and shrink as the window does. + * The overlay will maintain the same relative position within the window bounds as the window + * moves. The overlay will also maintain the same relative position within the window bounds if + * the window is resized. + * To attach an overlay to a window, use {@link attachAccessibilityOverlayToWindow}. + * Attaching an overlay to the display means that the overlay is independent of the active + * windows on that display. + * To attach an overlay to a display, use {@link attachAccessibilityOverlayToDisplay}. </p> + * <p> When positioning an overlay that is attached to a window, the service must use window + * coordinates. In order to position an overlay on top of an existing UI element it is necessary + * to know the bounds of that element in window coordinates. To find the bounds in window + * coordinates of an element, find the corresponding {@link AccessibilityNodeInfo} as discussed + * above and call {@link AccessibilityNodeInfo#getBoundsInWindow}. </p> * <h3>Notification strategy</h3> * <p> * All accessibility services are notified of all events they have requested, regardless of their @@ -3421,22 +3441,28 @@ public abstract class AccessibilityService extends Service { } /** - * Attaches a {@link android.view.SurfaceControl} containing an accessibility + * <p>Attaches a {@link android.view.SurfaceControl} containing an accessibility * overlay to the * specified display. This type of overlay should be used for content that does * not need to * track the location and size of Views in the currently active app e.g. service * configuration - * or general service UI. To remove this overlay and free the associated + * or general service UI.</p> + * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. + * To embed the View into a {@link android.view.SurfaceControl}, create a + * {@link android.view.SurfaceControlViewHost} and attach the View using + * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by + * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p> + * <p>To remove this overlay and free the associated * resources, use - * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>. - * If the specified overlay has already been attached to the specified display + * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p> + * <p>If the specified overlay has already been attached to the specified display * this method does nothing. * If the specified overlay has already been attached to a previous display this * function will transfer the overlay to the new display. * Services can attach multiple overlays. Use * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. - * to coordinate the order of the overlays on screen. + * to coordinate the order of the overlays on screen.</p> * * @param displayId the display to which the SurfaceControl should be attached. * @param sc the SurfaceControl containing the overlay content @@ -3456,20 +3482,24 @@ public abstract class AccessibilityService extends Service { } /** - * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the + * <p>Attaches an accessibility overlay {@link android.view.SurfaceControl} to the * specified * window. This method should be used when you want the overlay to move and - * resize as the parent - * window moves and resizes. To remove this overlay and free the associated - * resources, use - * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>. - * If the specified overlay has already been attached to the specified window + * resize as the parent window moves and resizes.</p> + * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. + * To embed the View into a {@link android.view.SurfaceControl}, create a + * {@link android.view.SurfaceControlViewHost} and attach the View using + * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by + * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p> + * <p>To remove this overlay and free the associated resources, use + * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p> + * <p>If the specified overlay has already been attached to the specified window * this method does nothing. * If the specified overlay has already been attached to a previous window this * function will transfer the overlay to the new window. * Services can attach multiple overlays. Use * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. - * to coordinate the order of the overlays on screen. + * to coordinate the order of the overlays on screen.</p> * * @param accessibilityWindowId The window id, from * {@link AccessibilityWindowInfo#getId()}. diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 8b6d7cbaa449..ba36d937edc3 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -751,9 +751,17 @@ public class ActivityManager { @SystemApi public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2; - /** @hide Process can access network despite any power saving resrictions */ + /** @hide Process can access network despite any power saving restrictions */ @TestApi - public static final int PROCESS_CAPABILITY_NETWORK = 1 << 3; + public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 1 << 3; + /** + * @hide + * @deprecated Use {@link #PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK} instead. + */ + @TestApi + @Deprecated + public static final int PROCESS_CAPABILITY_NETWORK = + PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; /** * Flag used to indicate whether an app is allowed to start a foreground service from the diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b50245d14d3b..50275abe249a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -36,6 +36,7 @@ import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL; import android.annotation.NonNull; import android.annotation.Nullable; @@ -49,6 +50,7 @@ import android.app.assist.AssistStructure; import android.app.backup.BackupAgent; import android.app.backup.BackupAnnotations.BackupDestination; import android.app.backup.BackupAnnotations.OperationType; +import android.app.compat.CompatChanges; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; import android.app.servertransaction.ActivityRelaunchItem; @@ -206,6 +208,7 @@ import com.android.internal.content.ReferrerIntent; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; +import com.android.internal.os.SafeZipPathValidatorCallback; import com.android.internal.os.SomeArgs; import com.android.internal.policy.DecorView; import com.android.internal.util.ArrayUtils; @@ -219,6 +222,7 @@ import dalvik.system.AppSpecializationHooks; import dalvik.system.CloseGuard; import dalvik.system.VMDebug; import dalvik.system.VMRuntime; +import dalvik.system.ZipPathValidator; import libcore.io.ForwardingOs; import libcore.io.IoUtils; @@ -4621,7 +4625,7 @@ public final class ActivityThread extends ClientTransactionHandler ActivityManager.getService()); if (!service.isUiContext()) { // WindowProviderService is a UI Context. VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class); - if (mLastReportedDeviceId == VirtualDeviceManager.DEVICE_ID_DEFAULT + if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) { service.updateDeviceId(mLastReportedDeviceId); } @@ -6149,7 +6153,7 @@ public final class ActivityThread extends ClientTransactionHandler private void updateDeviceIdForNonUIContexts(int deviceId) { // Invalid device id is treated as a no-op. - if (deviceId == VirtualDeviceManager.DEVICE_ID_INVALID) { + if (deviceId == Context.DEVICE_ID_INVALID) { return; } if (deviceId == mLastReportedDeviceId) { @@ -6703,6 +6707,11 @@ public final class ActivityThread extends ClientTransactionHandler // Let libcore handle any compat changes after installing the list of compat changes. AppSpecializationHooks.handleCompatChangesBeforeBindingApplication(); + // Initialize the zip path validator callback depending on the targetSdk. + // This has to be after AppCompatCallbacks#install() so that the Compat + // checks work accordingly. + initZipPathValidatorCallback(); + mBoundApplication = data; mConfigurationController.setConfiguration(data.config); mConfigurationController.setCompatConfiguration(data.config); @@ -7070,6 +7079,18 @@ public final class ActivityThread extends ClientTransactionHandler } } + /** + * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip + * entry names. + * Otherwise: clear the callback to the default validation. + */ + private void initZipPathValidatorCallback() { + if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) { + ZipPathValidator.setCallback(new SafeZipPathValidatorCallback()); + } else { + ZipPathValidator.clearCallback(); + } + } private void handleSetContentCaptureOptionsCallback(String packageName) { if (mContentCaptureOptionsCallback != null) { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 40ed269f57fd..9bf9e0c8eec3 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -25,6 +25,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.StringDef; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -1464,6 +1465,149 @@ public class AppOpsManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static final int _NUM_OP = 134; + /** + * All app ops represented as strings. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = { "OPSTR_" }, value = { + OPSTR_COARSE_LOCATION, + OPSTR_FINE_LOCATION, + OPSTR_MONITOR_LOCATION, + OPSTR_MONITOR_HIGH_POWER_LOCATION, + OPSTR_GET_USAGE_STATS, + OPSTR_ACTIVATE_VPN, + OPSTR_READ_CONTACTS, + OPSTR_WRITE_CONTACTS, + OPSTR_READ_CALL_LOG, + OPSTR_WRITE_CALL_LOG, + OPSTR_READ_CALENDAR, + OPSTR_WRITE_CALENDAR, + OPSTR_CALL_PHONE, + OPSTR_READ_SMS, + OPSTR_RECEIVE_SMS, + OPSTR_RECEIVE_MMS, + OPSTR_RECEIVE_WAP_PUSH, + OPSTR_SEND_SMS, + OPSTR_CAMERA, + OPSTR_RECORD_AUDIO, + OPSTR_READ_PHONE_STATE, + OPSTR_ADD_VOICEMAIL, + OPSTR_USE_SIP, + OPSTR_PROCESS_OUTGOING_CALLS, + OPSTR_USE_FINGERPRINT, + OPSTR_BODY_SENSORS, + OPSTR_READ_CELL_BROADCASTS, + OPSTR_MOCK_LOCATION, + OPSTR_READ_EXTERNAL_STORAGE, + OPSTR_WRITE_EXTERNAL_STORAGE, + OPSTR_SYSTEM_ALERT_WINDOW, + OPSTR_WRITE_SETTINGS, + OPSTR_GET_ACCOUNTS, + OPSTR_READ_PHONE_NUMBERS, + OPSTR_PICTURE_IN_PICTURE, + OPSTR_INSTANT_APP_START_FOREGROUND, + OPSTR_ANSWER_PHONE_CALLS, + OPSTR_ACCEPT_HANDOVER, + OPSTR_GPS, + OPSTR_VIBRATE, + OPSTR_WIFI_SCAN, + OPSTR_POST_NOTIFICATION, + OPSTR_NEIGHBORING_CELLS, + OPSTR_WRITE_SMS, + OPSTR_RECEIVE_EMERGENCY_BROADCAST, + OPSTR_READ_ICC_SMS, + OPSTR_WRITE_ICC_SMS, + OPSTR_ACCESS_NOTIFICATIONS, + OPSTR_PLAY_AUDIO, + OPSTR_READ_CLIPBOARD, + OPSTR_WRITE_CLIPBOARD, + OPSTR_TAKE_MEDIA_BUTTONS, + OPSTR_TAKE_AUDIO_FOCUS, + OPSTR_AUDIO_MASTER_VOLUME, + OPSTR_AUDIO_VOICE_VOLUME, + OPSTR_AUDIO_RING_VOLUME, + OPSTR_AUDIO_MEDIA_VOLUME, + OPSTR_AUDIO_ALARM_VOLUME, + OPSTR_AUDIO_NOTIFICATION_VOLUME, + OPSTR_AUDIO_BLUETOOTH_VOLUME, + OPSTR_WAKE_LOCK, + OPSTR_MUTE_MICROPHONE, + OPSTR_TOAST_WINDOW, + OPSTR_PROJECT_MEDIA, + OPSTR_WRITE_WALLPAPER, + OPSTR_ASSIST_STRUCTURE, + OPSTR_ASSIST_SCREENSHOT, + OPSTR_TURN_SCREEN_ON, + OPSTR_RUN_IN_BACKGROUND, + OPSTR_AUDIO_ACCESSIBILITY_VOLUME, + OPSTR_REQUEST_INSTALL_PACKAGES, + OPSTR_RUN_ANY_IN_BACKGROUND, + OPSTR_CHANGE_WIFI_STATE, + OPSTR_REQUEST_DELETE_PACKAGES, + OPSTR_BIND_ACCESSIBILITY_SERVICE, + OPSTR_MANAGE_IPSEC_TUNNELS, + OPSTR_START_FOREGROUND, + OPSTR_BLUETOOTH_SCAN, + OPSTR_BLUETOOTH_CONNECT, + OPSTR_BLUETOOTH_ADVERTISE, + OPSTR_USE_BIOMETRIC, + OPSTR_ACTIVITY_RECOGNITION, + OPSTR_SMS_FINANCIAL_TRANSACTIONS, + OPSTR_READ_MEDIA_AUDIO, + OPSTR_WRITE_MEDIA_AUDIO, + OPSTR_READ_MEDIA_VIDEO, + OPSTR_WRITE_MEDIA_VIDEO, + OPSTR_READ_MEDIA_IMAGES, + OPSTR_WRITE_MEDIA_IMAGES, + OPSTR_LEGACY_STORAGE, + OPSTR_ACCESS_MEDIA_LOCATION, + OPSTR_ACCESS_ACCESSIBILITY, + OPSTR_READ_DEVICE_IDENTIFIERS, + OPSTR_QUERY_ALL_PACKAGES, + OPSTR_MANAGE_EXTERNAL_STORAGE, + OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, + OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER, + OPSTR_INTERACT_ACROSS_PROFILES, + OPSTR_ACTIVATE_PLATFORM_VPN, + OPSTR_LOADER_USAGE_STATS, + OPSTR_MANAGE_ONGOING_CALLS, + OPSTR_NO_ISOLATED_STORAGE, + OPSTR_PHONE_CALL_MICROPHONE, + OPSTR_PHONE_CALL_CAMERA, + OPSTR_RECORD_AUDIO_HOTWORD, + OPSTR_MANAGE_CREDENTIALS, + OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, + OPSTR_RECORD_AUDIO_OUTPUT, + OPSTR_SCHEDULE_EXACT_ALARM, + OPSTR_FINE_LOCATION_SOURCE, + OPSTR_COARSE_LOCATION_SOURCE, + OPSTR_MANAGE_MEDIA, + OPSTR_UWB_RANGING, + OPSTR_NEARBY_WIFI_DEVICES, + OPSTR_ACTIVITY_RECOGNITION_SOURCE, + OPSTR_RECORD_INCOMING_PHONE_AUDIO, + OPSTR_ESTABLISH_VPN_SERVICE, + OPSTR_ESTABLISH_VPN_MANAGER, + OPSTR_ACCESS_RESTRICTED_SETTINGS, + OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO, + OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, + OPSTR_READ_WRITE_HEALTH_DATA, + OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, + OPSTR_RUN_USER_INITIATED_JOBS, + OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION, + OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, + OPSTR_FOREGROUND_SERVICE_SPECIAL_USE, + OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS, + OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION, + OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, + OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, + OPSTR_BODY_SENSORS_WRIST_TEMPERATURE, + OPSTR_USE_FULL_SCREEN_INTENT, + }) + public @interface AppOpString {} + /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; /** Access to fine location information. */ @@ -7736,18 +7880,19 @@ public class AppOpsManager { } /** - * Start watching for noted app ops. An app op may be immediate or long running. - * Immediate ops are noted while long running ones are started and stopped. This - * method allows registering a listener to be notified when an app op is noted. If - * an op is being noted by any package you will get a callback. To change the - * watched ops for a registered callback you need to unregister and register it again. + * Start watching for noted app ops. * - * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission - * you can watch changes only for your UID. + * <p> Similar to {@link #startWatchingNoted(String[], Executor, OnOpNotedListener)}, but + * without an executor parameter. * - * @param ops The ops to watch. - * @param callback Where to report changes. + * <p> Note that the listener will be called on the main thread using + * {@link Context.getMainThread()}. To specify the execution thread, use + * {@link #startWatchingNoted(String[], Executor, OnOpNotedListener)}. + * + * @param ops the ops to watch + * @param listener listener to notify when an app op is noted * + * @see #startWatchingNoted(String[], Executor, OnOpNotedListener) * @see #stopWatchingNoted(OnOpNotedListener) * @see #noteOp(String, int, String, String, String) * @@ -7755,40 +7900,111 @@ public class AppOpsManager { */ @SystemApi @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) - public void startWatchingNoted(@NonNull String[] ops, @NonNull OnOpNotedListener callback) { + public void startWatchingNoted(@NonNull @AppOpString String[] ops, + @NonNull OnOpNotedListener listener) { final int[] intOps = new int[ops.length]; for (int i = 0; i < ops.length; i++) { intOps[i] = strOpToOp(ops[i]); } - startWatchingNoted(intOps, callback); + startWatchingNoted(intOps, listener); } /** - * Start watching for noted app ops. An app op may be immediate or long running. - * Immediate ops are noted while long running ones are started and stopped. This - * method allows registering a listener to be notified when an app op is noted. If - * an op is being noted by any package you will get a callback. To change the - * watched ops for a registered callback you need to unregister and register it again. + * Start watching for noted app ops. * - * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission - * you can watch changes only for your UID. + * <p> An app op may be immediate or long-running. Immediate ops are noted while long-running + * ones are started and stopped. * - * This allows observing noted ops by their raw op codes instead of string op names. + * <p> This method allows registering a listener to be notified when an app op is noted. To + * change the watched ops for a registered callback you need to unregister and register it + * again. * - * @param ops The ops to watch. - * @param callback Where to report changes. + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission you can + * watch changes only for your UID. + * + * @param ops the ops to watch + * @param executor the executor on which the listener will be notified + * @param listener listener to notify when an app op is noted + * + * @see #startWatchingNoted(String[], OnOpNotedListener) + * @see #stopWatchingNoted(OnOpNotedListener) + * @see #noteOp(String, int, String, String, String) + * + * @hide + */ + @SystemApi + @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) + public void startWatchingNoted(@NonNull @AppOpString String[] ops, + @CallbackExecutor @NonNull Executor executor, @NonNull OnOpNotedListener listener) { + final int[] intOps = new int[ops.length]; + for (int i = 0; i < ops.length; i++) { + intOps[i] = strOpToOp(ops[i]); + } + startWatchingNoted(intOps, executor, listener); + } + + /** + * Start watching for noted app ops. + * + * <p> Similar to {@link #startWatchingNoted(int[], Executor, OnOpNotedListener)}, but without + * an executor parameter. + * + * <p> This method is also similar to {@link #startWatchingNoted(String[], OnOpNotedListener)}, + * but allows observing noted ops by their raw op codes instead of string op names. + * + * <p> Note that the listener will be called on the main thread using + * {@link Context.getMainThread()}. To specify the execution thread, use + * {@link {@link #startWatchingNoted(String[], Executor, OnOpNotedListener)}. + * + * @param ops the ops to watch + * @param listener listener to notify when an app op is noted * * @see #startWatchingActive(int[], OnOpActiveChangedListener) * @see #startWatchingStarted(int[], OnOpStartedListener) * @see #startWatchingNoted(String[], OnOpNotedListener) + * @see #startWatchingNoted(int[], Executor, OnOpNotedListener) * * @hide */ @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) - public void startWatchingNoted(@NonNull int[] ops, @NonNull OnOpNotedListener callback) { + public void startWatchingNoted(@NonNull int[] ops, @NonNull OnOpNotedListener listener) { + startWatchingNoted(ops, mContext.getMainExecutor(), listener); + } + + /** + * Start watching for noted app ops. + * + * <p> This method is similar to + * {@link #startWatchingNoted(String[], Executor, OnOpNotedListener)}, but allows observing + * noted ops by their raw op codes instead of string op names. + * + * <p> An app op may be immediate or long-running. Immediate ops are noted while long-running + * ones are started and stopped. + * + * <p> This method allows registering a listener to be notified when an app op is noted. To + * change the watched ops for a registered callback you need to unregister and register it + * again. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission you + * can watch changes only for your UID. + * + * @param ops the ops to watch + * @param executor the executor on which the listener will be notified + * @param listener listener to notify when an app op is noted + * + * @see #startWatchingActive(int[], OnOpActiveChangedListener) + * @see #startWatchingStarted(int[], OnOpStartedListener) + * @see #startWatchingNoted(int[], Executor, OnOpNotedListener) + * @see #startWatchingNoted(String[], OnOpNotedListener) + * + * @hide + */ + @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) + public void startWatchingNoted(@NonNull int[] ops, + @CallbackExecutor @NonNull Executor executor, @NonNull OnOpNotedListener listener) { IAppOpsNotedCallback cb; synchronized (mNotedWatchers) { - cb = mNotedWatchers.get(callback); + cb = mNotedWatchers.get(listener); if (cb != null) { return; } @@ -7796,13 +8012,21 @@ public class AppOpsManager { @Override public void opNoted(int op, int uid, String packageName, String attributionTag, int flags, int mode) { - if (sAppOpInfos[op].name != null) { - callback.onOpNoted(sAppOpInfos[op].name, uid, packageName, attributionTag, - flags, mode); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + if (sAppOpInfos[op].name != null) { + listener.onOpNoted(sAppOpInfos[op].name, uid, packageName, + attributionTag, + flags, mode); + } + }); + } finally { + Binder.restoreCallingIdentity(identity); } } }; - mNotedWatchers.put(callback, cb); + mNotedWatchers.put(listener, cb); } try { mService.startWatchingNoted(ops, cb); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index a99815c20fe6..6301ad7f1278 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -556,12 +556,7 @@ public class ApplicationPackageManager extends PackageManager { @Override public ActivityInfo getActivityInfo(ComponentName className, ComponentInfoFlags flags) throws NameNotFoundException { - return getActivityInfoAsUser(className, flags, getUserId()); - } - - @Override - public ActivityInfo getActivityInfoAsUser(ComponentName className, - ComponentInfoFlags flags, @UserIdInt int userId) throws NameNotFoundException { + final int userId = getUserId(); try { ActivityInfo ai = mPM.getActivityInfo(className, updateFlagsForComponent(flags.getValue(), userId, null), userId); diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index fe40a4ce3d13..f48181b95892 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -60,7 +60,6 @@ public class BroadcastOptions extends ComponentOptions { private String[] mRequireNoneOfPermissions; private long mRequireCompatChangeId = CHANGE_INVALID; private long mIdForResponseEvent; - private @Nullable IntentFilter mRemoveMatchingFilter; private @DeliveryGroupPolicy int mDeliveryGroupPolicy; private @Nullable String mDeliveryGroupMatchingKey; private @Nullable BundleMerger mDeliveryGroupExtrasMerger; @@ -190,12 +189,6 @@ public class BroadcastOptions extends ComponentOptions { "android:broadcast.idForResponseEvent"; /** - * Corresponds to {@link #setRemoveMatchingFilter}. - */ - private static final String KEY_REMOVE_MATCHING_FILTER = - "android:broadcast.removeMatchingFilter"; - - /** * Corresponds to {@link #setDeliveryGroupPolicy(int)}. */ private static final String KEY_DELIVERY_GROUP_POLICY = @@ -274,18 +267,6 @@ public class BroadcastOptions extends ComponentOptions { } /** - * {@hide} - * @deprecated use {@link #setDeliveryGroupMatchingFilter(IntentFilter)} instead. - */ - @Deprecated - public static @NonNull BroadcastOptions makeRemovingMatchingFilter( - @NonNull IntentFilter removeMatchingFilter) { - BroadcastOptions opts = new BroadcastOptions(); - opts.setRemoveMatchingFilter(removeMatchingFilter); - return opts; - } - - /** * Creates a new {@code BroadcastOptions} with no options initially set. */ public BroadcastOptions() { @@ -315,8 +296,6 @@ public class BroadcastOptions extends ComponentOptions { mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS); mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID); mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT); - mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER, - IntentFilter.class); mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY, DELIVERY_GROUP_POLICY_ALL); mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY); @@ -797,31 +776,6 @@ public class BroadcastOptions extends ComponentOptions { } /** - * When enqueuing this broadcast, remove all pending broadcasts previously - * sent by this app which match the given filter. - * <p> - * For example, sending {@link Intent#ACTION_SCREEN_ON} would typically want - * to remove any pending {@link Intent#ACTION_SCREEN_OFF} broadcasts. - * - * @hide - * @deprecated use {@link #setDeliveryGroupMatchingFilter(IntentFilter)} instead. - */ - @Deprecated - public void setRemoveMatchingFilter(@NonNull IntentFilter removeMatchingFilter) { - mRemoveMatchingFilter = Objects.requireNonNull(removeMatchingFilter); - } - - /** @hide */ - public void clearRemoveMatchingFilter() { - mRemoveMatchingFilter = null; - } - - /** @hide */ - public @Nullable IntentFilter getRemoveMatchingFilter() { - return mRemoveMatchingFilter; - } - - /** * Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to * the same delivery group has to be handled. * @@ -1092,9 +1046,6 @@ public class BroadcastOptions extends ComponentOptions { if (mIdForResponseEvent != 0) { b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent); } - if (mRemoveMatchingFilter != null) { - b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter); - } if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) { b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 1c0be686330d..e3ec4937ba2c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -245,7 +245,7 @@ class ContextImpl extends Context { @UnsupportedAppUsage private @NonNull Resources mResources; private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet. - private int mDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT; + private int mDeviceId = Context.DEVICE_ID_DEFAULT; /** * If set to {@code true} the resources for this context will be configured for mDisplay which @@ -2812,7 +2812,7 @@ class ContextImpl extends Context { @Override public @NonNull Context createDeviceContext(int deviceId) { - if (deviceId != VirtualDeviceManager.DEVICE_ID_DEFAULT) { + if (deviceId != Context.DEVICE_ID_DEFAULT) { VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class); if (!vdm.isValidVirtualDeviceId(deviceId)) { throw new IllegalArgumentException( @@ -3092,7 +3092,7 @@ class ContextImpl extends Context { @Override public void updateDeviceId(int updatedDeviceId) { - if (updatedDeviceId != VirtualDeviceManager.DEVICE_ID_DEFAULT) { + if (updatedDeviceId != Context.DEVICE_ID_DEFAULT) { VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class); if (!vdm.isValidVirtualDeviceId(updatedDeviceId)) { throw new IllegalArgumentException( diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index f653e132f7e3..5c38c42fe21f 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -341,12 +341,6 @@ interface IActivityManager { in String message, boolean force, int exceptionTypeId); void crashApplicationWithTypeWithExtras(int uid, int initialPid, in String packageName, int userId, in String message, boolean force, int exceptionTypeId, in Bundle extras); - /** @deprecated -- use getProviderMimeTypeAsync */ - @UnsupportedAppUsage(maxTargetSdk = 29, publicAlternatives = - "Use {@link android.content.ContentResolver#getType} public API instead.") - String getProviderMimeType(in Uri uri, int userId); - - oneway void getProviderMimeTypeAsync(in Uri uri, int userId, in RemoteCallback resultCallback); oneway void getMimeTypeFilterAsync(in Uri uri, int userId, in RemoteCallback resultCallback); // Cause the specified process to dump the specified heap. boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo, diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index ef5cd9334be7..440ee202cc5b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5758,7 +5758,7 @@ public class Notification implements Parcelable List<Notification.Action> nonContextualActions = getNonContextualActions(); int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS); - boolean emphazisedMode = mN.fullScreenIntent != null + boolean emphasizedMode = mN.fullScreenIntent != null || p.mCallStyleActions || ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0); @@ -5771,7 +5771,7 @@ public class Notification implements Parcelable big.setInt(R.id.actions, "setCollapsibleIndentDimen", R.dimen.call_notification_collapsible_indent); } - big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode); + big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode); if (numActions > 0 && !p.mHideActions) { big.setViewVisibility(R.id.actions_container, View.VISIBLE); big.setViewVisibility(R.id.actions, View.VISIBLE); @@ -5783,12 +5783,12 @@ public class Notification implements Parcelable boolean actionHasValidInput = hasValidRemoteInput(action); validRemoteInput |= actionHasValidInput; - final RemoteViews button = generateActionButton(action, emphazisedMode, p); - if (actionHasValidInput && !emphazisedMode) { + final RemoteViews button = generateActionButton(action, emphasizedMode, p); + if (actionHasValidInput && !emphasizedMode) { // Clear the drawable button.setInt(R.id.action0, "setBackgroundResource", 0); } - if (emphazisedMode && i > 0) { + if (emphasizedMode && i > 0) { // Clear start margin from non-first buttons to reduce the gap between them. // (8dp remaining gap is from all buttons' standard 4dp inset). button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0); @@ -7475,10 +7475,10 @@ public class Notification implements Parcelable Resources resources = context.getResources(); boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); if (mPictureIcon != null) { - int maxPictureWidth = resources.getDimensionPixelSize(isLowRam + int maxPictureHeight = resources.getDimensionPixelSize(isLowRam ? R.dimen.notification_big_picture_max_height_low_ram : R.dimen.notification_big_picture_max_height); - int maxPictureHeight = resources.getDimensionPixelSize(isLowRam + int maxPictureWidth = resources.getDimensionPixelSize(isLowRam ? R.dimen.notification_big_picture_max_width_low_ram : R.dimen.notification_big_picture_max_width); mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 91efa755e4c9..d2f2c3c4e95a 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -873,7 +873,7 @@ public class NotificationManager { * permission to your manifest, and use * {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT}. */ - public boolean canSendFullScreenIntent() { + public boolean canUseFullScreenIntent() { final int result = PermissionChecker.checkPermissionForPreflight(mContext, android.Manifest.permission.USE_FULL_SCREEN_INTENT, mContext.getAttributionSource()); diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index 50a7da1cede5..5ee10a50568d 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -115,6 +115,20 @@ public interface TimeDetector { String SHELL_COMMAND_CONFIRM_TIME = "confirm_time"; /** + * A shell command that clears the network time signal used by {@link + * SystemClock#currentNetworkTimeClock()}. + * @hide + */ + String SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME = "clear_system_clock_network_time"; + + /** + * A shell command that sets the network time signal used by {@link + * SystemClock#currentNetworkTimeClock()}. + * @hide + */ + String SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME = "set_system_clock_network_time"; + + /** * A shared utility method to create a {@link ManualTimeSuggestion}. * * @hide diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java index f3f433540ef7..4a09186570e0 100644 --- a/core/java/android/companion/virtual/VirtualDevice.java +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -18,6 +18,7 @@ package android.companion.virtual; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.os.Parcel; import android.os.Parcelable; @@ -38,9 +39,9 @@ public final class VirtualDevice implements Parcelable { * @hide */ public VirtualDevice(int id, @Nullable String name) { - if (id <= VirtualDeviceManager.DEVICE_ID_DEFAULT) { + if (id <= Context.DEVICE_ID_DEFAULT) { throw new IllegalArgumentException("VirtualDevice ID mist be greater than " - + VirtualDeviceManager.DEVICE_ID_DEFAULT); + + Context.DEVICE_ID_DEFAULT); } mId = id; mName = name; diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index ae43c6eb8b85..6cc4c8a24c48 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -89,24 +89,6 @@ public final class VirtualDeviceManager { private static final String TAG = "VirtualDeviceManager"; - private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS = - DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC - | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT - | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY - | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL - | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH - | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; - - /** - * The default device ID, which is the ID of the primary (non-virtual) device. - */ - public static final int DEVICE_ID_DEFAULT = 0; - - /** - * Invalid device ID. - */ - public static final int DEVICE_ID_INVALID = -1; - /** * Broadcast Action: A Virtual Device was removed. * @@ -250,7 +232,7 @@ public final class VirtualDeviceManager { public int getDeviceIdForDisplayId(int displayId) { if (mService == null) { Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service."); - return DEVICE_ID_DEFAULT; + return Context.DEVICE_ID_DEFAULT; } try { return mService.getDeviceIdForDisplayId(displayId); @@ -261,7 +243,7 @@ public final class VirtualDeviceManager { /** * Checks whether the passed {@code deviceId} is a valid virtual device ID or not. - * {@link VirtualDeviceManager#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default + * {@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default * device which is not a virtual device. {@code deviceId} must correspond to a virtual device * created by {@link VirtualDeviceManager#createVirtualDevice(int, VirtualDeviceParams)}. * @@ -549,7 +531,11 @@ public final class VirtualDeviceManager { * not create the virtual display. * * @see DisplayManager#createVirtualDisplay + * + * @deprecated use {@link #createVirtualDisplay(VirtualDisplayConfig, Executor, + * VirtualDisplay.Callback)} */ + @Deprecated @Nullable public VirtualDisplay createVirtualDisplay( @IntRange(from = 1) int width, @@ -562,30 +548,16 @@ public final class VirtualDeviceManager { VirtualDisplayConfig config = new VirtualDisplayConfig.Builder( getVirtualDisplayName(), width, height, densityDpi) .setSurface(surface) - .setFlags(getVirtualDisplayFlags(flags)) + .setFlags(flags) .build(); - return createVirtualDisplayInternal(config, executor, callback); + return createVirtualDisplay(config, executor, callback); } /** * Creates a virtual display for this virtual device. All displays created on the same * device belongs to the same display group. * - * @param width The width of the virtual display in pixels, must be greater than 0. - * @param height The height of the virtual display in pixels, must be greater than 0. - * @param densityDpi The density of the virtual display in dpi, must be greater than 0. - * @param displayCategories The categories of the virtual display, indicating the type of - * activities allowed to run on the display. Activities can declare their type using - * {@link android.content.pm.ActivityInfo#requiredDisplayCategory}. - * @param surface The surface to which the content of the virtual display should - * be rendered, or null if there is none initially. The surface can also be set later using - * {@link VirtualDisplay#setSurface(Surface)}. - * @param flags A combination of virtual display flags accepted by - * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are - * automatically set for all virtual devices: - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY - * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}. + * @param config The configuration of the display. * @param executor The executor on which {@code callback} will be invoked. This is ignored * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must * not be null. @@ -597,28 +569,6 @@ public final class VirtualDeviceManager { */ @Nullable public VirtualDisplay createVirtualDisplay( - @IntRange(from = 1) int width, - @IntRange(from = 1) int height, - @IntRange(from = 1) int densityDpi, - @NonNull List<String> displayCategories, - @Nullable Surface surface, - @VirtualDisplayFlag int flags, - @Nullable @CallbackExecutor Executor executor, - @Nullable VirtualDisplay.Callback callback) { - VirtualDisplayConfig config = new VirtualDisplayConfig.Builder( - getVirtualDisplayName(), width, height, densityDpi) - .setDisplayCategories(displayCategories) - .setSurface(surface) - .setFlags(getVirtualDisplayFlags(flags)) - .build(); - return createVirtualDisplayInternal(config, executor, callback); - } - - /** - * @hide - */ - @Nullable - private VirtualDisplay createVirtualDisplayInternal( @NonNull VirtualDisplayConfig config, @Nullable @CallbackExecutor Executor executor, @Nullable VirtualDisplay.Callback callback) { @@ -907,16 +857,6 @@ public final class VirtualDeviceManager { } } - /** - * Returns the display flags that should be added to a particular virtual display. - * Additional device-level flags from {@link - * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will - * be added by DisplayManagerService. - */ - private int getVirtualDisplayFlags(int flags) { - return DEFAULT_VIRTUAL_DISPLAY_FLAGS | flags; - } - private String getVirtualDisplayName() { try { // Currently this just use the device ID, which means all of the virtual displays diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index d8076b5c0fd7..9f3b60148004 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -35,6 +35,7 @@ import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; +import android.os.SharedMemory; import android.os.UserHandle; import android.util.ArraySet; import android.util.SparseArray; @@ -577,6 +578,25 @@ public final class VirtualDeviceParams implements Parcelable { mExecutor.execute(() -> mCallback.onConfigurationChanged( sensor, enabled, samplingPeriod, batchReportingLatency)); } + + @Override + public void onDirectChannelCreated(int channelHandle, + @NonNull SharedMemory sharedMemory) { + mExecutor.execute( + () -> mCallback.onDirectChannelCreated(channelHandle, sharedMemory)); + } + + @Override + public void onDirectChannelDestroyed(int channelHandle) { + mExecutor.execute(() -> mCallback.onDirectChannelDestroyed(channelHandle)); + } + + @Override + public void onDirectChannelConfigured(int channelHandle, @NonNull VirtualSensor sensor, + int rateLevel, int reportToken) { + mExecutor.execute(() -> mCallback.onDirectChannelConfigured( + channelHandle, sensor, rateLevel, reportToken)); + } } /** diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl index 7da9c3224400..3cb0572f3350 100644 --- a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl +++ b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl @@ -17,6 +17,7 @@ package android.companion.virtual.sensor; import android.companion.virtual.sensor.VirtualSensor; +import android.os.SharedMemory; /** * Interface for notifying the sensor owner about whether and how sensor events should be injected. @@ -36,4 +37,31 @@ oneway interface IVirtualSensorCallback { */ void onConfigurationChanged(in VirtualSensor sensor, boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros); + + /** + * Called when a sensor direct channel is created. + * + * @param channelHandle Identifier of the channel that was created. + * @param sharedMemory The shared memory region for the direct sensor channel. + */ + void onDirectChannelCreated(int channelHandle, in SharedMemory sharedMemory); + + /** + * Called when a sensor direct channel is destroyed. + * + * @param channelHandle Identifier of the channel that was destroyed. + */ + void onDirectChannelDestroyed(int channelHandle); + + /** + * Called when a sensor direct channel is configured. + * + * @param channelHandle Identifier of the channel that was configured. + * @param sensor The sensor, for which the channel was configured. + * @param rateLevel The rate level used to configure the direct sensor channel. + * @param reportToken A positive sensor report token, used to differentiate between events from + * different sensors within the same channel. + */ + void onDirectChannelConfigured(int channelHandle, in VirtualSensor sensor, int rateLevel, + int reportToken); } diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java index e09718941302..f7af283a749b 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java @@ -17,8 +17,13 @@ package android.companion.virtual.sensor; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.hardware.Sensor; +import android.hardware.SensorDirectChannel; +import android.os.MemoryFile; +import android.os.SharedMemory; import java.time.Duration; @@ -50,4 +55,74 @@ public interface VirtualSensorCallback { */ void onConfigurationChanged(@NonNull VirtualSensor sensor, boolean enabled, @NonNull Duration samplingPeriod, @NonNull Duration batchReportLatency); + + /** + * Called when a {@link android.hardware.SensorDirectChannel} is created. + * + * <p>The {@link android.hardware.SensorManager} instance used to create the direct channel must + * be associated with the virtual device. + * + * <p>A typical order of callback invocations is: + * <ul> + * <li>{@code onDirectChannelCreated} - the channel handle and the associated shared memory + * should be stored by the virtual device</li> + * <li>{@code onDirectChannelConfigured} with a positive {@code rateLevel} - the virtual + * device should start writing to the shared memory for the associated channel with the + * requested parameters.</li> + * <li>{@code onDirectChannelConfigured} with a {@code rateLevel = RATE_STOP} - the virtual + * device should stop writing to the shared memory for the associated channel.</li> + * <li>{@code onDirectChannelDestroyed} - the shared memory associated with the channel + * handle should be closed.</li> + * </ul> + * + * @param channelHandle Identifier of the newly created channel. + * @param sharedMemory writable shared memory region. + * + * @see android.hardware.SensorManager#createDirectChannel(MemoryFile) + * @see #onDirectChannelConfigured + * @see #onDirectChannelDestroyed + */ + default void onDirectChannelCreated(@IntRange(from = 1) int channelHandle, + @NonNull SharedMemory sharedMemory) {} + + /** + * Called when a {@link android.hardware.SensorDirectChannel} is destroyed. + * + * <p>The virtual device must perform any clean-up and close the shared memory that was + * received with the {@link #onDirectChannelCreated} callback and the corresponding + * {@code channelHandle}. + * + * @param channelHandle Identifier of the channel that was destroyed. + * + * @see SensorDirectChannel#close() + */ + default void onDirectChannelDestroyed(@IntRange(from = 1) int channelHandle) {} + + /** + * Called when a {@link android.hardware.SensorDirectChannel} is configured. + * + * <p>Sensor events for the corresponding sensor should be written at the indicated rate to the + * shared memory region that was received with the {@link #onDirectChannelCreated} callback and + * the corresponding {@code channelHandle}. The events should be written in the correct format + * and with the provided {@code reportToken} until the channel is reconfigured with + * {@link SensorDirectChannel#RATE_STOP}. + * + * <p>The sensor must support direct channel in order for this callback to be invoked. Only + * {@link MemoryFile} sensor direct channels are supported for virtual sensors. + * + * @param channelHandle Identifier of the channel that was configured. + * @param sensor The sensor, for which the channel was configured. + * @param rateLevel The rate level used to configure the direct sensor channel. + * @param reportToken A positive sensor report token, used to differentiate between events from + * different sensors within the same channel. + * + * @see VirtualSensorConfig.Builder#setHighestDirectReportRateLevel(int) + * @see VirtualSensorConfig.Builder#setDirectChannelTypesSupported(int) + * @see android.hardware.SensorManager#createDirectChannel(MemoryFile) + * @see #onDirectChannelCreated + * @see SensorDirectChannel#configure(Sensor, int) + */ + default void onDirectChannelConfigured(@IntRange(from = 1) int channelHandle, + @NonNull VirtualSensor sensor, @SensorDirectChannel.RateLevel int rateLevel, + @IntRange(from = 1) int reportToken) {} } diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java index 6d45365ebbd4..ffbdff8c2e3b 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java @@ -21,11 +21,13 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.hardware.Sensor; +import android.hardware.SensorDirectChannel; import android.os.Parcel; import android.os.Parcelable; import java.util.Objects; + /** * Configuration for creation of a virtual sensor. * @see VirtualSensor @@ -33,6 +35,14 @@ import java.util.Objects; */ @SystemApi public final class VirtualSensorConfig implements Parcelable { + private static final String TAG = "VirtualSensorConfig"; + + // Mask for direct mode highest rate level, bit 7, 8, 9. + private static final int DIRECT_REPORT_MASK = 0x380; + private static final int DIRECT_REPORT_SHIFT = 7; + + // Mask for supported direct channel, bit 10, 11 + private static final int DIRECT_CHANNEL_SHIFT = 10; private final int mType; @NonNull @@ -40,16 +50,21 @@ public final class VirtualSensorConfig implements Parcelable { @Nullable private final String mVendor; - private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor) { + private final int mFlags; + + private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor, + int flags) { mType = type; mName = name; mVendor = vendor; + mFlags = flags; } private VirtualSensorConfig(@NonNull Parcel parcel) { mType = parcel.readInt(); mName = parcel.readString8(); mVendor = parcel.readString8(); + mFlags = parcel.readInt(); } @Override @@ -62,6 +77,7 @@ public final class VirtualSensorConfig implements Parcelable { parcel.writeInt(mType); parcel.writeString8(mName); parcel.writeString8(mVendor); + parcel.writeInt(mFlags); } /** @@ -92,22 +108,64 @@ public final class VirtualSensorConfig implements Parcelable { } /** + * Returns the highest supported direct report mode rate level of the sensor. + * + * @see Sensor#getHighestDirectReportRateLevel() + */ + @SensorDirectChannel.RateLevel + public int getHighestDirectReportRateLevel() { + int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT); + return rateLevel <= SensorDirectChannel.RATE_VERY_FAST + ? rateLevel : SensorDirectChannel.RATE_VERY_FAST; + } + + /** + * Returns a combination of all supported direct channel types. + * + * @see Builder#setDirectChannelTypesSupported(int) + * @see Sensor#isDirectChannelTypeSupported(int) + */ + public @SensorDirectChannel.MemoryType int getDirectChannelTypesSupported() { + int memoryTypes = 0; + if ((mFlags & (1 << DIRECT_CHANNEL_SHIFT)) > 0) { + memoryTypes |= SensorDirectChannel.TYPE_MEMORY_FILE; + } + if ((mFlags & (1 << (DIRECT_CHANNEL_SHIFT + 1))) > 0) { + memoryTypes |= SensorDirectChannel.TYPE_HARDWARE_BUFFER; + } + return memoryTypes; + } + + /** + * Returns the sensor flags. + * @hide + */ + public int getFlags() { + return mFlags; + } + + /** * Builder for {@link VirtualSensorConfig}. */ public static final class Builder { + private static final int FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED = + 1 << DIRECT_CHANNEL_SHIFT; private final int mType; @NonNull private final String mName; @Nullable private String mVendor; + private int mFlags; + @SensorDirectChannel.RateLevel + int mHighestDirectReportRateLevel; /** * Creates a new builder. * * @param type The type of the sensor, matching {@link Sensor#getType}. * @param name The name of the sensor. Must be unique among all sensors with the same type - * that belong to the same virtual device. + * that belong to the same virtual device. */ public Builder(int type, @NonNull String name) { mType = type; @@ -119,7 +177,19 @@ public final class VirtualSensorConfig implements Parcelable { */ @NonNull public VirtualSensorConfig build() { - return new VirtualSensorConfig(mType, mName, mVendor); + if (mHighestDirectReportRateLevel > 0) { + if ((mFlags & FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED) == 0) { + throw new IllegalArgumentException("Setting direct channel type is required " + + "for sensors with direct channel support."); + } + mFlags |= mHighestDirectReportRateLevel << DIRECT_REPORT_SHIFT; + } + if ((mFlags & FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED) > 0 + && mHighestDirectReportRateLevel == 0) { + throw new IllegalArgumentException("Highest direct report rate level is " + + "required for sensors with direct channel support."); + } + return new VirtualSensorConfig(mType, mName, mVendor, mFlags); } /** @@ -130,6 +200,44 @@ public final class VirtualSensorConfig implements Parcelable { mVendor = vendor; return this; } + + /** + * Sets the highest supported rate level for direct sensor report. + * + * @see VirtualSensorConfig#getHighestDirectReportRateLevel() + */ + @NonNull + public VirtualSensorConfig.Builder setHighestDirectReportRateLevel( + @SensorDirectChannel.RateLevel int rateLevel) { + mHighestDirectReportRateLevel = rateLevel; + return this; + } + + /** + * Sets whether direct sensor channel of the given types is supported. + * + * @param memoryTypes A combination of {@link SensorDirectChannel.MemoryType} flags + * indicating the types of shared memory supported for creating direct channels. Only + * {@link SensorDirectChannel#TYPE_MEMORY_FILE} direct channels may be supported for virtual + * sensors. + * @throws IllegalArgumentException if {@link SensorDirectChannel#TYPE_HARDWARE_BUFFER} is + * set to be supported. + */ + @NonNull + public VirtualSensorConfig.Builder setDirectChannelTypesSupported( + @SensorDirectChannel.MemoryType int memoryTypes) { + if ((memoryTypes & SensorDirectChannel.TYPE_MEMORY_FILE) > 0) { + mFlags |= FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED; + } else { + mFlags &= ~FLAG_MEMORY_FILE_DIRECT_CHANNEL_SUPPORTED; + } + if ((memoryTypes & ~SensorDirectChannel.TYPE_MEMORY_FILE) > 0) { + throw new IllegalArgumentException( + "Only TYPE_MEMORY_FILE direct channels can be supported for virtual " + + "sensors."); + } + return this; + } } @NonNull diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 795c77ff5105..d3502c5254c8 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -389,6 +389,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall @Override public void getTypeAnonymousAsync(Uri uri, RemoteCallback callback) { + uri = validateIncomingUri(uri); + uri = maybeGetUriWithoutUserId(uri); final Bundle result = new Bundle(); try { result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getTypeAnonymous(uri)); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4dccc8d0e81a..658702f2b1cc 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -135,6 +135,15 @@ public abstract class Context { @VisibleForTesting public static final long OVERRIDABLE_COMPONENT_CALLBACKS = 193247900L; + /** + * The default device ID, which is the ID of the primary (non-virtual) device. + */ + public static final int DEVICE_ID_DEFAULT = 0; + /** + * Invalid device ID. + */ + public static final int DEVICE_ID_INVALID = -1; + /** @hide */ @IntDef(flag = true, prefix = { "MODE_" }, value = { MODE_PRIVATE, @@ -4267,7 +4276,7 @@ public abstract class Context { * <p>Note: When implementing this method, keep in mind that new services can be added on newer * Android releases, so if you're looking for just the explicit names mentioned above, make sure * to return {@code null} when you don't recognize the name — if you throw a - * {@link RuntimeException} exception instead, you're app might break on new Android releases. + * {@link RuntimeException} exception instead, your app might break on new Android releases. * * @param name The name of the desired service. * @@ -7175,7 +7184,7 @@ public abstract class Context { * <p> * Applications that run on virtual devices may use this method to access the default device * capabilities and functionality (by passing - * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT}. Similarly, + * {@link Context#DEVICE_ID_DEFAULT}. Similarly, * applications running on the default device may access the functionality of virtual devices. * </p> * <p> @@ -7542,7 +7551,7 @@ public abstract class Context { * determine whether they are running on a virtual device and identify that device. * * The device ID of the host device is - * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT} + * {@link Context#DEVICE_ID_DEFAULT} * * <p> * If the underlying device ID is changed by the system, for example, when an diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 5818ed75056c..85daf15865d1 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3234,8 +3234,9 @@ public class Intent implements Parcelable, Cloneable { /** * Broadcast Action: The receiver's effective locale has changed. * - * This happens when the device locale, or the receiving app's locale - * (set via {@link android.app.LocaleManager#setApplicationLocales}) changed. + * This happens when the device locale, the receiving app's locale + * (set via {@link android.app.LocaleManager#setApplicationLocales}) or language tags + * of Regional preferences changed. * * Can be received by manifest-declared receivers. * diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index bd3cf5f0e6bc..afc2285c62c3 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -20,9 +20,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.app.compat.CompatChanges; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.net.Uri; import android.os.Build; @@ -185,28 +182,6 @@ public class IntentFilter implements Parcelable { private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; /** - * An intent with action set as null used to always pass the action test during intent - * filter matching. This causes a lot of confusion and unexpected intent matches. - * Null action intents should be blocked when either the intent sender or receiver - * application targets U or higher. - * - * mBlockNullAction indicates whether the intent filter owner (intent receiver) is - * targeting U+. This value will be properly set by package manager when IntentFilters are - * passed to an application, so that when an application is trying to perform intent filter - * matching locally, the correct matching algorithm will be chosen. - * - * When an IntentFilter is sent to system server (e.g. for registering runtime receivers), - * the value set in mBlockNullAction will be ignored and overwritten with the correct - * value evaluated based on the Binder calling identity. This makes sure that the - * security enforcement cannot be bypassed by crafting a malicious IntentFilter. - * - * @hide - */ - @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) - public static final long BLOCK_NULL_ACTION_INTENTS = 264497795; - - /** * The filter {@link #setPriority} value at which system high-priority * receivers are placed; that is, receivers that should execute before * application code. Applications should never use filters with this or @@ -2301,7 +2276,6 @@ public class IntentFilter implements Parcelable { String type = resolve ? intent.resolveType(resolver) : intent.getType(); return match(intent.getAction(), type, intent.getScheme(), intent.getData(), intent.getCategories(), logTag, - CompatChanges.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS), false /* supportWildcards */, null /* ignoreActions */, intent.getExtras()); } @@ -2354,7 +2328,6 @@ public class IntentFilter implements Parcelable { Uri data, Set<String> categories, String logTag, boolean supportWildcards, @Nullable Collection<String> ignoreActions) { return match(action, type, scheme, data, categories, logTag, supportWildcards, - CompatChanges.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS), ignoreActions, null /* extras */); } @@ -2366,10 +2339,8 @@ public class IntentFilter implements Parcelable { */ public final int match(String action, String type, String scheme, Uri data, Set<String> categories, String logTag, boolean supportWildcards, - boolean blockNullAction, @Nullable Collection<String> ignoreActions, - @Nullable Bundle extras) { - if ((action == null && blockNullAction) - || !matchAction(action, supportWildcards, ignoreActions)) { + @Nullable Collection<String> ignoreActions, @Nullable Bundle extras) { + if (action != null && !matchAction(action, supportWildcards, ignoreActions)) { if (false) Log.v( logTag, "No matching action " + action + " for " + this); return NO_MATCH_ACTION; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a0c620ac26c0..9388823b1a9c 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5157,6 +5157,8 @@ public abstract class PackageManager { * Retrieve overall information about an application package that is * installed on the system. * + * Use {@link #getPackageInfo(String, PackageInfoFlags)} when long flags are needed. + * * @param packageName The full name (i.e. com.google.apps.contacts) of the * desired package. * @param flags Additional option flags to modify the data returned. @@ -5169,9 +5171,7 @@ public abstract class PackageManager { * deleted with {@code DELETE_KEEP_DATA} flag set). * @throws NameNotFoundException if no such package is available to the * caller. - * @deprecated Use {@link #getPackageInfo(String, PackageInfoFlags)} instead. */ - @Deprecated public abstract PackageInfo getPackageInfo(@NonNull String packageName, int flags) throws NameNotFoundException; @@ -5195,6 +5195,8 @@ public abstract class PackageManager { * {@link #VERSION_CODE_HIGHEST} in the {@link VersionedPackage} * constructor. * + * Use {@link #getPackageInfo(VersionedPackage, PackageInfoFlags)} when long flags are needed. + * * @param versionedPackage The versioned package for which to query. * @param flags Additional option flags to modify the data returned. * @return A PackageInfo object containing information about the package. If @@ -5206,9 +5208,7 @@ public abstract class PackageManager { * deleted with {@code DELETE_KEEP_DATA} flag set). * @throws NameNotFoundException if no such package is available to the * caller. - * @deprecated Use {@link #getPackageInfo(VersionedPackage, PackageInfoFlags)} instead. */ - @Deprecated public abstract PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage, int flags) throws NameNotFoundException; @@ -5226,6 +5226,8 @@ public abstract class PackageManager { * Retrieve overall information about an application package that is * installed on the system. * + * Use {@link #getPackageInfoAsUser(String, PackageInfoFlags, int)} when long flags are needed. + * * @param packageName The full name (i.e. com.google.apps.contacts) of the * desired package. * @param flags Additional option flags to modify the data returned. @@ -5239,10 +5241,8 @@ public abstract class PackageManager { * deleted with {@code DELETE_KEEP_DATA} flag set). * @throws NameNotFoundException if no such package is available to the * caller. - * @deprecated Use {@link #getPackageInfoAsUser(String, PackageInfoFlags, int)} instead. * @hide */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @UnsupportedAppUsage @@ -5376,15 +5376,16 @@ public abstract class PackageManager { * Note that the same package may have different GIDs under different * {@link UserHandle} on the same device. * + * Use {@link #getPackageGids(String, PackageInfoFlags)} when long flags are needed. + * * @param packageName The full name (i.e. com.google.apps.contacts) of the * desired package. * @return Returns an int array of the assigned gids, or null if there are * none. * @throws NameNotFoundException if no such package is available to the * caller. - * @deprecated Use {@link #getPackageGids(String, PackageInfoFlags)} instead. */ - @Deprecated + public abstract int[] getPackageGids(@NonNull String packageName, int flags) throws NameNotFoundException; @@ -5404,14 +5405,14 @@ public abstract class PackageManager { * Note that the same package will have different UIDs under different * {@link UserHandle} on the same device. * + * Use {@link #getPackageUid(String, PackageInfoFlags)} when long flags are needed. + * * @param packageName The full name (i.e. com.google.apps.contacts) of the * desired package. * @return Returns an integer UID who owns the given package name. * @throws NameNotFoundException if no such package is available to the * caller. - * @deprecated Use {@link #getPackageUid(String, PackageInfoFlags)} instead. */ - @Deprecated public abstract int getPackageUid(@NonNull String packageName, int flags) throws NameNotFoundException; @@ -5445,10 +5446,9 @@ public abstract class PackageManager { /** * See {@link #getPackageUidAsUser(String, PackageInfoFlags, int)}. - * @deprecated Use {@link #getPackageUidAsUser(String, PackageInfoFlags, int)} instead. + * Use {@link #getPackageUidAsUser(String, PackageInfoFlags, int)} when long flags are needed. * @hide */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract int getPackageUidAsUser(@NonNull String packageName, @@ -5589,6 +5589,8 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular * package/application. * + * Use {@link #getApplicationInfo(String, ApplicationInfoFlags)} when long flags are needed. + * * @param packageName The full name (i.e. com.google.apps.contacts) of an * application. * @param flags Additional option flags to modify the data returned. @@ -5601,10 +5603,8 @@ public abstract class PackageManager { * which had been deleted with {@code DELETE_KEEP_DATA} flag set). * @throws NameNotFoundException if a package with the given name cannot be * found on the system. - * @deprecated Use {@link #getApplicationInfo(String, ApplicationInfoFlags)} instead. */ @NonNull - @Deprecated public abstract ApplicationInfo getApplicationInfo(@NonNull String packageName, int flags) throws NameNotFoundException; @@ -5619,13 +5619,13 @@ public abstract class PackageManager { } /** - * @deprecated Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, int)} instead. + * Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, int)} when long flags are + * needed. * {@hide} */ @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage - @Deprecated public abstract ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName, int flags, @UserIdInt int userId) throws NameNotFoundException; @@ -5642,6 +5642,9 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular * package/application, for a specific user. * + * Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, UserHandle)} when long + * flags are needed. + * * @param packageName The full name (i.e. com.google.apps.contacts) of an * application. * @param flags Additional option flags to modify the data returned. @@ -5654,14 +5657,11 @@ public abstract class PackageManager { * which had been deleted with {@code DELETE_KEEP_DATA} flag set). * @throws NameNotFoundException if a package with the given name cannot be * found on the system. - * @deprecated Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, UserHandle)} - * instead. * @hide */ @NonNull @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @SystemApi - @Deprecated public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName, int flags, @NonNull UserHandle user) throws NameNotFoundException { @@ -5694,6 +5694,8 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular activity * class. * + * Use {@link #getActivityInfo(ComponentName, ComponentInfoFlags)} when long flags are needed. + * * @param component The full component name (i.e. * com.google.apps.contacts/com.google.apps.contacts. * ContactsList) of an Activity class. @@ -5702,9 +5704,7 @@ public abstract class PackageManager { * activity. * @throws NameNotFoundException if a package with the given name cannot be * found on the system. - * @deprecated Use {@link #getActivityInfo(ComponentName, ComponentInfoFlags)} instead. */ - @Deprecated @NonNull public abstract ActivityInfo getActivityInfo(@NonNull ComponentName component, int flags) throws NameNotFoundException; @@ -5720,20 +5720,11 @@ public abstract class PackageManager { } /** - * @hide - */ - @NonNull - public ActivityInfo getActivityInfoAsUser(@NonNull ComponentName component, - @NonNull ComponentInfoFlags flags, @UserIdInt int userId) - throws NameNotFoundException { - throw new UnsupportedOperationException( - "getActivityInfoAsUser not implemented in subclass"); - } - - /** * Retrieve all of the information we know about a particular receiver * class. * + * Use {@link #getReceiverInfo(ComponentName, ComponentInfoFlags)} when long flags are needed. + * * @param component The full component name (i.e. * com.google.apps.calendar/com.google.apps.calendar. * CalendarAlarm) of a Receiver class. @@ -5742,9 +5733,7 @@ public abstract class PackageManager { * receiver. * @throws NameNotFoundException if a package with the given name cannot be * found on the system. - * @deprecated Use {@link #getReceiverInfo(ComponentName, ComponentInfoFlags)} instead. */ - @Deprecated @NonNull public abstract ActivityInfo getReceiverInfo(@NonNull ComponentName component, int flags) throws NameNotFoundException; @@ -5762,6 +5751,8 @@ public abstract class PackageManager { /** * Retrieve all of the information we know about a particular service class. * + * Use {@link #getServiceInfo(ComponentName, ComponentInfoFlags)} when long flags are needed. + * * @param component The full component name (i.e. * com.google.apps.media/com.google.apps.media. * BackgroundPlayback) of a Service class. @@ -5769,9 +5760,7 @@ public abstract class PackageManager { * @return A {@link ServiceInfo} object containing information about the * service. * @throws NameNotFoundException if the component cannot be found on the system. - * @deprecated Use {@link #getServiceInfo(ComponentName, ComponentInfoFlags)} instead. */ - @Deprecated @NonNull public abstract ServiceInfo getServiceInfo(@NonNull ComponentName component, int flags) throws NameNotFoundException; @@ -5790,6 +5779,8 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular content * provider class. * + * Use {@link #getProviderInfo(ComponentName, ComponentInfoFlags)} when long flags are needed. + * * @param component The full component name (i.e. * com.google.providers.media/com.google.providers.media. * MediaProvider) of a ContentProvider class. @@ -5798,9 +5789,7 @@ public abstract class PackageManager { * provider. * @throws NameNotFoundException if a package with the given name cannot be * found on the system. - * @deprecated Use {@link #getProviderInfo(ComponentName, ComponentInfoFlags)} instead. */ - @Deprecated @NonNull public abstract ProviderInfo getProviderInfo(@NonNull ComponentName component, int flags) throws NameNotFoundException; @@ -5849,6 +5838,8 @@ public abstract class PackageManager { /** * Return a List of all packages that are installed for the current user. * + * Use {@link #getInstalledPackages(PackageInfoFlags)} when long flags are needed. + * * @param flags Additional option flags to modify the data returned. * @return A List of PackageInfo objects, one for each installed package, * containing information about the package. In the unlikely case @@ -5858,15 +5849,12 @@ public abstract class PackageManager { * applications (which includes installed applications as well as * applications with data directory i.e. applications which had been * deleted with {@code DELETE_KEEP_DATA} flag set). - * @deprecated Use {@link #getInstalledPackages(PackageInfoFlags)} instead. */ - @Deprecated @NonNull public abstract List<PackageInfo> getInstalledPackages(int flags); /** * See {@link #getInstalledPackages(int)}. - * @param flags */ @NonNull public List<PackageInfo> getInstalledPackages(@NonNull PackageInfoFlags flags) { @@ -5896,6 +5884,9 @@ public abstract class PackageManager { * Return a List of all installed packages that are currently holding any of * the given permissions. * + * Use {@link #getPackagesHoldingPermissions(String[], PackageInfoFlags)} when long flags are + * needed. + * * @param flags Additional option flags to modify the data returned. * @return A List of PackageInfo objects, one for each installed package * that holds any of the permissions that were provided, containing @@ -5906,9 +5897,7 @@ public abstract class PackageManager { * applications (which includes installed applications as well as * applications with data directory i.e. applications which had been * deleted with {@code DELETE_KEEP_DATA} flag set). - * @deprecated Use {@link #getPackagesHoldingPermissions(String[], PackageInfoFlags)} instead. */ - @Deprecated @NonNull public abstract List<PackageInfo> getPackagesHoldingPermissions( @NonNull String[] permissions, int flags); @@ -5927,6 +5916,8 @@ public abstract class PackageManager { * Return a List of all packages that are installed on the device, for a * specific user. * + * Use {@link #getInstalledPackagesAsUser(PackageInfoFlags, int)} when long flags are needed. + * * @param flags Additional option flags to modify the data returned. * @param userId The user for whom the installed packages are to be listed * @return A List of PackageInfo objects, one for each installed package, @@ -5937,10 +5928,8 @@ public abstract class PackageManager { * applications (which includes installed applications as well as * applications with data directory i.e. applications which had been * deleted with {@code DELETE_KEEP_DATA} flag set). - * @deprecated Use {@link #getInstalledPackagesAsUser(PackageInfoFlags, int)} instead. * @hide */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @NonNull @SystemApi @@ -6652,6 +6641,8 @@ public abstract class PackageManager { * applications including those deleted with {@code DELETE_KEEP_DATA} * (partially installed apps with data directory) will be returned. * + * Use {@link #getInstalledApplications(ApplicationInfoFlags)} when long flags are needed. + * * @param flags Additional option flags to modify the data returned. * @return A List of ApplicationInfo objects, one for each installed * application. In the unlikely case there are no installed @@ -6661,10 +6652,8 @@ public abstract class PackageManager { * applications (which includes installed applications as well as * applications with data directory i.e. applications which had been * deleted with {@code DELETE_KEEP_DATA} flag set). - * @deprecated Use {@link #getInstalledApplications(ApplicationInfoFlags)} instead. */ @NonNull - @Deprecated public abstract List<ApplicationInfo> getInstalledApplications(int flags); /** @@ -6683,6 +6672,9 @@ public abstract class PackageManager { * {@code DELETE_KEEP_DATA} (partially installed apps with data directory) * will be returned. * + * Use {@link #getInstalledApplicationsAsUser(ApplicationInfoFlags, int)} when long flags are + * needed. + * * @param flags Additional option flags to modify the data returned. * @param userId The user for whom the installed applications are to be * listed @@ -6694,13 +6686,11 @@ public abstract class PackageManager { * applications (which includes installed applications as well as * applications with data directory i.e. applications which had been * deleted with {@code DELETE_KEEP_DATA} flag set). - * @deprecated Use {@link #getInstalledApplicationsAsUser(ApplicationInfoFlags, int)} instead. * @hide */ @SuppressWarnings("HiddenAbstractMethod") @NonNull @TestApi - @Deprecated public abstract List<ApplicationInfo> getInstalledApplicationsAsUser( int flags, @UserIdInt int userId); @@ -6859,13 +6849,13 @@ public abstract class PackageManager { /** * Get a list of shared libraries on the device. * + * Use {@link #getSharedLibraries(PackageInfoFlags)} when long flags are needed. + * * @param flags To filter the libraries to return. * @return The shared library list. * * @see #MATCH_UNINSTALLED_PACKAGES - * @deprecated Use {@link #getSharedLibraries(PackageInfoFlags)} instead. */ - @Deprecated public abstract @NonNull List<SharedLibraryInfo> getSharedLibraries(int flags); /** @@ -6880,6 +6870,8 @@ public abstract class PackageManager { /** * Get a list of shared libraries on the device. * + * Use {@link #getSharedLibrariesAsUser(PackageInfoFlags, int)} when long flags are needed. + * * @param flags To filter the libraries to return. * @param userId The user to query for. * @return The shared library list. @@ -6890,9 +6882,7 @@ public abstract class PackageManager { * @see #MATCH_UNINSTALLED_PACKAGES * * @hide - * @deprecated Use {@link #getSharedLibrariesAsUser(PackageInfoFlags, int)} instead. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") public abstract @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, @UserIdInt int userId); @@ -6910,14 +6900,14 @@ public abstract class PackageManager { /** * Get the list of shared libraries declared by a package. * + * Use {@link #getDeclaredSharedLibraries(String, PackageInfoFlags)} when long flags are needed. + * * @param packageName the package name to query * @param flags the flags to filter packages * @return the shared library list * * @hide - * @deprecated Use {@link #getDeclaredSharedLibraries(String, PackageInfoFlags)} instead. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @NonNull @RequiresPermission(Manifest.permission.ACCESS_SHARED_LIBRARIES) @@ -7026,6 +7016,8 @@ public abstract class PackageManager { * Intent.resolveActivity(PackageManager)} do. * </p> * + * Use {@link #resolveActivity(Intent, ResolveInfoFlags)} when long flags are needed. + * * @param intent An intent containing all of the desired specification * (action, data, type, category, and/or component). * @param flags Additional option flags to modify the data returned. The @@ -7037,9 +7029,7 @@ public abstract class PackageManager { * matching activity was found. If multiple matching activities are * found and there is no default set, returns a ResolveInfo object * containing something else, such as the activity resolver. - * @deprecated Use {@link #resolveActivity(Intent, ResolveInfoFlags)} instead. */ - @Deprecated @Nullable public abstract ResolveInfo resolveActivity(@NonNull Intent intent, int flags); @@ -7066,6 +7056,8 @@ public abstract class PackageManager { * Intent.resolveActivity(PackageManager)} do. * </p> * + * Use {@link #resolveActivityAsUser(Intent, ResolveInfoFlags, int)} when long flags are needed. + * * @param intent An intent containing all of the desired specification * (action, data, type, category, and/or component). * @param flags Additional option flags to modify the data returned. The @@ -7079,9 +7071,7 @@ public abstract class PackageManager { * found and there is no default set, returns a ResolveInfo object * containing something else, such as the activity resolver. * @hide - * @deprecated Use {@link #resolveActivityAsUser(Intent, ResolveInfoFlags, int)} instead. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @Nullable @UnsupportedAppUsage @@ -7102,6 +7092,8 @@ public abstract class PackageManager { /** * Retrieve all activities that can be performed for the given intent. * + * Use {@link #queryIntentActivities(Intent, ResolveInfoFlags)} when long flags are needed. + * * @param intent The desired intent as per resolveActivity(). * @param flags Additional option flags to modify the data returned. The * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the @@ -7113,9 +7105,7 @@ public abstract class PackageManager { * words, the first item is what would be returned by * {@link #resolveActivity}. If there are no matching activities, an * empty list is returned. - * @deprecated Use {@link #queryIntentActivities(Intent, ResolveInfoFlags)} instead. */ - @Deprecated @NonNull public abstract List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, int flags); @@ -7133,6 +7123,9 @@ public abstract class PackageManager { * Retrieve all activities that can be performed for the given intent, for a * specific user. * + * Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, int)} when long flags are + * needed. + * * @param intent The desired intent as per resolveActivity(). * @param flags Additional option flags to modify the data returned. The * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the @@ -7145,9 +7138,7 @@ public abstract class PackageManager { * {@link #resolveActivity}. If there are no matching activities, an * empty list is returned. * @hide - * @deprecated Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, int)} instead. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage @@ -7169,6 +7160,9 @@ public abstract class PackageManager { * Retrieve all activities that can be performed for the given intent, for a * specific user. * + * Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, UserHandle)} when long + * flags are needed. + * * @param intent The desired intent as per resolveActivity(). * @param flags Additional option flags to modify the data returned. The * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the @@ -7182,10 +7176,7 @@ public abstract class PackageManager { * {@link #resolveActivity}. If there are no matching activities, an * empty list is returned. * @hide - * @deprecated Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, UserHandle)} - * instead. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @NonNull @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @@ -7215,6 +7206,9 @@ public abstract class PackageManager { * final ResolveInfo list in a reasonable order, with no duplicates, based * on those inputs. * + * Use {@link #queryIntentActivityOptions(ComponentName, List, Intent, ResolveInfoFlags)} when + * long flags are needed. + * * @param caller The class name of the activity that is making the request. * This activity will never appear in the output list. Can be * null. @@ -7231,10 +7225,7 @@ public abstract class PackageManager { * activities that can handle <var>intent</var> but did not get * included by one of the <var>specifics</var> intents. If there are * no matching activities, an empty list is returned. - * @deprecated Use {@link #queryIntentActivityOptions(ComponentName, List, Intent, - * ResolveInfoFlags)} instead. */ - @Deprecated @NonNull public abstract List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller, @Nullable Intent[] specifics, @NonNull Intent intent, int flags); @@ -7253,14 +7244,14 @@ public abstract class PackageManager { /** * Retrieve all receivers that can handle a broadcast of the given intent. * + * Use {@link #queryBroadcastReceivers(Intent, ResolveInfoFlags)} when long flags are needed. + * * @param intent The desired intent as per resolveActivity(). * @param flags Additional option flags to modify the data returned. * @return Returns a List of ResolveInfo objects containing one entry for * each matching receiver, ordered from best to worst. If there are * no matching receivers, an empty list or null is returned. - * @deprecated Use {@link #queryBroadcastReceivers(Intent, ResolveInfoFlags)} instead. */ - @Deprecated @NonNull public abstract List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent, int flags); @@ -7278,6 +7269,9 @@ public abstract class PackageManager { * Retrieve all receivers that can handle a broadcast of the given intent, * for a specific user. * + * Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, UserHandle)} when long + * flags are needed. + * * @param intent The desired intent as per resolveActivity(). * @param flags Additional option flags to modify the data returned. * @param userHandle UserHandle of the user being queried. @@ -7285,10 +7279,7 @@ public abstract class PackageManager { * each matching receiver, ordered from best to worst. If there are * no matching receivers, an empty list or null is returned. * @hide - * @deprecated Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, UserHandle)} - * instead. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @NonNull @SystemApi @@ -7312,10 +7303,9 @@ public abstract class PackageManager { /** * @hide - * @deprecated Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, int)} - * instead. + * Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, int)} when long flags are + * needed. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage @@ -7353,15 +7343,15 @@ public abstract class PackageManager { /** * Determine the best service to handle for a given Intent. * + * Use {@link #resolveService(Intent, ResolveInfoFlags)} when long flags are needed. + * * @param intent An intent containing all of the desired specification * (action, data, type, category, and/or component). * @param flags Additional option flags to modify the data returned. * @return Returns a ResolveInfo object containing the final service intent * that was determined to be the best action. Returns null if no * matching service was found. - * @deprecated Use {@link #resolveService(Intent, ResolveInfoFlags)} instead. */ - @Deprecated @Nullable public abstract ResolveInfo resolveService(@NonNull Intent intent, int flags); @@ -7376,9 +7366,8 @@ public abstract class PackageManager { /** * @hide - * @deprecated Use {@link #resolveServiceAsUser(Intent, ResolveInfoFlags, int)} instead. + * Use {@link #resolveServiceAsUser(Intent, ResolveInfoFlags, int)} when long flags are needed. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @Nullable public abstract ResolveInfo resolveServiceAsUser(@NonNull Intent intent, @@ -7398,6 +7387,8 @@ public abstract class PackageManager { /** * Retrieve all services that can match the given intent. * + * Use {@link #queryIntentServices(Intent, ResolveInfoFlags)} when long flags are needed. + * * @param intent The desired intent as per resolveService(). * @param flags Additional option flags to modify the data returned. * @return Returns a List of ResolveInfo objects containing one entry for @@ -7405,9 +7396,7 @@ public abstract class PackageManager { * words, the first item is what would be returned by * {@link #resolveService}. If there are no matching services, an * empty list or null is returned. - * @deprecated Use {@link #queryIntentServices(Intent, ResolveInfoFlags)} instead. */ - @Deprecated @NonNull public abstract List<ResolveInfo> queryIntentServices(@NonNull Intent intent, int flags); @@ -7425,6 +7414,9 @@ public abstract class PackageManager { /** * Retrieve all services that can match the given intent for a given user. * + * Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, int)} when long flags are + * needed. + * * @param intent The desired intent as per resolveService(). * @param flags Additional option flags to modify the data returned. * @param userId The user id. @@ -7434,9 +7426,7 @@ public abstract class PackageManager { * {@link #resolveService}. If there are no matching services, an * empty list or null is returned. * @hide - * @deprecated Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, int)} instead. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage @@ -7457,6 +7447,9 @@ public abstract class PackageManager { /** * Retrieve all services that can match the given intent for a given user. * + * Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, UserHandle)} when long flags + * are needed. + * * @param intent The desired intent as per resolveService(). * @param flags Additional option flags to modify the data returned. * @param user The user being queried. @@ -7466,10 +7459,7 @@ public abstract class PackageManager { * {@link #resolveService}. If there are no matching services, an * empty list or null is returned. * @hide - * @deprecated Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, UserHandle)} - * instead. */ - @Deprecated @NonNull @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @SystemApi @@ -7492,6 +7482,9 @@ public abstract class PackageManager { /** * Retrieve all providers that can match the given intent. * + * Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags, int)} when long flags + * are needed. + * * @param intent An intent containing all of the desired specification * (action, data, type, category, and/or component). * @param flags Additional option flags to modify the data returned. @@ -7500,10 +7493,7 @@ public abstract class PackageManager { * each matching provider, ordered from best to worst. If there are * no matching services, an empty list or null is returned. * @hide - * @deprecated Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags, int)} - * instead. */ - @Deprecated @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage @@ -7524,6 +7514,9 @@ public abstract class PackageManager { /** * Retrieve all providers that can match the given intent. * + * Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags, UserHandle)} when + * long flags are needed. + * * @param intent An intent containing all of the desired specification * (action, data, type, category, and/or component). * @param flags Additional option flags to modify the data returned. @@ -7532,10 +7525,7 @@ public abstract class PackageManager { * each matching provider, ordered from best to worst. If there are * no matching services, an empty list or null is returned. * @hide - * @deprecated Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags, - * UserHandle)} instead. */ - @Deprecated @NonNull @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @SystemApi @@ -7559,15 +7549,16 @@ public abstract class PackageManager { /** * Retrieve all providers that can match the given intent. * + * Use {@link #queryIntentContentProviders(Intent, ResolveInfoFlags)} when long flags are + * needed. + * * @param intent An intent containing all of the desired specification * (action, data, type, category, and/or component). * @param flags Additional option flags to modify the data returned. * @return Returns a List of ResolveInfo objects containing one entry for * each matching provider, ordered from best to worst. If there are * no matching services, an empty list or null is returned. - * @deprecated Use {@link #queryIntentContentProviders(Intent, ResolveInfoFlags)} instead. */ - @Deprecated @NonNull public abstract List<ResolveInfo> queryIntentContentProviders(@NonNull Intent intent, int flags); @@ -7591,13 +7582,13 @@ public abstract class PackageManager { * ProviderInfo info = packageManager.resolveContentProvider(uri.getAuthority(), flags); * </pre> * + * Use {@link #resolveContentProvider(String, ComponentInfoFlags)} when long flags are needed. + * * @param authority The authority of the provider to find. * @param flags Additional option flags to modify the data returned. * @return A {@link ProviderInfo} object containing information about the * provider. If a provider was not found, returns null. - * @deprecated Use {@link #resolveContentProvider(String, ComponentInfoFlags)} instead. */ - @Deprecated @Nullable public abstract ProviderInfo resolveContentProvider(@NonNull String authority, int flags); @@ -7615,14 +7606,15 @@ public abstract class PackageManager { /** * Find a single content provider by its base path name. * + * Use {@link #resolveContentProviderAsUser(String, ComponentInfoFlags, int)} when long flags + * are needed. + * * @param providerName The name of the provider to find. * @param flags Additional option flags to modify the data returned. * @param userId The user id. * @return A {@link ProviderInfo} object containing information about the * provider. If a provider was not found, returns null. * @hide - * @deprecated Use {@link #resolveContentProviderAsUser(String, ComponentInfoFlags, int)} - * instead. */ @SuppressWarnings("HiddenAbstractMethod") @Nullable @@ -7647,6 +7639,9 @@ public abstract class PackageManager { * <em>Note: unlike most other methods, an empty result set is indicated * by a null return instead of an empty list.</em> * + * Use {@link #queryContentProviders(String, int, ComponentInfoFlags)} when long flags are + * needed. + * * @param processName If non-null, limits the returned providers to only * those that are hosted by the given process. If null, all * content providers are returned. @@ -7657,9 +7652,7 @@ public abstract class PackageManager { * each provider either matching <var>processName</var> or, if * <var>processName</var> is null, all known content providers. * <em>If there are no matching providers, null is returned.</em> - * @deprecated Use {@link #queryContentProviders(String, int, ComponentInfoFlags)} instead. */ - @Deprecated @NonNull public abstract List<ProviderInfo> queryContentProviders( @Nullable String processName, int uid, int flags); @@ -7687,11 +7680,11 @@ public abstract class PackageManager { * to mark GAL providers, rather than intent filters, so we can't use * {@link #queryIntentContentProviders} for that. * + * Use {@link #queryContentProviders(String, int, ComponentInfoFlags, String)} when long flags + * are needed. + * * @hide - * @deprecated Use {@link #queryContentProviders(String, int, ComponentInfoFlags, String)} - * instead. */ - @Deprecated @NonNull public List<ProviderInfo> queryContentProviders(@Nullable String processName, int uid, int flags, String metaDataKey) { @@ -8237,13 +8230,13 @@ public abstract class PackageManager { * Retrieve overall information about an application package defined in a * package archive file * + * Use {@link #getPackageArchiveInfo(String, PackageInfoFlags)} when long flags are needed. + * * @param archiveFilePath The path to the archive file * @param flags Additional option flags to modify the data returned. * @return A PackageInfo object containing information about the package * archive. If the package could not be parsed, returns null. - * @deprecated Use {@link #getPackageArchiveInfo(String, PackageInfoFlags)} instead. */ - @Deprecated @Nullable public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, int flags) { return getPackageArchiveInfo(archiveFilePath, PackageInfoFlags.of(flags)); diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 02b930860a84..1a3c3d97634c 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -376,8 +376,8 @@ public class ServiceInfo extends ComponentInfo * <li>Headless system apps</li> * <li><a href="{@docRoot}guide/topics/admin/device-admin">Device admin apps</a></li> * <li>Active VPN apps</li> - * <li>Apps holding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or - * {@link Manifest.permission#USE_EXACT_ALARM} permission.</li> + * <li>Apps holding {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} or + * {@link android.Manifest.permission#USE_EXACT_ALARM} permission.</li> * </ul> * </p> */ @@ -393,7 +393,7 @@ public class ServiceInfo extends ComponentInfo * * <p>Unlike other foreground service types, this type is not associated with a specific use * case, and it will not require any special permissions - * (besides {@link Manifest.permission#FOREGROUND_SERVICE}). + * (besides {@link android.Manifest.permission#FOREGROUND_SERVICE}). * * However, this type has the following restrictions. * @@ -401,19 +401,21 @@ public class ServiceInfo extends ComponentInfo * <li> * The type has a 3 minute timeout. * A foreground service of this type must be stopped within the timeout by - * {@link android.app.Service#stopSelf), - * or {@link android.content.Context#stopService). - * {@link android.app.Service#stopForeground) will also work, which will demote the + * {@link android.app.Service#stopSelf()}, + * {@link android.content.Context#stopService(android.content.Intent)} + * or their overloads). + * {@link android.app.Service#stopForeground(int)} will also work, + * which will demote the * service to a "background" service, which will soon be stopped by the system. * - * <p>The system will <em>not</em> automatically stop it. - * * <p>If the service isn't stopped within the timeout, - * {@link android.app.Service#onTimeout(int)} will be called. + * {@link android.app.Service#onTimeout(int)} will be called. Note, even when the + * system calls this callback, it will not stop the service automatically. + * You still need to stop the service using one of the aforementioned + * ways even when you get this callback. * * <p>If the service is still not stopped after the callback, - * the app will be declared an ANR after a short grace period of several seconds. - * + * the app will be declared an ANR, after a short grace period of several seconds. * <li> * A foreground service of this type cannot be made "sticky" * (see {@link android.app.Service#START_STICKY}). That is, if an app is killed @@ -427,10 +429,26 @@ public class ServiceInfo extends ComponentInfo * <a href="/guide/components/foreground-services#background-start-restrictions> * Restrictions on background starts * </a> + * <li> + * You can combine multiple foreground services types with {@code |}s, and you can + * combine + * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}. + * with other types as well. + * However, + * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} + * is for situations + * where you have no other valid foreground services to use and the timeout is long + * enough for the task, and if you can use other types, there's no point using + * this type. + * For this reason, if + * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} + * is combined with other foreground service types, the system will simply ignore + * it, and as a result, + * none of the above restrictions will apply (e.g. there'll be no timeout). * </ul> * - * <p>Note, even though - * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} + * <p>Also note, even though + * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} * was added * on Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, * it can be also used on diff --git a/core/java/android/content/pm/UserPackage.java b/core/java/android/content/pm/UserPackage.java index 7ca92c3d4777..c35e67801b71 100644 --- a/core/java/android/content/pm/UserPackage.java +++ b/core/java/android/content/pm/UserPackage.java @@ -18,14 +18,16 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.UserIdInt; -import android.os.Process; -import android.os.UserHandle; import android.util.SparseArrayMap; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import libcore.util.EmptyArray; + import java.util.Objects; +import java.util.Random; /** * POJO to represent a package for a specific user ID. @@ -34,6 +36,16 @@ import java.util.Objects; */ public final class UserPackage { private static final boolean ENABLE_CACHING = true; + /** + * The maximum number of entries to keep in the cache per user ID. + * The value should ideally be high enough to cover all packages on an end-user device, + * but low enough that stale or invalid packages would eventually (probably) get removed. + * This should benefit components that loop through all packages on a device and use this class, + * since being able to cache the objects for all packages on the device + * means we don't have to keep recreating the objects. + */ + @VisibleForTesting + static final int MAX_NUM_CACHED_ENTRIES_PER_USER = 1000; @UserIdInt public final int userId; @@ -43,11 +55,13 @@ public final class UserPackage { @GuardedBy("sCacheLock") private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>(); - private static final class NoPreloadHolder { - /** Set of userIDs to cache objects for. */ - @GuardedBy("sCacheLock") - private static int[] sUserIds = new int[]{UserHandle.getUserId(Process.myUid())}; - } + /** + * Set of userIDs to cache objects for. We start off with an empty set, so there's no caching + * by default. The system will override with a valid set of userIDs in its process so that + * caching becomes active in the system process. + */ + @GuardedBy("sCacheLock") + private static int[] sUserIds = EmptyArray.INT; private UserPackage(int userId, String packageName) { this.userId = userId; @@ -87,13 +101,14 @@ public final class UserPackage { } synchronized (sCacheLock) { - if (!ArrayUtils.contains(NoPreloadHolder.sUserIds, userId)) { + if (!ArrayUtils.contains(sUserIds, userId)) { // Don't cache objects for invalid userIds. return new UserPackage(userId, packageName); } UserPackage up = sCache.get(userId, packageName); if (up == null) { + maybePurgeRandomEntriesLocked(userId); packageName = packageName.intern(); up = new UserPackage(userId, packageName); sCache.add(userId, packageName, up); @@ -121,7 +136,7 @@ public final class UserPackage { userIds = userIds.clone(); synchronized (sCacheLock) { - NoPreloadHolder.sUserIds = userIds; + sUserIds = userIds; for (int u = sCache.numMaps() - 1; u >= 0; --u) { final int userId = sCache.keyAt(u); @@ -131,4 +146,35 @@ public final class UserPackage { } } } + + @VisibleForTesting + public static int numEntriesForUser(int userId) { + synchronized (sCacheLock) { + return sCache.numElementsForKey(userId); + } + } + + /** Purge a random set of entries if the cache size is too large. */ + @GuardedBy("sCacheLock") + private static void maybePurgeRandomEntriesLocked(int userId) { + final int uIdx = sCache.indexOfKey(userId); + if (uIdx < 0) { + return; + } + int numCached = sCache.numElementsForKeyAt(uIdx); + if (numCached < MAX_NUM_CACHED_ENTRIES_PER_USER) { + return; + } + // Purge a random set of 1% of cached elements for the userId. We don't want to use a + // deterministic system of purging because that may cause us to repeatedly remove elements + // that are frequently added and queried more than others. Choosing a random set + // means we will probably eventually remove less useful elements. + // An LRU cache is too expensive for this commonly used utility class. + final Random rand = new Random(); + final int numToPurge = Math.max(1, MAX_NUM_CACHED_ENTRIES_PER_USER / 100); + for (int i = 0; i < numToPurge && numCached > 0; ++i) { + final int removeIdx = rand.nextInt(numCached--); + sCache.deleteAt(uIdx, removeIdx); + } + } } diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java index ffdc7b3807cf..6b09c303e3cd 100644 --- a/core/java/android/content/res/FontScaleConverterFactory.java +++ b/core/java/android/content/res/FontScaleConverterFactory.java @@ -18,6 +18,7 @@ package android.content.res; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.MathUtils; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; @@ -98,22 +99,81 @@ public class FontScaleConverterFactory { public static FontScaleConverter forScale(float fontScale) { if (fontScale <= 1) { // We don't need non-linear curves for shrinking text or for 100%. - // Also, fontScale==0 should not have a curve either + // Also, fontScale==0 should not have a curve either. + // And ignore negative font scales; that's just silly. return null; } FontScaleConverter lookupTable = get(fontScale); - // TODO(b/247861716): interpolate between two tables when null + if (lookupTable != null) { + return lookupTable; + } + + // Didn't find an exact match: interpolate between two existing tables + final int index = LOOKUP_TABLES.indexOfKey(getKey(fontScale)); + if (index >= 0) { + // This should never happen, should have been covered by get() above. + return LOOKUP_TABLES.valueAt(index); + } + // Didn't find an exact match: interpolate between two existing tables + final int lowerIndex = -(index + 1) - 1; + final int higherIndex = lowerIndex + 1; + if (lowerIndex < 0 || higherIndex >= LOOKUP_TABLES.size()) { + // We have gone beyond our bounds and have nothing to interpolate between. Just give + // them a straight linear table instead. + // This works because when FontScaleConverter encounters a size beyond its bounds, it + // calculates a linear fontScale factor using the ratio of the last element pair. + return new FontScaleConverter(new float[] {1f}, new float[] {fontScale}); + } else { + float startScale = getScaleFromKey(LOOKUP_TABLES.keyAt(lowerIndex)); + float endScale = getScaleFromKey(LOOKUP_TABLES.keyAt(higherIndex)); + float interpolationPoint = MathUtils.constrainedMap( + /* rangeMin= */ 0f, + /* rangeMax= */ 1f, + startScale, + endScale, + fontScale + ); + return createInterpolatedTableBetween( + LOOKUP_TABLES.valueAt(lowerIndex), + LOOKUP_TABLES.valueAt(higherIndex), + interpolationPoint); + } + } + + @NonNull + private static FontScaleConverter createInterpolatedTableBetween( + FontScaleConverter start, + FontScaleConverter end, + float interpolationPoint + ) { + float[] commonSpSizes = new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100f}; + float[] dpInterpolated = new float[commonSpSizes.length]; + + for (int i = 0; i < commonSpSizes.length; i++) { + float sp = commonSpSizes[i]; + float startDp = start.convertSpToDp(sp); + float endDp = end.convertSpToDp(sp); + dpInterpolated[i] = MathUtils.lerp(startDp, endDp, interpolationPoint); + } + + return new FontScaleConverter(commonSpSizes, dpInterpolated); + } + + private static int getKey(float fontScale) { + return (int) (fontScale * SCALE_KEY_MULTIPLIER); + } - return lookupTable; + private static float getScaleFromKey(int key) { + return (float) key / SCALE_KEY_MULTIPLIER; } private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) { - LOOKUP_TABLES.put((int) (scaleKey * SCALE_KEY_MULTIPLIER), fontScaleConverter); + LOOKUP_TABLES.put(getKey(scaleKey), fontScaleConverter); } @Nullable private static FontScaleConverter get(float scaleKey) { - return LOOKUP_TABLES.get((int) (scaleKey * SCALE_KEY_MULTIPLIER)); + return LOOKUP_TABLES.get(getKey(scaleKey)); } } diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS index d12d920b2a54..a7bce122eb35 100644 --- a/core/java/android/content/res/OWNERS +++ b/core/java/android/content/res/OWNERS @@ -4,3 +4,5 @@ toddke@android.com toddke@google.com patb@google.com zyy@google.com + +per-file FontScaleConverter*=fuego@google.com
\ No newline at end of file diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java index b756a4323842..c89a5c62cd58 100644 --- a/core/java/android/credentials/CreateCredentialRequest.java +++ b/core/java/android/credentials/CreateCredentialRequest.java @@ -244,7 +244,7 @@ public final class CreateCredentialRequest implements Parcelable { /** A builder for {@link CreateCredentialRequest}. */ public static final class Builder { - private boolean mAlwaysSendAppInfoToProvider; + private boolean mAlwaysSendAppInfoToProvider = true; @NonNull private String mType; diff --git a/core/java/android/credentials/ui/CancelUiRequest.java b/core/java/android/credentials/ui/CancelUiRequest.java new file mode 100644 index 000000000000..6bd9de481a79 --- /dev/null +++ b/core/java/android/credentials/ui/CancelUiRequest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.credentials.ui; + +import android.annotation.NonNull; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +/** + * A request to cancel any ongoing UI matching this request. + * + * @hide + */ +public final class CancelUiRequest implements Parcelable { + + /** + * The intent extra key for the {@code CancelUiRequest} object when launching the UX + * activities. + */ + @NonNull public static final String EXTRA_CANCEL_UI_REQUEST = + "android.credentials.ui.extra.EXTRA_CANCEL_UI_REQUEST"; + + @NonNull + private final IBinder mToken; + + /** Returns the request token matching the user request that should be cancelled. */ + @NonNull + public IBinder getToken() { + return mToken; + } + + public CancelUiRequest(@NonNull IBinder token) { + mToken = token; + } + + private CancelUiRequest(@NonNull Parcel in) { + mToken = in.readStrongBinder(); + AnnotationValidations.validate(NonNull.class, null, mToken); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mToken); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull public static final Creator<CancelUiRequest> CREATOR = new Creator<>() { + @Override + public CancelUiRequest createFromParcel(@NonNull Parcel in) { + return new CancelUiRequest(in); + } + + @Override + public CancelUiRequest[] newArray(int size) { + return new CancelUiRequest[size]; + } + }; +} diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java index 67634dc0ad8c..dcfef56f86a4 100644 --- a/core/java/android/credentials/ui/IntentFactory.java +++ b/core/java/android/credentials/ui/IntentFactory.java @@ -22,6 +22,7 @@ import android.annotation.TestApi; import android.content.ComponentName; import android.content.Intent; import android.content.res.Resources; +import android.os.IBinder; import android.os.Parcel; import android.os.ResultReceiver; @@ -66,6 +67,25 @@ public class IntentFactory { } /** + * Creates an Intent that cancels any UI matching the given request token id. + * + * @hide + */ + @NonNull + public static Intent createCancelUiIntent(@NonNull IBinder requestToken) { + Intent intent = new Intent(); + ComponentName componentName = + ComponentName.unflattenFromString( + Resources.getSystem() + .getString( + com.android.internal.R.string + .config_credentialManagerDialogComponent)); + intent.setComponent(componentName); + intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST, new CancelUiRequest(requestToken)); + return intent; + } + + /** * Notify the UI that providers have been enabled/disabled. * * @hide @@ -78,7 +98,7 @@ public class IntentFactory { Resources.getSystem() .getString( com.android.internal.R.string - .config_credentialManagerDialogComponent)); + .config_credentialManagerReceiverComponent)); intent.setComponent(componentName); intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED); return intent; diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 9388ae3fd5e4..73157e62cb56 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -17,10 +17,10 @@ package android.hardware; import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS; +import static android.content.Context.DEVICE_ID_DEFAULT; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.companion.virtual.VirtualDeviceManager; @@ -92,7 +92,8 @@ public class SystemSensorManager extends SensorManager { private static native boolean nativeIsDataInjectionEnabled(long nativeInstance); private static native int nativeCreateDirectChannel( - long nativeInstance, long size, int channelType, int fd, HardwareBuffer buffer); + long nativeInstance, int deviceId, long size, int channelType, int fd, + HardwareBuffer buffer); private static native void nativeDestroyDirectChannel( long nativeInstance, int channelHandle); private static native int nativeConfigDirectChannel( @@ -695,6 +696,10 @@ public class SystemSensorManager extends SensorManager { /** @hide */ protected SensorDirectChannel createDirectChannelImpl( MemoryFile memoryFile, HardwareBuffer hardwareBuffer) { + int deviceId = mContext.getDeviceId(); + if (isDeviceSensorPolicyDefault(deviceId)) { + deviceId = DEVICE_ID_DEFAULT; + } int id; int type; long size; @@ -713,8 +718,8 @@ public class SystemSensorManager extends SensorManager { } size = memoryFile.length(); - id = nativeCreateDirectChannel( - mNativeInstance, size, SensorDirectChannel.TYPE_MEMORY_FILE, fd, null); + id = nativeCreateDirectChannel(mNativeInstance, deviceId, size, + SensorDirectChannel.TYPE_MEMORY_FILE, fd, null); if (id <= 0) { throw new UncheckedIOException( new IOException("create MemoryFile direct channel failed " + id)); @@ -738,7 +743,7 @@ public class SystemSensorManager extends SensorManager { } size = hardwareBuffer.getWidth(); id = nativeCreateDirectChannel( - mNativeInstance, size, SensorDirectChannel.TYPE_HARDWARE_BUFFER, + mNativeInstance, deviceId, size, SensorDirectChannel.TYPE_HARDWARE_BUFFER, -1, hardwareBuffer); if (id <= 0) { throw new UncheckedIOException( diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 19719a88ab2e..fbc018444dc5 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -142,24 +142,18 @@ public final class CameraManager { PackageManager.PERMISSION_GRANTED; } - mHandlerThread = new HandlerThread(TAG); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); mFoldStateListener = new FoldStateListener(context); try { - context.getSystemService(DeviceStateManager.class) - .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener); + context.getSystemService(DeviceStateManager.class).registerCallback( + new HandlerExecutor(CameraManagerGlobal.get().getDeviceStateHandler()), + mFoldStateListener); } catch (IllegalStateException e) { Log.v(TAG, "Failed to register device state listener!"); Log.v(TAG, "Device state dependent characteristics updates will not be functional!"); - mHandlerThread.quitSafely(); - mHandler = null; mFoldStateListener = null; } } - private HandlerThread mHandlerThread; - private Handler mHandler; private FoldStateListener mFoldStateListener; /** @@ -1645,6 +1639,9 @@ public final class CameraManager { private ICameraService mCameraService; private boolean mHasOpenCloseListenerPermission = false; + private HandlerThread mDeviceStateHandlerThread; + private Handler mDeviceStateHandler; + // Singleton, don't allow construction private CameraManagerGlobal() { } @@ -1658,6 +1655,18 @@ public final class CameraManager { return gCameraManager; } + public Handler getDeviceStateHandler() { + synchronized(mLock) { + if (mDeviceStateHandlerThread == null) { + mDeviceStateHandlerThread = new HandlerThread(TAG); + mDeviceStateHandlerThread.start(); + mDeviceStateHandler = new Handler(mDeviceStateHandlerThread.getLooper()); + } + + return mDeviceStateHandler; + } + } + @Override public IBinder asBinder() { return this; diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index b766cd19cdb0..50dd7a0bc1be 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -989,24 +989,6 @@ public final class DisplayManager { /** * Creates a virtual display. - * - * @see #createVirtualDisplay(String, int, int, int, float, Surface, int, - * Handler, VirtualDisplay.Callback) - */ - @Nullable - public VirtualDisplay createVirtualDisplay(@NonNull String name, - @IntRange(from = 1) int width, - @IntRange(from = 1) int height, - @IntRange(from = 1) int densityDpi, - float requestedRefreshRate, - @Nullable Surface surface, - @VirtualDisplayFlag int flags) { - return createVirtualDisplay(name, width, height, densityDpi, requestedRefreshRate, - surface, flags, null, null); - } - - /** - * Creates a virtual display. * <p> * The content of a virtual display is rendered to a {@link Surface} provided * by the application. @@ -1056,8 +1038,23 @@ public final class DisplayManager { @VirtualDisplayFlag int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { - return createVirtualDisplay(name, width, height, densityDpi, 0.0f, surface, - flags, handler, callback); + final VirtualDisplayConfig.Builder builder = + new VirtualDisplayConfig.Builder(name, width, height, densityDpi); + builder.setFlags(flags); + if (surface != null) { + builder.setSurface(surface); + } + return createVirtualDisplay(builder.build(), handler, callback); + } + + /** + * Creates a virtual display. + * + * @see #createVirtualDisplay(VirtualDisplayConfig, Handler, VirtualDisplay.Callback) + */ + @Nullable + public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig config) { + return createVirtualDisplay(config, /*handler=*/null, /*callback=*/null); } /** @@ -1084,21 +1081,7 @@ public final class DisplayManager { * turning off the screen. * </p> * - * @param name The name of the virtual display, must be non-empty. - * @param width The width of the virtual display in pixels, must be greater than 0. - * @param height The height of the virtual display in pixels, must be greater than 0. - * @param densityDpi The density of the virtual display in dpi, must be greater than 0. - * @param requestedRefreshRate The requested refresh rate in frames per second. - * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on - * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded - * up or down to a divisor of the physical display. If 0 is specified, the virtual - * display is refreshed at the physical display refresh rate. - * @param surface The surface to which the content of the virtual display should - * be rendered, or null if there is none initially. - * @param flags A combination of virtual display flags: - * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION}, - * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, - * or {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. + * @param config The configuration of the virtual display, must be non-null. * @param handler The handler on which the listener should be invoked, or null * if the listener should be invoked on the calling thread's looper. * @param callback Callback to call when the state of the {@link VirtualDisplay} changes @@ -1106,33 +1089,14 @@ public final class DisplayManager { * not create the virtual display. * * @throws SecurityException if the caller does not have permission to create - * a virtual display with the specified flags. + * a virtual display with flags specified in the configuration. */ @Nullable - public VirtualDisplay createVirtualDisplay(@NonNull String name, - @IntRange(from = 1) int width, - @IntRange(from = 1) int height, - @IntRange(from = 1) int densityDpi, - float requestedRefreshRate, - @Nullable Surface surface, - @VirtualDisplayFlag int flags, + public VirtualDisplay createVirtualDisplay( + @NonNull VirtualDisplayConfig config, @Nullable Handler handler, @Nullable VirtualDisplay.Callback callback) { - if (!ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE && requestedRefreshRate != 0.0f) { - Slog.e(TAG, "Please turn on ENABLE_VIRTUAL_DISPLAY_REFRESH_RATE to use the new api"); - return null; - } - - final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width, - height, densityDpi); - builder.setFlags(flags); - if (surface != null) { - builder.setSurface(surface); - } - if (requestedRefreshRate != 0.0f) { - builder.setRequestedRefreshRate(requestedRefreshRate); - } - return createVirtualDisplay(null /* projection */, builder.build(), callback, handler, + return createVirtualDisplay(null /* projection */, config, callback, handler, null /* windowContext */); } diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java index f6a2e33c7164..6b56a067a198 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -18,121 +18,44 @@ package android.hardware.display; import static android.view.Display.DEFAULT_DISPLAY; +import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.display.DisplayManager.VirtualDisplayFlag; import android.media.projection.MediaProjection; +import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; +import android.view.Display; import android.view.Surface; -import com.android.internal.util.DataClass; - import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; /** - * Holds configuration used to create {@link VirtualDisplay} instances. See - * {@link MediaProjection#createVirtualDisplay} and - * {@link android.companion.virtual.VirtualDeviceManager.VirtualDevice#createVirtualDisplay}. + * Holds configuration used to create {@link VirtualDisplay} instances. * - * @hide + * @see DisplayManager#createVirtualDisplay(VirtualDisplayConfig, Handler, VirtualDisplay.Callback) + * @see MediaProjection#createVirtualDisplay */ -@DataClass(genParcelable = true, genAidl = true, genBuilder = true) public final class VirtualDisplayConfig implements Parcelable { - /** - * The name of the virtual display, must be non-empty. - */ - @NonNull - private String mName; - - /** - * The width of the virtual display in pixels. Must be greater than 0. - */ - @IntRange(from = 1) - private int mWidth; - - /** - * The height of the virtual display in pixels. Must be greater than 0. - */ - @IntRange(from = 1) - private int mHeight; - - /** - * The density of the virtual display in dpi. Must be greater than 0. - */ - @IntRange(from = 1) - private int mDensityDpi; - - /** - * A combination of virtual display flags. - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}, - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}, - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE}, - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, - * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. - */ - @VirtualDisplayFlag - private int mFlags = 0; - - /** - * The surface to which the content of the virtual display should be rendered, or null if - * there is none initially. - */ - @Nullable - private Surface mSurface = null; - - /** - * The unique identifier for the display. Shouldn't be displayed to the user. - * @hide - */ - @Nullable - private String mUniqueId = null; - - /** - * The id of the display that the virtual display should mirror, or - * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially. - */ - private int mDisplayIdToMirror = DEFAULT_DISPLAY; - - /** - * Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or - * if DisplayManager should record contents instead. - */ - private boolean mWindowManagerMirroring = false; - - /** - * The display categories. If set, only corresponding activities from the same category can be - * shown on the display. - */ - @DataClass.PluralOf("displayCategory") - @NonNull private List<String> mDisplayCategories = new ArrayList<>(); - - /** - * The refresh rate of a virtual display in frames per second. - * If this value is non-zero, this is the requested refresh rate to set. - * If this value is zero, the system chooses a default refresh rate. - */ - private float mRequestedRefreshRate = 0.0f; - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - /* package-private */ VirtualDisplayConfig( + private final String mName; + private final int mWidth; + private final int mHeight; + private final int mDensityDpi; + private final int mFlags; + private final Surface mSurface; + private final String mUniqueId; + private final int mDisplayIdToMirror; + private final boolean mWindowManagerMirroring; + private ArrayList<String> mDisplayCategories = null; + private final float mRequestedRefreshRate; + + private VirtualDisplayConfig( @NonNull String name, @IntRange(from = 1) int width, @IntRange(from = 1) int height, @@ -142,219 +65,200 @@ public final class VirtualDisplayConfig implements Parcelable { @Nullable String uniqueId, int displayIdToMirror, boolean windowManagerMirroring, - @NonNull List<String> displayCategories, + @NonNull ArrayList<String> displayCategories, float requestedRefreshRate) { - this.mName = name; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mName); - this.mWidth = width; - com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, mWidth, - "from", 1); - this.mHeight = height; - com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, mHeight, - "from", 1); - this.mDensityDpi = densityDpi; - com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, mDensityDpi, - "from", 1); - this.mFlags = flags; - com.android.internal.util.AnnotationValidations.validate( - VirtualDisplayFlag.class, null, mFlags); - this.mSurface = surface; - this.mUniqueId = uniqueId; - this.mDisplayIdToMirror = displayIdToMirror; - this.mWindowManagerMirroring = windowManagerMirroring; - this.mDisplayCategories = displayCategories; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mDisplayCategories); - this.mRequestedRefreshRate = requestedRefreshRate; - - // onConstructed(); // You can define this method to get a callback + mName = name; + mWidth = width; + mHeight = height; + mDensityDpi = densityDpi; + mFlags = flags; + mSurface = surface; + mUniqueId = uniqueId; + mDisplayIdToMirror = displayIdToMirror; + mWindowManagerMirroring = windowManagerMirroring; + mDisplayCategories = displayCategories; + mRequestedRefreshRate = requestedRefreshRate; } /** - * The name of the virtual display, must be non-empty. + * Returns the name of the virtual display. */ - @DataClass.Generated.Member - public @NonNull String getName() { + @NonNull + public String getName() { return mName; } /** - * The width of the virtual display in pixels. Must be greater than 0. + * Returns the width of the virtual display in pixels. */ - @DataClass.Generated.Member - public @IntRange(from = 1) int getWidth() { + public int getWidth() { return mWidth; } /** - * The height of the virtual display in pixels. Must be greater than 0. + * Returns the height of the virtual display in pixels. */ - @DataClass.Generated.Member - public @IntRange(from = 1) int getHeight() { + public int getHeight() { return mHeight; } /** - * The density of the virtual display in dpi. Must be greater than 0. + * Returns the density of the virtual display in dpi. */ - @DataClass.Generated.Member - public @IntRange(from = 1) int getDensityDpi() { + public int getDensityDpi() { return mDensityDpi; } /** - * A combination of virtual display flags. - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}, - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}, - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE}, - * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, - * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. + * Returns the virtual display flags. + * + * @see Builder#setFlags */ - @DataClass.Generated.Member - public @VirtualDisplayFlag int getFlags() { + public int getFlags() { return mFlags; } /** - * The surface to which the content of the virtual display should be rendered, or null if - * there is none initially. + * Returns the surface to which the content of the virtual display should be rendered, if any. + * + * @see Builder#setSurface */ - @DataClass.Generated.Member - public @Nullable Surface getSurface() { + @Nullable + public Surface getSurface() { return mSurface; } /** - * The unique identifier for the display. Shouldn't be displayed to the user. - * + * Returns the unique identifier for the display. Shouldn't be displayed to the user. * @hide */ - @DataClass.Generated.Member - public @Nullable String getUniqueId() { + @Nullable + public String getUniqueId() { return mUniqueId; } /** - * The id of the display that the virtual display should mirror, or - * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially. + * Returns the id of the display that the virtual display should mirror, or + * {@link android.view.Display#DEFAULT_DISPLAY} if there is none. + * @hide */ - @DataClass.Generated.Member public int getDisplayIdToMirror() { return mDisplayIdToMirror; } /** - * Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or + * Whether if WindowManager is responsible for mirroring content to this VirtualDisplay, or * if DisplayManager should record contents instead. + * @hide */ - @DataClass.Generated.Member public boolean isWindowManagerMirroring() { return mWindowManagerMirroring; } /** - * The display categories. If set, only corresponding activities from the same category can be - * shown on the display. + * Returns the display categories. + * + * @see Builder#setDisplayCategories */ - @DataClass.Generated.Member - public @NonNull List<String> getDisplayCategories() { - return mDisplayCategories; + @NonNull + public List<String> getDisplayCategories() { + return Collections.unmodifiableList(mDisplayCategories); } /** - * The refresh rate of a virtual display in frames per second. - * If this value is none zero, this is the requested refresh rate to set. - * If this value is zero, the system chooses a default refresh rate. + * Returns the refresh rate of a virtual display in frames per second, or zero if it is using a + * default refresh rate chosen by the system. + * + * @see Builder#setRequestedRefreshRate */ - @DataClass.Generated.Member public float getRequestedRefreshRate() { return mRequestedRefreshRate; } @Override - @DataClass.Generated.Member public void writeToParcel(@NonNull Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - int flg = 0; - if (mWindowManagerMirroring) flg |= 0x100; - if (mSurface != null) flg |= 0x20; - if (mUniqueId != null) flg |= 0x40; - dest.writeInt(flg); - dest.writeString(mName); + dest.writeString8(mName); dest.writeInt(mWidth); dest.writeInt(mHeight); dest.writeInt(mDensityDpi); dest.writeInt(mFlags); - if (mSurface != null) dest.writeTypedObject(mSurface, flags); - if (mUniqueId != null) dest.writeString(mUniqueId); + dest.writeTypedObject(mSurface, flags); + dest.writeString8(mUniqueId); dest.writeInt(mDisplayIdToMirror); + dest.writeBoolean(mWindowManagerMirroring); dest.writeStringList(mDisplayCategories); dest.writeFloat(mRequestedRefreshRate); } @Override - @DataClass.Generated.Member public int describeContents() { return 0; } - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ VirtualDisplayConfig(@NonNull Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - int flg = in.readInt(); - boolean windowManagerMirroring = (flg & 0x100) != 0; - String name = in.readString(); - int width = in.readInt(); - int height = in.readInt(); - int densityDpi = in.readInt(); - int flags = in.readInt(); - Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR); - String uniqueId = (flg & 0x40) == 0 ? null : in.readString(); - int displayIdToMirror = in.readInt(); - List<String> displayCategories = new ArrayList<>(); - in.readStringList(displayCategories); - float requestedRefreshRate = in.readFloat(); - - this.mName = name; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mName); - this.mWidth = width; - com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, mWidth, - "from", 1); - this.mHeight = height; - com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, mHeight, - "from", 1); - this.mDensityDpi = densityDpi; - com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, mDensityDpi, - "from", 1); - this.mFlags = flags; - com.android.internal.util.AnnotationValidations.validate( - VirtualDisplayFlag.class, null, mFlags); - this.mSurface = surface; - this.mUniqueId = uniqueId; - this.mDisplayIdToMirror = displayIdToMirror; - this.mWindowManagerMirroring = windowManagerMirroring; - this.mDisplayCategories = displayCategories; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mDisplayCategories); - this.mRequestedRefreshRate = requestedRefreshRate; - - // onConstructed(); // You can define this method to get a callback + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof VirtualDisplayConfig)) { + return false; + } + VirtualDisplayConfig that = (VirtualDisplayConfig) o; + return Objects.equals(mName, that.mName) + && mWidth == that.mWidth + && mHeight == that.mHeight + && mDensityDpi == that.mDensityDpi + && mFlags == that.mFlags + && Objects.equals(mSurface, that.mSurface) + && Objects.equals(mUniqueId, that.mUniqueId) + && mDisplayIdToMirror == that.mDisplayIdToMirror + && mWindowManagerMirroring == that.mWindowManagerMirroring + && Objects.equals(mDisplayCategories, that.mDisplayCategories) + && mRequestedRefreshRate == that.mRequestedRefreshRate; + } + + @Override + public int hashCode() { + int hashCode = Objects.hash( + mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId, + mDisplayIdToMirror, mWindowManagerMirroring, mDisplayCategories, + mRequestedRefreshRate); + return hashCode; + } + + @Override + @NonNull + public String toString() { + return "VirtualDisplayConfig(" + + " mName=" + mName + + " mHeight=" + mHeight + + " mWidth=" + mWidth + + " mDensityDpi=" + mDensityDpi + + " mFlags=" + mFlags + + " mSurface=" + mSurface + + " mUniqueId=" + mUniqueId + + " mDisplayIdToMirror=" + mDisplayIdToMirror + + " mWindowManagerMirroring=" + mWindowManagerMirroring + + " mDisplayCategories=" + mDisplayCategories + + " mRequestedRefreshRate=" + mRequestedRefreshRate + + ")"; + } + + private VirtualDisplayConfig(@NonNull Parcel in) { + mName = in.readString8(); + mWidth = in.readInt(); + mHeight = in.readInt(); + mDensityDpi = in.readInt(); + mFlags = in.readInt(); + mSurface = in.readTypedObject(Surface.CREATOR); + mUniqueId = in.readString8(); + mDisplayIdToMirror = in.readInt(); + mWindowManagerMirroring = in.readBoolean(); + mDisplayCategories = new ArrayList<>(); + in.readStringList(mDisplayCategories); + mRequestedRefreshRate = in.readFloat(); } - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<VirtualDisplayConfig> CREATOR + @NonNull + public static final Parcelable.Creator<VirtualDisplayConfig> CREATOR = new Parcelable.Creator<VirtualDisplayConfig>() { @Override public VirtualDisplayConfig[] newArray(int size) { @@ -368,229 +272,161 @@ public final class VirtualDisplayConfig implements Parcelable { }; /** - * A builder for {@link VirtualDisplayConfig} + * A builder for {@link VirtualDisplayConfig}. */ - @SuppressWarnings("WeakerAccess") - @DataClass.Generated.Member public static final class Builder { - - private @NonNull String mName; - private @IntRange(from = 1) int mWidth; - private @IntRange(from = 1) int mHeight; - private @IntRange(from = 1) int mDensityDpi; - private @VirtualDisplayFlag int mFlags; - private @Nullable Surface mSurface; - private @Nullable String mUniqueId; - private int mDisplayIdToMirror; - private boolean mWindowManagerMirroring; - private @NonNull List<String> mDisplayCategories; - private float mRequestedRefreshRate; - - private long mBuilderFieldsSet = 0L; + private final String mName; + private final int mWidth; + private final int mHeight; + private final int mDensityDpi; + private int mFlags = 0; + private Surface mSurface = null; + private String mUniqueId = null; + private int mDisplayIdToMirror = DEFAULT_DISPLAY; + private boolean mWindowManagerMirroring = false; + private ArrayList<String> mDisplayCategories = new ArrayList<>(); + private float mRequestedRefreshRate = 0.0f; /** * Creates a new Builder. * - * @param name - * The name of the virtual display, must be non-empty. - * @param width - * The width of the virtual display in pixels. Must be greater than 0. - * @param height - * The height of the virtual display in pixels. Must be greater than 0. - * @param densityDpi - * The density of the virtual display in dpi. Must be greater than 0. + * @param name The name of the virtual display, must be non-empty. + * @param width The width of the virtual display in pixels. Must be greater than 0. + * @param height The height of the virtual display in pixels. Must be greater than 0. + * @param densityDpi The density of the virtual display in dpi. Must be greater than 0. */ public Builder( @NonNull String name, @IntRange(from = 1) int width, @IntRange(from = 1) int height, @IntRange(from = 1) int densityDpi) { + if (name == null) { + throw new IllegalArgumentException("Virtual display name is required"); + } + if (width <= 0) { + throw new IllegalArgumentException("Virtual display width must be positive"); + } + if (height <= 0) { + throw new IllegalArgumentException("Virtual display height must be positive"); + } + if (densityDpi <= 0) { + throw new IllegalArgumentException("Virtual display density must be positive"); + } mName = name; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mName); mWidth = width; - com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, mWidth, - "from", 1); mHeight = height; - com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, mHeight, - "from", 1); mDensityDpi = densityDpi; - com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, mDensityDpi, - "from", 1); - } - - /** - * The name of the virtual display, must be non-empty. - */ - @DataClass.Generated.Member - public @NonNull Builder setName(@NonNull String value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x1; - mName = value; - return this; - } - - /** - * The width of the virtual display in pixels. Must be greater than 0. - */ - @DataClass.Generated.Member - public @NonNull Builder setWidth(@IntRange(from = 1) int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x2; - mWidth = value; - return this; - } - - /** - * The height of the virtual display in pixels. Must be greater than 0. - */ - @DataClass.Generated.Member - public @NonNull Builder setHeight(@IntRange(from = 1) int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x4; - mHeight = value; - return this; - } - - /** - * The density of the virtual display in dpi. Must be greater than 0. - */ - @DataClass.Generated.Member - public @NonNull Builder setDensityDpi(@IntRange(from = 1) int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x8; - mDensityDpi = value; - return this; } /** - * A combination of virtual display flags. + * Sets the virtual display flags, a combination of * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC}, * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PRESENTATION}, * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_SECURE}, * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}, * or {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}. */ - @DataClass.Generated.Member - public @NonNull Builder setFlags(@VirtualDisplayFlag int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x10; - mFlags = value; + @NonNull + public Builder setFlags(@VirtualDisplayFlag int flags) { + mFlags = flags; return this; } /** - * The surface to which the content of the virtual display should be rendered, or null if - * there is none initially. + * Sets the surface to which the content of the virtual display should be rendered. + * + * <p>The surface can also be set after the display creation using + * {@link VirtualDisplay#setSurface(Surface)}. */ - @DataClass.Generated.Member - public @NonNull Builder setSurface(@NonNull Surface value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x20; - mSurface = value; + @NonNull + public Builder setSurface(@Nullable Surface surface) { + mSurface = surface; return this; } /** - * The unique identifier for the display. Shouldn't be displayed to the user. - * + * Sets the unique identifier for the display. * @hide */ - @DataClass.Generated.Member - public @NonNull Builder setUniqueId(@NonNull String value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x40; - mUniqueId = value; + @NonNull + public Builder setUniqueId(@Nullable String uniqueId) { + mUniqueId = uniqueId; return this; } /** - * The id of the display that the virtual display should mirror, or - * {@link android.view.Display#DEFAULT_DISPLAY} if there is none initially. + * Sets the id of the display that the virtual display should mirror. + * @hide */ - @DataClass.Generated.Member - public @NonNull Builder setDisplayIdToMirror(int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x80; - mDisplayIdToMirror = value; + @NonNull + public Builder setDisplayIdToMirror(int displayIdToMirror) { + mDisplayIdToMirror = displayIdToMirror; return this; } /** - * Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or - * if DisplayManager should record contents instead. + * Sets whether WindowManager is responsible for mirroring content to this VirtualDisplay. + * If unset or false, DisplayManager should record contents instead. + * @hide */ - @DataClass.Generated.Member - public @NonNull Builder setWindowManagerMirroring(boolean value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x100; - mWindowManagerMirroring = value; + @NonNull + public Builder setWindowManagerMirroring(boolean windowManagerMirroring) { + mWindowManagerMirroring = windowManagerMirroring; return this; } /** - * The display categories. If set, only corresponding activities from the same category can be - * shown on the display. + * Sets the display categories. + * + * <p>The categories of the display indicate the type of activities allowed to run on that + * display. Activities can declare a display category using + * {@link android.content.pm.ActivityInfo#requiredDisplayCategory}. */ - @DataClass.Generated.Member - public @NonNull Builder setDisplayCategories(@NonNull List<String> value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x200; - mDisplayCategories = value; + @NonNull + public Builder setDisplayCategories(@NonNull List<String> displayCategories) { + mDisplayCategories.clear(); + mDisplayCategories.addAll(Objects.requireNonNull(displayCategories)); return this; } - /** @see #setDisplayCategories */ - @DataClass.Generated.Member - public @NonNull Builder addDisplayCategory(@NonNull String value) { - if (mDisplayCategories == null) setDisplayCategories(new ArrayList<>()); - mDisplayCategories.add(value); + /** + * Adds a display category. + * + * @see #setDisplayCategories + */ + @NonNull + public Builder addDisplayCategory(@NonNull String displayCategory) { + mDisplayCategories.add(Objects.requireNonNull(displayCategory)); return this; } /** - * The refresh rate of a virtual display in frames per second. - * If this value is none zero, this is the requested refresh rate to set. - * If this value is zero, the system chooses a default refresh rate. + * Sets the refresh rate of a virtual display in frames per second. + * + * <p>For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on + * a 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded up + * down to a divisor of the physical display. If unset or zero, the virtual display will be + * refreshed at the physical display refresh rate. + * + * @see Display#getRefreshRate() */ - @DataClass.Generated.Member - public @NonNull Builder setRequestedRefreshRate(float value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x400; - mRequestedRefreshRate = value; + @NonNull + public Builder setRequestedRefreshRate( + @FloatRange(from = 0.0f) float requestedRefreshRate) { + if (requestedRefreshRate < 0.0f) { + throw new IllegalArgumentException( + "Virtual display requested refresh rate must be non-negative"); + } + mRequestedRefreshRate = requestedRefreshRate; return this; } - /** Builds the instance. This builder should not be touched after calling this! */ - public @NonNull VirtualDisplayConfig build() { - checkNotUsed(); - mBuilderFieldsSet |= 0x800; // Mark builder used - - if ((mBuilderFieldsSet & 0x10) == 0) { - mFlags = 0; - } - if ((mBuilderFieldsSet & 0x20) == 0) { - mSurface = null; - } - if ((mBuilderFieldsSet & 0x40) == 0) { - mUniqueId = null; - } - if ((mBuilderFieldsSet & 0x80) == 0) { - mDisplayIdToMirror = DEFAULT_DISPLAY; - } - if ((mBuilderFieldsSet & 0x100) == 0) { - mWindowManagerMirroring = false; - } - if ((mBuilderFieldsSet & 0x200) == 0) { - mDisplayCategories = new ArrayList<>(); - } - if ((mBuilderFieldsSet & 0x400) == 0) { - mRequestedRefreshRate = 0.0f; - } - VirtualDisplayConfig o = new VirtualDisplayConfig( + /** + * Builds the {@link VirtualDisplayConfig} instance. + */ + @NonNull + public VirtualDisplayConfig build() { + return new VirtualDisplayConfig( mName, mWidth, mHeight, @@ -602,27 +438,6 @@ public final class VirtualDisplayConfig implements Parcelable { mWindowManagerMirroring, mDisplayCategories, mRequestedRefreshRate); - return o; - } - - private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x800) != 0) { - throw new IllegalStateException( - "This Builder should not be reused. Use a new Builder instance instead"); - } } } - - @DataClass.Generated( - time = 1671047069703L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java", - inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate boolean mWindowManagerMirroring\nprivate @com.android.internal.util.DataClass.PluralOf(\"displayCategory\") @android.annotation.NonNull java.util.List<java.lang.String> mDisplayCategories\nprivate float mRequestedRefreshRate\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - } diff --git a/core/java/android/hardware/face/FaceAuthenticateOptions.java b/core/java/android/hardware/face/FaceAuthenticateOptions.java index 4009fa7682fe..1c6de0464245 100644 --- a/core/java/android/hardware/face/FaceAuthenticateOptions.java +++ b/core/java/android/hardware/face/FaceAuthenticateOptions.java @@ -52,7 +52,7 @@ public class FaceAuthenticateOptions implements AuthenticateOptions, Parcelable } /** The sensor id for this operation. */ - private final int mSensorId; + private int mSensorId; private static int defaultSensorId() { return -1; } @@ -299,6 +299,15 @@ public class FaceAuthenticateOptions implements AuthenticateOptions, Parcelable } /** + * The sensor id for this operation. + */ + @DataClass.Generated.Member + public @NonNull FaceAuthenticateOptions setSensorId( int value) { + mSensorId = value; + return this; + } + + /** * The package name for that operation that should be used for * {@link android.app.AppOpsManager} verification. * @@ -610,10 +619,10 @@ public class FaceAuthenticateOptions implements AuthenticateOptions, Parcelable } @DataClass.Generated( - time = 1676508211385L, + time = 1677119626034L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/hardware/face/FaceAuthenticateOptions.java", - inputSignatures = "private final int mUserId\nprivate final int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final int AUTHENTICATE_REASON_UNKNOWN\npublic static final int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static int defaultDisplayState()\nprivate static int defaultAuthenticateReason()\nprivate static int defaultWakeReason()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)") + inputSignatures = "private final int mUserId\nprivate int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final int AUTHENTICATE_REASON_UNKNOWN\npublic static final int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static int defaultDisplayState()\nprivate static int defaultAuthenticateReason()\nprivate static int defaultWakeReason()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 2857627bf712..9d5073e43957 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -68,7 +68,7 @@ interface IFaceService { // by BiometricService. To start authentication after the clients are ready, use // startPreparedClient(). @EnforcePermission("USE_BIOMETRIC_INTERNAL") - void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token, + void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId, IBiometricSensorReceiver sensorReceiver, in FaceAuthenticateOptions options, long requestId, int cookie, boolean allowBackgroundAuthentication); diff --git a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java index cecb3172a8d1..763246e25a26 100644 --- a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java +++ b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java @@ -46,7 +46,7 @@ public final class FingerprintAuthenticateOptions implements AuthenticateOptions } /** The sensor id for this operation. */ - private final int mSensorId; + private int mSensorId; private static int defaultSensorId() { return SENSOR_ID_ANY; } @@ -176,6 +176,15 @@ public final class FingerprintAuthenticateOptions implements AuthenticateOptions } /** + * The sensor id for this operation. + */ + @DataClass.Generated.Member + public @NonNull FingerprintAuthenticateOptions setSensorId( int value) { + mSensorId = value; + return this; + } + + /** * The package name for that operation that should be used for * {@link android.app.AppOpsManager} verification. * @@ -433,10 +442,10 @@ public final class FingerprintAuthenticateOptions implements AuthenticateOptions } @DataClass.Generated( - time = 1676508212083L, + time = 1677119626721L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java", - inputSignatures = "private final int mUserId\nprivate final int mSensorId\nprivate final boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static boolean defaultIgnoreEnrollmentState()\nprivate static int defaultDisplayState()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)") + inputSignatures = "private final int mUserId\nprivate int mSensorId\nprivate final boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static boolean defaultIgnoreEnrollmentState()\nprivate static int defaultDisplayState()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index e3ae299be730..ec5749ed4f05 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -73,8 +73,8 @@ interface IFingerprintService { // by BiometricService. To start authentication after the clients are ready, use // startPreparedClient(). @EnforcePermission("MANAGE_BIOMETRIC") - void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId, - IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId, + void prepareForAuthentication(IBinder token, long operationId, + IBiometricSensorReceiver sensorReceiver, in FingerprintAuthenticateOptions options, long requestId, int cookie, boolean allowBackgroundAuthentication); // Starts authentication with the previously prepared client. diff --git a/core/java/android/os/CancellationSignalBeamer.java b/core/java/android/os/CancellationSignalBeamer.java new file mode 100644 index 000000000000..afb5ff7bf626 --- /dev/null +++ b/core/java/android/os/CancellationSignalBeamer.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.system.SystemCleaner; + +import java.lang.ref.Cleaner; +import java.lang.ref.Reference; +import java.util.HashMap; + +/** + * A transport for {@link CancellationSignal}, but unlike + * {@link CancellationSignal#createTransport()} doesn't require pre-creating the transport in the + * target process. Instead, cancellation is forwarded over the same IPC surface as the cancellable + * request. + * + * <p><strong>Important:</strong> For this to work, the following invariants must be held up: + * <ul> + * <li>A call to beam() <strong>MUST</strong> result in a call to close() on the result + * (otherwise, the token will be leaked and cancellation isn't propagated), and that call + * must happen after the call using the + * token is sent (otherwise, any concurrent cancellation may be lost). It is strongly + * recommended to use try-with-resources on the token. + * <li>The cancel(), forget() and cancellable operations transporting the token must either + * all be oneway on the same binder, or all be non-oneway to guarantee proper ordering. + * <li>A {@link CancellationSignal} <strong>SHOULD</strong> be used only once, as there + * can only be a single {@link android.os.CancellationSignal.OnCancelListener OnCancelListener}. + * + * </ul> + * <p>Caveats: + * <ul> + * <li>Cancellation is only ever dispatched after the token is closed, and thus after the + * call performing the cancellable operation (if the invariants are followed). The operation + * must therefore not block the incoming binder thread, or cancellation won't be possible. + * <li>Consequently, in the unlikely event that the sender dies right after beaming an already + * cancelled {@link CancellationSignal}, the cancellation may be lost (unlike with + * {@link CancellationSignal#createTransport()}). + * <li>The forwarding OnCancelListener is set in the implied finally phase of try-with-resources + * / when closing the token. If the receiver is in the same process, and the signal is + * already cancelled, this may invoke the target's OnCancelListener during that phase. + * </ul> + * + * + * <p>Usage: + * <pre> + * // Sender: + * + * class FooManager { + * var mCancellationSignalSender = new CancellationSignalBeamer.Sender() { + * @Override + * public void onCancel(IBinder token) { remoteIFooService.onCancelToken(token); } + * + * @Override + * public void onForget(IBinder token) { remoteIFooService.onForgetToken(token); } + * }; + * + * public void doCancellableOperation(..., CancellationSignal cs) { + * try (var csToken = mCancellationSignalSender.beam(cs)) { + * remoteIFooService.doCancellableOperation(..., csToken); + * } + * } + * } + * + * // Receiver: + * + * class FooManagerService extends IFooService.Stub { + * var mCancellationSignalReceiver = new CancellationSignalBeamer.Receiver(); + * + * @Override + * public void doCancellableOperation(..., IBinder csToken) { + * CancellationSignal cs = mCancellationSignalReceiver.unbeam(csToken)) + * // ... + * } + * + * @Override + * public void onCancelToken(..., IBinder csToken) { + * mCancellationSignalReceiver.cancelToken(csToken)) + * } + * + * @Override + * public void onForgetToken(..., IBinder csToken) { + * mCancellationSignalReceiver.forgetToken(csToken)) + * } + * } + * + * </pre> + * + * @hide + */ +public class CancellationSignalBeamer { + + static final Cleaner sCleaner = SystemCleaner.cleaner(); + + /** The sending side of an {@link CancellationSignalBeamer} */ + public abstract static class Sender { + + /** + * Beams a {@link CancellationSignal} through an existing Binder interface. + * + * @param cs the {@code CancellationSignal} to beam, or {@code null}. + * @return an {@link IBinder} token. MUST be {@link CloseableToken#close}d <em>after</em> + * the binder call transporting it to the remote process, best with + * try-with-resources. {@code null} if {@code cs} was {@code null}. + */ + // TODO(b/254888024): @MustBeClosed + @Nullable + public CloseableToken beam(@Nullable CancellationSignal cs) { + if (cs == null) { + return null; + } + return new Token(this, cs); + } + + /** + * A {@link #beam}ed {@link CancellationSignal} was closed. + * + * MUST be forwarded to {@link Receiver#cancel} with proper ordering. See + * {@link CancellationSignalBeamer} for details. + */ + public abstract void onCancel(IBinder token); + + /** + * A {@link #beam}ed {@link CancellationSignal} was GC'd. + * + * MUST be forwarded to {@link Receiver#forget} with proper ordering. See + * {@link CancellationSignalBeamer} for details. + */ + public abstract void onForget(IBinder token); + + private static class Token extends Binder implements CloseableToken, Runnable { + + private final Sender mSender; + private Preparer mPreparer; + + private Token(Sender sender, CancellationSignal signal) { + mSender = sender; + mPreparer = new Preparer(sender, signal, this); + } + + @Override + public void close() { + Preparer preparer = mPreparer; + mPreparer = null; + if (preparer != null) { + preparer.setup(); + } + } + + @Override + public void run() { + mSender.onForget(this); + } + + private static class Preparer implements CancellationSignal.OnCancelListener { + private final Sender mSender; + private final CancellationSignal mSignal; + private final Token mToken; + + private Preparer(Sender sender, CancellationSignal signal, Token token) { + mSender = sender; + mSignal = signal; + mToken = token; + } + + void setup() { + sCleaner.register(this, mToken); + mSignal.setOnCancelListener(this); + } + + @Override + public void onCancel() { + try { + mSender.onCancel(mToken); + } finally { + // Make sure we dispatch onCancel before the cleaner can run. + Reference.reachabilityFence(this); + } + } + } + } + + /** + * A {@link #beam}ed {@link CancellationSignal} ready for sending over Binder. + * + * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources. + */ + public interface CloseableToken extends IBinder, AutoCloseable { + @Override + void close(); // No throws + } + } + + /** The receiving side of a {@link CancellationSignalBeamer}. */ + public static class Receiver implements IBinder.DeathRecipient { + private final HashMap<IBinder, CancellationSignal> mTokenMap = new HashMap<>(); + private final boolean mCancelOnSenderDeath; + + /** + * Constructs a new {@code Receiver}. + * + * @param cancelOnSenderDeath if true, {@link CancellationSignal}s obtained from + * {@link #unbeam} are automatically {@link #cancel}led if the sender token + * {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the + * sending process drops all references to the {@link CancellationSignal} before + * process death, the cancellation is not guaranteed. + */ + public Receiver(boolean cancelOnSenderDeath) { + mCancelOnSenderDeath = cancelOnSenderDeath; + } + + /** + * Unbeams a token that was obtained via {@link Sender#beam} and turns it back into a + * {@link CancellationSignal}. + * + * A subsequent call to {@link #cancel} with the same token will cancel the returned + * {@code CancellationSignal}. + * + * @param token a token that was obtained from {@link Sender}, possibly in a remote process. + * @return a {@link CancellationSignal} linked to the given token. + */ + @Nullable + public CancellationSignal unbeam(@Nullable IBinder token) { + if (token == null) { + return null; + } + synchronized (this) { + CancellationSignal cs = mTokenMap.get(token); + if (cs != null) { + return cs; + } + + cs = new CancellationSignal(); + mTokenMap.put(token, cs); + try { + token.linkToDeath(this, 0); + } catch (RemoteException e) { + dead(token); + } + return cs; + } + } + + /** + * Forgets state associated with the given token (if any). + * + * Subsequent calls to {@link #cancel} or binder death notifications on the token will not + * have any effect. + * + * This MUST be invoked when forwarding {@link Sender#onForget}, otherwise the token and + * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed. + * + * Optionally, the receiving service logic may also invoke this if it can guarantee that + * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation + * using the CancellationSignal has been fully completed). + * + * @param token the token to forget. No-op if {@code null}. + */ + public void forget(@Nullable IBinder token) { + synchronized (this) { + if (mTokenMap.remove(token) != null) { + token.unlinkToDeath(this, 0); + } + } + } + + /** + * Cancels the {@link CancellationSignal} associated with the given token (if any). + * + * This MUST be invoked when forwarding {@link Sender#onCancel}, otherwise the token and + * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed. + * + * Optionally, the receiving service logic may also invoke this if it can guarantee that + * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation + * using the CancellationSignal has been fully completed). + * + * @param token the token to forget. No-op if {@code null}. + */ + public void cancel(@Nullable IBinder token) { + CancellationSignal cs; + synchronized (this) { + cs = mTokenMap.get(token); + if (cs != null) { + forget(token); + } else { + return; + } + } + cs.cancel(); + } + + private void dead(@NonNull IBinder token) { + if (mCancelOnSenderDeath) { + cancel(token); + } else { + forget(token); + } + } + + @Override + public void binderDied(@NonNull IBinder who) { + dead(who); + } + + @Override + public void binderDied() { + throw new RuntimeException("unreachable"); + } + } +} diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index de94b867382e..8d8deaa28df1 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -3057,6 +3057,7 @@ public class UserManager { * * @hide */ + @TestApi public int getDisplayIdAssignedToUser() { try { return mService.getDisplayIdAssignedToUser(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index acb1f5bede9b..a3f859909ec5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -99,7 +99,6 @@ import android.widget.Editor; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; -import com.android.internal.widget.ILockSettings; import java.io.IOException; import java.lang.annotation.ElementType; @@ -3218,13 +3217,13 @@ public final class Settings { } } } else { - if (LOCAL_LOGV) { + if (DEBUG || LOCAL_LOGV) { Log.v(TAG, "get setting for user " + userHandle + " by user " + UserHandle.myUserId() + " so skipping cache"); } } if (DEBUG) { - Log.i(TAG, "Cache miss for setting:" + name); + Log.i(TAG, "Cache miss for setting:" + name + " for user:" + userHandle); } // Check if the target settings key is readable. Reject if the caller is not system and @@ -6192,9 +6191,6 @@ public final class Settings { sProviderHolder, Secure.class); - private static ILockSettings sLockSettings = null; - - private static boolean sIsSystemProcess; @UnsupportedAppUsage private static final HashSet<String> MOVED_TO_LOCK_SETTINGS; @UnsupportedAppUsage @@ -6358,35 +6354,25 @@ public final class Settings { return Global.getStringForUser(resolver, name, userHandle); } - if (MOVED_TO_LOCK_SETTINGS.contains(name)) { - synchronized (Secure.class) { - if (sLockSettings == null) { - sLockSettings = ILockSettings.Stub.asInterface( - (IBinder) ServiceManager.getService("lock_settings")); - sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID; - } - } - if (sLockSettings != null && !sIsSystemProcess) { - // No context; use the ActivityThread's context as an approximation for - // determining the target API level. - Application application = ActivityThread.currentApplication(); - - boolean isPreMnc = application != null - && application.getApplicationInfo() != null - && application.getApplicationInfo().targetSdkVersion - <= VERSION_CODES.LOLLIPOP_MR1; - if (isPreMnc) { - try { - return sLockSettings.getString(name, "0", userHandle); - } catch (RemoteException re) { - // Fall through - } - } else { - throw new SecurityException("Settings.Secure." + name - + " is deprecated and no longer accessible." - + " See API documentation for potential replacements."); - } + if (MOVED_TO_LOCK_SETTINGS.contains(name) && Process.myUid() != Process.SYSTEM_UID) { + // No context; use the ActivityThread's context as an approximation for + // determining the target API level. + Application application = ActivityThread.currentApplication(); + + boolean isPreMnc = application != null + && application.getApplicationInfo() != null + && application.getApplicationInfo().targetSdkVersion + <= VERSION_CODES.LOLLIPOP_MR1; + if (isPreMnc) { + // Old apps used to get the three deprecated LOCK_PATTERN_* settings from + // ILockSettings.getString(). For security reasons, we now just return a + // stubbed-out value. Note: the only one of these three settings actually known + // to have been used was LOCK_PATTERN_ENABLED, and ILockSettings.getString() + // already always returned "0" for that starting in Android 11. + return "0"; } + throw new SecurityException("Settings.Secure." + name + " is deprecated and no" + + " longer accessible. See API documentation for potential replacements."); } return sNameValueCache.getStringForUser(resolver, name, userHandle); diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 6896e0244791..d14abfd5d7ae 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4937,4 +4937,66 @@ public final class Telephony { return ALL_COLUMNS; } } + + /** + * Stores incoming satellite datagrams. + * @hide + */ + public static final class SatelliteDatagrams { + /** + * Not instantiable. + * @hide + */ + private SatelliteDatagrams() {} + + /** + * Provider name for Satellite Datagrams table. + */ + public static final String PROVIDER_NAME = "satellite"; + + /** + * Table name for Satellite Datagrams table. + */ + public static final String TABLE_NAME = "incoming_datagrams"; + + /** + * URL for satellite incoming datagrams table. + */ + private static final String URL = "content://" + PROVIDER_NAME + "/" + TABLE_NAME; + + /** + * The {@code content://} style URI for this provider. + * @hide + */ + public static final Uri CONTENT_URI = Uri.parse(URL); + + /** + * SatelliteProvider unique key column name is the datagram id. + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String COLUMN_UNIQUE_KEY_DATAGRAM_ID = "datagram_id"; + + /** + * SatelliteProvider column name for storing datagram. + * <p>TYPE: BLOB + * @hide + */ + public static final String COLUMN_DATAGRAM = "datagram"; + + /** All columns in {@link SatelliteDatagrams} table. */ + private static final List<String> ALL_COLUMNS = List.of( + COLUMN_UNIQUE_KEY_DATAGRAM_ID, + COLUMN_DATAGRAM + ); + + /** + * @return All columns in {@link SatelliteDatagrams} table. + * @hide + */ + @NonNull + public static List<String> getAllColumns() { + return ALL_COLUMNS; + } + } } diff --git a/core/java/android/service/credentials/BeginGetCredentialOption.java b/core/java/android/service/credentials/BeginGetCredentialOption.java index 81b9f22ebc7f..1ad0424f44e3 100644 --- a/core/java/android/service/credentials/BeginGetCredentialOption.java +++ b/core/java/android/service/credentials/BeginGetCredentialOption.java @@ -116,12 +116,12 @@ public class BeginGetCredentialOption implements Parcelable { * @param id the unique id associated with this option * @param type the requested credential type * @param candidateQueryData the request candidateQueryData - * @throws IllegalArgumentException If type is empty. + * @throws IllegalArgumentException If id or type is empty. */ public BeginGetCredentialOption( @NonNull String id, @NonNull String type, @NonNull Bundle candidateQueryData) { - mId = id; + mId = Preconditions.checkStringNotEmpty(id, "id must not be empty"); mType = Preconditions.checkStringNotEmpty(type, "type must not be empty"); Bundle bundle = new Bundle(); bundle.putAll(candidateQueryData); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 12cd5236017c..f53abce1d1ea 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -2198,6 +2198,11 @@ public abstract class WallpaperService extends Service { } mCreated = false; } + + if (mSurfaceControl != null) { + mSurfaceControl.release(); + mSurfaceControl = null; + } } private final DisplayListener mDisplayListener = new DisplayListener() { diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index f648ad4fbac3..434b1c76113f 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -53,9 +53,7 @@ import com.android.internal.telephony.ITelephonyRegistry; import java.lang.ref.WeakReference; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.WeakHashMap; @@ -83,15 +81,16 @@ public class TelephonyRegistryManager { * A mapping between {@link SubscriptionManager.OnSubscriptionsChangedListener} and * its callback IOnSubscriptionsChangedListener. */ - private final Map<SubscriptionManager.OnSubscriptionsChangedListener, - IOnSubscriptionsChangedListener> mSubscriptionChangedListenerMap = new HashMap<>(); + private final ConcurrentHashMap<SubscriptionManager.OnSubscriptionsChangedListener, + IOnSubscriptionsChangedListener> + mSubscriptionChangedListenerMap = new ConcurrentHashMap<>(); /** * A mapping between {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} and * its callback IOnSubscriptionsChangedListener. */ - private final Map<SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, - IOnSubscriptionsChangedListener> mOpportunisticSubscriptionChangedListenerMap - = new HashMap<>(); + private final ConcurrentHashMap<SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, + IOnSubscriptionsChangedListener> + mOpportunisticSubscriptionChangedListenerMap = new ConcurrentHashMap<>(); /** * A mapping between {@link CarrierConfigManager.CarrierConfigChangeListener} and its callback diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index 98c0d7f4fbf1..5aa0f59024f8 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -477,7 +477,14 @@ public abstract class NtpTrustedTime implements TrustedTime { return mTimeResult; } - /** Clears the last received NTP. Intended for use during tests. */ + /** Sets the last received NTP time. Intended for use during tests. */ + public void setCachedTimeResult(TimeResult timeResult) { + synchronized (this) { + mTimeResult = timeResult; + } + } + + /** Clears the last received NTP time. Intended for use during tests. */ public void clearCachedTimeResult() { synchronized (this) { mTimeResult = null; diff --git a/core/java/android/util/SparseArrayMap.java b/core/java/android/util/SparseArrayMap.java index 1a2c4df96b36..b4e1f59874b0 100644 --- a/core/java/android/util/SparseArrayMap.java +++ b/core/java/android/util/SparseArrayMap.java @@ -90,6 +90,14 @@ public class SparseArrayMap<K, V> { } /** + * Removes the data for the keyIndex and mapIndex, if there was any. + * @hide + */ + public void deleteAt(int keyIndex, int mapIndex) { + mData.valueAt(keyIndex).removeAt(mapIndex); + } + + /** * Get the value associated with the int-K pair. */ @Nullable diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 20be9d65ab8e..547608899259 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1261,20 +1261,17 @@ public final class Display { /** * @hide - * Returns the display's HDR supported types. + * Returns the current mode's supported HDR types. * * @see #isHdr() - * @see HdrCapabilities#getSupportedHdrTypes() + * @see Mode#getSupportedHdrTypes() */ @TestApi @NonNull public int[] getReportedHdrTypes() { synchronized (mLock) { updateDisplayInfoLocked(); - if (mDisplayInfo.hdrCapabilities == null) { - return new int[0]; - } - return mDisplayInfo.hdrCapabilities.getSupportedHdrTypes(); + return mDisplayInfo.getMode().getSupportedHdrTypes(); } } diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index e02e600f53c8..39477380e196 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -20,7 +20,6 @@ import static android.view.InsetsSourceProto.FRAME; import static android.view.InsetsSourceProto.TYPE; import static android.view.InsetsSourceProto.VISIBLE; import static android.view.InsetsSourceProto.VISIBLE_FRAME; -import static android.view.ViewRootImpl.CAPTION_ON_SHELL; import static android.view.WindowInsets.Type.ime; import android.annotation.IntRange; @@ -169,7 +168,7 @@ public class InsetsSource implements Parcelable { // During drag-move and drag-resizing, the caption insets position may not get updated // before the app frame get updated. To layout the app content correctly during drag events, // we always return the insets with the corresponding height covering the top. - if (!CAPTION_ON_SHELL && getType() == WindowInsets.Type.captionBar()) { + if (getType() == WindowInsets.Type.captionBar()) { return Insets.of(0, frame.height(), 0, 0); } // Checks for whether there is shared edge with insets for 0-width/height window. diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index 9e1762065367..b574ecf32dd8 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -31,6 +31,7 @@ per-file Input*.java = file:/services/core/java/com/android/server/input/OWNERS per-file Input*.aidl = file:/services/core/java/com/android/server/input/OWNERS per-file KeyEvent.java = file:/services/core/java/com/android/server/input/OWNERS per-file MotionEvent.java = file:/services/core/java/com/android/server/input/OWNERS +per-file MotionPredictor.java = file:/services/core/java/com/android/server/input/OWNERS per-file PointerIcon.java = file:/services/core/java/com/android/server/input/OWNERS per-file SimulatedDpad.java = file:/services/core/java/com/android/server/input/OWNERS per-file BatchedInputEventReceiver.java = file:/services/core/java/com/android/server/input/OWNERS diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f430ec300b5b..2ff9ad346a71 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -11382,6 +11382,10 @@ public final class ViewRootImpl implements ViewParent, sendBackKeyEvent(KeyEvent.ACTION_DOWN); sendBackKeyEvent(KeyEvent.ACTION_UP); }; + if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) { + Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher"); + return; + } mOnBackInvokedDispatcher.registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatOnBackInvokedCallback); } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index aa631cfa5980..9504852f6e98 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -2068,12 +2068,14 @@ public final class AccessibilityManager { * {@link android.view.Display#INVALID_DISPLAY}, or is already being proxy-ed. * * @throws SecurityException if the app does not hold the - * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission. + * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission or the + * {@link Manifest.permission#CREATE_VIRTUAL_DEVICE} permission. * * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + @RequiresPermission(allOf = {Manifest.permission.MANAGE_ACCESSIBILITY, + Manifest.permission.CREATE_VIRTUAL_DEVICE}) public boolean registerDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) { final IAccessibilityManager service; synchronized (mLock) { @@ -2096,12 +2098,14 @@ public final class AccessibilityManager { * @return {@code true} if the proxy is successfully unregistered. * * @throws SecurityException if the app does not hold the - * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission. + * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission or the + * {@link Manifest.permission#CREATE_VIRTUAL_DEVICE} permission. * * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + @RequiresPermission(allOf = {Manifest.permission.MANAGE_ACCESSIBILITY, + Manifest.permission.CREATE_VIRTUAL_DEVICE}) public boolean unregisterDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) { final IAccessibilityManager service; synchronized (mLock) { diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index bdc7333f2751..aef0e651ff8d 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -899,9 +899,10 @@ public final class AutofillManager { // 3. Get the activity names substring between the indexes final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1; - if (activityStringStartIndex < firstNextSemicolonIndex) { + if (activityStringStartIndex >= firstNextSemicolonIndex) { Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly " + "formatted"); + return; } final String activitySubstring = denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex); diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index db17a533c303..d84acc03826b 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -25,6 +25,7 @@ import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.content.Context; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; @@ -33,6 +34,7 @@ import android.view.WindowManager; import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.DirectBootAwareness; +import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; @@ -40,7 +42,6 @@ import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; -import com.android.internal.view.IImeTracker; import com.android.internal.view.IInputMethodManager; import java.util.ArrayList; @@ -581,51 +582,57 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */ @AnyThread - @Nullable - static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin, - @SoftInputShowHideReason int reason) { + @NonNull + static ImeTracker.Token onRequestShow(@NonNull String tag, int uid, + @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { final IImeTracker service = getImeTrackerService(); if (service == null) { - return null; + // Create token with "fake" binder if the service was not found. + return new ImeTracker.Token(new Binder(), tag); } try { - return service.onRequestShow(uid, origin, reason); + return service.onRequestShow(tag, uid, origin, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */ @AnyThread - @Nullable - static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin, - @SoftInputShowHideReason int reason) { + @NonNull + static ImeTracker.Token onRequestHide(@NonNull String tag, int uid, + @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { final IImeTracker service = getImeTrackerService(); if (service == null) { - return null; + // Create token with "fake" binder if the service was not found. + return new ImeTracker.Token(new Binder(), tag); } try { - return service.onRequestHide(uid, origin, reason); + return service.onRequestHide(tag, uid, origin, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + /** @see com.android.server.inputmethod.ImeTrackerService#onProgress */ @AnyThread - static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + static void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; } try { - service.onProgress(statsToken, phase); + service.onProgress(binder, phase); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + /** @see com.android.server.inputmethod.ImeTrackerService#onFailed */ @AnyThread - static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + static void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; @@ -637,8 +644,9 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onCancelled */ @AnyThread - static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + static void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; @@ -650,8 +658,9 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onShown */ @AnyThread - static void onShown(@NonNull IBinder statsToken) { + static void onShown(@NonNull ImeTracker.Token statsToken) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; @@ -663,8 +672,9 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onHidden */ @AnyThread - static void onHidden(@NonNull IBinder statsToken) { + static void onHidden(@NonNull ImeTracker.Token statsToken) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; @@ -676,6 +686,7 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */ @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) static boolean hasPendingImeVisibilityRequests() { diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index e5a99ff8566b..f0d1019ffb06 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -26,7 +26,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; -import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -47,7 +46,7 @@ import java.lang.reflect.Field; import java.util.Arrays; import java.util.Locale; import java.util.Map; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; /** @hide */ @@ -321,9 +320,8 @@ public interface ImeTracker { /** * Creates an IME show request tracking token. * - * @param component the component name where the IME show request was created, - * or {@code null} otherwise - * (defaulting to {@link ActivityThread#currentProcessName()}). + * @param component the name of the component that created the IME request, or {@code null} + * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). * @param uid the uid of the client that requested the IME. * @param origin the origin of the IME show request. * @param reason the reason why the IME show request was created. @@ -337,9 +335,8 @@ public interface ImeTracker { /** * Creates an IME hide request tracking token. * - * @param component the component name where the IME hide request was created, - * or {@code null} otherwise - * (defaulting to {@link ActivityThread#currentProcessName()}). + * @param component the name of the component that created the IME request, or {@code null} + * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). * @param uid the uid of the client that requested the IME. * @param origin the origin of the IME hide request. * @param reason the reason why the IME hide request was created. @@ -435,8 +432,7 @@ public interface ImeTracker { mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false); // Update logging flag dynamically. SystemProperties.addChangeCallback(() -> - mLogProgress = - SystemProperties.getBoolean("persist.debug.imetracker", false)); + mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false)); } /** Whether progress should be logged. */ @@ -446,10 +442,9 @@ public interface ImeTracker { @Override public Token onRequestShow(@Nullable String component, int uid, @Origin int origin, @SoftInputShowHideReason int reason) { - IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason); - if (binder == null) binder = new Binder(); - - final Token token = Token.build(binder, component); + final var tag = getTag(component); + final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin, + reason); Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin) + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)); @@ -461,10 +456,9 @@ public interface ImeTracker { @Override public Token onRequestHide(@Nullable String component, int uid, @Origin int origin, @SoftInputShowHideReason int reason) { - IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason); - if (binder == null) binder = new Binder(); - - final Token token = Token.build(binder, component); + final var tag = getTag(component); + final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin, + reason); Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin) + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)); @@ -485,7 +479,7 @@ public interface ImeTracker { @Override public void onFailed(@Nullable Token token, @Phase int phase) { if (token == null) return; - IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase); + IInputMethodManagerGlobalInvoker.onFailed(token, phase); Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase)); } @@ -499,7 +493,7 @@ public interface ImeTracker { @Override public void onCancelled(@Nullable Token token, @Phase int phase) { if (token == null) return; - IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase); + IInputMethodManagerGlobalInvoker.onCancelled(token, phase); Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase)); } @@ -507,7 +501,7 @@ public interface ImeTracker { @Override public void onShown(@Nullable Token token) { if (token == null) return; - IInputMethodManagerGlobalInvoker.onShown(token.mBinder); + IInputMethodManagerGlobalInvoker.onShown(token); Log.i(TAG, token.mTag + ": onShown"); } @@ -515,10 +509,24 @@ public interface ImeTracker { @Override public void onHidden(@Nullable Token token) { if (token == null) return; - IInputMethodManagerGlobalInvoker.onHidden(token.mBinder); + IInputMethodManagerGlobalInvoker.onHidden(token); Log.i(TAG, token.mTag + ": onHidden"); } + + /** + * Returns a logging tag using the given component name. + * + * @param component the name of the component that created the IME request, or {@code null} + * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). + */ + @NonNull + private String getTag(@Nullable String component) { + if (component == null) { + component = ActivityThread.currentProcessName(); + } + return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt()); + } }; /** The singleton IME tracker instance for instrumenting jank metrics. */ @@ -528,28 +536,31 @@ public interface ImeTracker { ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker(); /** A token that tracks the progress of an IME request. */ - class Token implements Parcelable { + final class Token implements Parcelable { + /** The binder used to identify this token. */ @NonNull - public final IBinder mBinder; + private final IBinder mBinder; + /** Logging tag, of the shape "component:random_hexadecimal". */ @NonNull private final String mTag; - @NonNull - private static Token build(@NonNull IBinder binder, @Nullable String component) { - if (component == null) component = ActivityThread.currentProcessName(); - final String tag = component + ":" + Integer.toHexString((new Random().nextInt())); + public Token(@NonNull IBinder binder, @NonNull String tag) { + mBinder = binder; + mTag = tag; + } - return new Token(binder, tag); + private Token(@NonNull Parcel in) { + mBinder = in.readStrongBinder(); + mTag = in.readString8(); } - private Token(@NonNull IBinder binder, @NonNull String tag) { - mBinder = binder; - mTag = tag; + @NonNull + public IBinder getBinder() { + return mBinder; } - /** Returns the {@link Token#mTag} */ @NonNull public String getTag() { return mTag; @@ -562,7 +573,7 @@ public interface ImeTracker { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeStrongBinder(mBinder); dest.writeString8(mTag); } @@ -571,12 +582,11 @@ public interface ImeTracker { public static final Creator<Token> CREATOR = new Creator<>() { @NonNull @Override - public Token createFromParcel(Parcel source) { - final IBinder binder = source.readStrongBinder(); - final String tag = source.readString8(); - return new Token(binder, tag); + public Token createFromParcel(@NonNull Parcel in) { + return new Token(in); } + @NonNull @Override public Token[] newArray(int size) { return new Token[size]; @@ -589,40 +599,50 @@ public interface ImeTracker { * * Note: This is held in a separate class so that it only gets initialized when actually needed. */ - class Debug { + final class Debug { + @NonNull private static final Map<Integer, String> sTypes = getFieldMapping(ImeTracker.class, "TYPE_"); + @NonNull private static final Map<Integer, String> sStatus = getFieldMapping(ImeTracker.class, "STATUS_"); + @NonNull private static final Map<Integer, String> sOrigins = getFieldMapping(ImeTracker.class, "ORIGIN_"); + @NonNull private static final Map<Integer, String> sPhases = getFieldMapping(ImeTracker.class, "PHASE_"); + @NonNull public static String typeToString(@Type int type) { return sTypes.getOrDefault(type, "TYPE_" + type); } + @NonNull public static String statusToString(@Status int status) { return sStatus.getOrDefault(status, "STATUS_" + status); } + @NonNull public static String originToString(@Origin int origin) { return sOrigins.getOrDefault(origin, "ORIGIN_" + origin); } + @NonNull public static String phaseToString(@Phase int phase) { return sPhases.getOrDefault(phase, "PHASE_" + phase); } - private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) { + @NonNull + private static Map<Integer, String> getFieldMapping(Class<?> cls, + @NonNull String fieldPrefix) { return Arrays.stream(cls.getDeclaredFields()) .filter(field -> field.getName().startsWith(fieldPrefix)) .collect(Collectors.toMap(Debug::getFieldValue, Field::getName)); } - private static int getFieldValue(Field field) { + private static int getFieldValue(@NonNull Field field) { try { return field.getInt(null); } catch (IllegalAccessException e) { diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index ec1badb637e4..8b55494c75d4 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -18,6 +18,7 @@ package android.view.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -83,6 +84,22 @@ public final class InputMethodInfo implements Parcelable { public static final String ACTION_STYLUS_HANDWRITING_SETTINGS = "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS"; + /** + * Maximal length of a component name + * @hide + */ + @TestApi + public static final int COMPONENT_NAME_MAX_LENGTH = 1000; + + /** + * The maximum amount of IMEs that are loaded per package (in order). + * If a package contains more IMEs, they will be ignored and cannot be enabled. + * @hide + */ + @TestApi + @SuppressLint("MinMaxConstant") + public static final int MAX_IMES_PER_PACKAGE = 20; + static final String TAG = "InputMethodInfo"; /** @@ -252,6 +269,13 @@ public final class InputMethodInfo implements Parcelable { com.android.internal.R.styleable.InputMethod); settingsActivityComponent = sa.getString( com.android.internal.R.styleable.InputMethod_settingsActivity); + if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) || ( + settingsActivityComponent != null + && settingsActivityComponent.length() > COMPONENT_NAME_MAX_LENGTH)) { + throw new XmlPullParserException( + "Activity name exceeds maximum of 1000 characters"); + } + isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false); isDefaultResId = sa.getResourceId( com.android.internal.R.styleable.InputMethod_isDefault, 0); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index b89dd31725b7..9f9a7815932c 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -52,6 +52,7 @@ import android.graphics.RectF; import android.graphics.RenderNode; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.os.Bundle; import android.os.LocaleList; @@ -3238,6 +3239,44 @@ public class Editor { .setOnMenuItemClickListener(mOnContextMenuItemClickListener); mPreserveSelection = true; + + // No-op for the old context menu because it doesn't have icons. + adjustIconSpacing(menu); + } + + /** + * Adjust icon spacing to align the texts. + * @hide + */ + @VisibleForTesting + public void adjustIconSpacing(ContextMenu menu) { + int width = -1; + int height = -1; + for (int i = 0; i < menu.size(); ++i) { + final MenuItem item = menu.getItem(i); + final Drawable d = item.getIcon(); + if (d == null) { + continue; + } + + width = Math.max(width, d.getIntrinsicWidth()); + height = Math.max(height, d.getIntrinsicHeight()); + } + + if (width < 0 || height < 0) { + return; // No menu has icon drawable. + } + + GradientDrawable paddingDrawable = new GradientDrawable(); + paddingDrawable.setSize(width, height); + + for (int i = 0; i < menu.size(); ++i) { + final MenuItem item = menu.getItem(i); + final Drawable d = item.getIcon(); + if (d == null) { + item.setIcon(paddingDrawable); + } + } } @Nullable diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index bf9d3c2eefaf..fd86769293a6 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -34,8 +34,9 @@ oneway interface ITaskOrganizer { * has create a starting window for the Task. * * @param info The information about the Task that's available + * @param appToken Token of the application being started. */ - void addStartingWindow(in StartingWindowInfo info); + void addStartingWindow(in StartingWindowInfo info, IBinder appToken); /** * Called when the Task want to remove the starting window. diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index 071c20f25e5c..1a58fd556609 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -329,21 +329,6 @@ public class SnapshotDrawerUtils { } /** - * Get or create a TaskDescription from a RunningTaskInfo. - */ - public static ActivityManager.TaskDescription getOrCreateTaskDescription( - ActivityManager.RunningTaskInfo runningTaskInfo) { - final ActivityManager.TaskDescription taskDescription; - if (runningTaskInfo.taskDescription != null) { - taskDescription = runningTaskInfo.taskDescription; - } else { - taskDescription = new ActivityManager.TaskDescription(); - taskDescription.setBackgroundColor(WHITE); - } - return taskDescription; - } - - /** * Help method to draw the snapshot on a surface. */ public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp, @@ -359,8 +344,13 @@ public class SnapshotDrawerUtils { final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; - final ActivityManager.TaskDescription taskDescription = - getOrCreateTaskDescription(runningTaskInfo); + final ActivityManager.TaskDescription taskDescription; + if (runningTaskInfo.taskDescription != null) { + taskDescription = runningTaskInfo.taskDescription; + } else { + taskDescription = new ActivityManager.TaskDescription(); + taskDescription.setBackgroundColor(WHITE); + } drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags, attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes); final Rect systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState); diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index 451acbe84a60..1b64e613ed66 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -22,12 +22,9 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.pm.ActivityInfo; -import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; import android.view.InsetsState; -import android.view.SurfaceControl; import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager; @@ -62,8 +59,6 @@ public final class StartingWindowInfo implements Parcelable { /** @hide **/ public static final int STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN = 4; - public static final int STARTING_WINDOW_TYPE_WINDOWLESS = 5; - /** * @hide */ @@ -72,8 +67,7 @@ public final class StartingWindowInfo implements Parcelable { STARTING_WINDOW_TYPE_SPLASH_SCREEN, STARTING_WINDOW_TYPE_SNAPSHOT, STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN, - STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN, - STARTING_WINDOW_TYPE_WINDOWLESS + STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN }) public @interface StartingWindowType {} @@ -124,7 +118,6 @@ public final class StartingWindowInfo implements Parcelable { TYPE_PARAMETER_ACTIVITY_CREATED, TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN, TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN, - TYPE_PARAMETER_WINDOWLESS, TYPE_PARAMETER_LEGACY_SPLASH_SCREEN }) public @interface StartingTypeParams {} @@ -158,12 +151,6 @@ public final class StartingWindowInfo implements Parcelable { * @hide */ public static final int TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN = 0x00000080; - - /** - * Windowless surface - */ - public static final int TYPE_PARAMETER_WINDOWLESS = 0x00000100; - /** * Application is allowed to use the legacy splash screen * @hide @@ -195,33 +182,7 @@ public final class StartingWindowInfo implements Parcelable { */ public TaskSnapshot taskSnapshot; - @InsetsType public int requestedVisibleTypes = WindowInsets.Type.defaultVisible(); - - /** - * App token where the starting window should add to. - */ - public IBinder appToken; - - public IWindowlessStartingSurfaceCallback windowlessStartingSurfaceCallback; - - /** - * The root surface where windowless surface should attach on. - */ - public SurfaceControl rootSurface; - - /** - * Notify windowless surface is created. - * @param addedSurface Created surface. - */ - public void notifyAddComplete(SurfaceControl addedSurface) { - if (windowlessStartingSurfaceCallback != null) { - try { - windowlessStartingSurfaceCallback.onSurfaceAdded(addedSurface); - } catch (RemoteException e) { - // - } - } - } + public @InsetsType int requestedVisibleTypes = WindowInsets.Type.defaultVisible(); public StartingWindowInfo() { @@ -255,9 +216,6 @@ public final class StartingWindowInfo implements Parcelable { dest.writeBoolean(isKeyguardOccluded); dest.writeTypedObject(taskSnapshot, flags); dest.writeInt(requestedVisibleTypes); - dest.writeStrongBinder(appToken); - dest.writeStrongInterface(windowlessStartingSurfaceCallback); - dest.writeTypedObject(rootSurface, flags); } void readFromParcel(@NonNull Parcel source) { @@ -272,10 +230,6 @@ public final class StartingWindowInfo implements Parcelable { isKeyguardOccluded = source.readBoolean(); taskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR); requestedVisibleTypes = source.readInt(); - appToken = source.readStrongBinder(); - windowlessStartingSurfaceCallback = IWindowlessStartingSurfaceCallback.Stub - .asInterface(source.readStrongBinder()); - rootSurface = source.readTypedObject(SurfaceControl.CREATOR); } @Override diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java index 518123600b9a..384dacfe89ed 100644 --- a/core/java/android/window/StartingWindowRemovalInfo.java +++ b/core/java/android/window/StartingWindowRemovalInfo.java @@ -67,16 +67,6 @@ public final class StartingWindowRemovalInfo implements Parcelable { */ public float roundedCornerRadius; - /** - * Remove windowless surface. - */ - public boolean windowlessSurface; - - /** - * Remove immediately. - */ - public boolean removeImmediately; - public StartingWindowRemovalInfo() { } @@ -97,8 +87,6 @@ public final class StartingWindowRemovalInfo implements Parcelable { playRevealAnimation = source.readBoolean(); deferRemoveForIme = source.readBoolean(); roundedCornerRadius = source.readFloat(); - windowlessSurface = source.readBoolean(); - removeImmediately = source.readBoolean(); } @Override @@ -109,8 +97,6 @@ public final class StartingWindowRemovalInfo implements Parcelable { dest.writeBoolean(playRevealAnimation); dest.writeBoolean(deferRemoveForIme); dest.writeFloat(roundedCornerRadius); - dest.writeBoolean(windowlessSurface); - dest.writeBoolean(removeImmediately); } @Override @@ -119,9 +105,7 @@ public final class StartingWindowRemovalInfo implements Parcelable { + " frame=" + mainFrame + " playRevealAnimation=" + playRevealAnimation + " roundedCornerRadius=" + roundedCornerRadius - + " deferRemoveForIme=" + deferRemoveForIme - + " windowlessSurface=" + windowlessSurface - + " removeImmediately=" + removeImmediately + "}"; + + " deferRemoveForIme=" + deferRemoveForIme + "}"; } public static final @android.annotation.NonNull Creator<StartingWindowRemovalInfo> CREATOR = diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index d4728c1187d7..02878f8ae72b 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -92,10 +92,13 @@ public class TaskOrganizer extends WindowOrganizer { * has create a starting window for the Task. * * @param info The information about the Task that's available + * @param appToken Token of the application being started. + * context to for resources * @hide */ @BinderThread - public void addStartingWindow(@NonNull StartingWindowInfo info) {} + public void addStartingWindow(@NonNull StartingWindowInfo info, + @NonNull IBinder appToken) {} /** * Called when the Task want to remove the starting window. @@ -294,8 +297,9 @@ public class TaskOrganizer extends WindowOrganizer { private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() { @Override - public void addStartingWindow(StartingWindowInfo windowInfo) { - mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo)); + public void addStartingWindow(StartingWindowInfo windowInfo, + IBinder appToken) { + mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo, appToken)); } @Override diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index a8c2b2f28df4..8066f5085a01 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -357,6 +357,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mImeDispatcher = imeDispatcher; } + /** Returns true if a non-null {@link ImeOnBackInvokedDispatcher} has been set. **/ + public boolean hasImeOnBackInvokedDispatcher() { + return mImeDispatcher != null; + } + /** * Class used to check whether a callback can be registered or not. This is meant to be * shared with {@link ProxyOnBackInvokedDispatcher} which needs to do the same checks. diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 7c237e69256f..ace8451722bb 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; +import static android.content.ContentProvider.getUserIdFromUri; import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; @@ -161,6 +162,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; +import java.util.stream.Collectors; /** * The Chooser Activity handles intent resolution specifically for sharing intents - @@ -1397,7 +1399,7 @@ public class ChooserActivity extends ResolverActivity implements ImageView previewThumbnailView = contentPreviewLayout.findViewById( R.id.content_preview_thumbnail); - if (previewThumbnail == null) { + if (!validForContentPreview(previewThumbnail)) { previewThumbnailView.setVisibility(View.GONE); } else { mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false); @@ -1427,6 +1429,10 @@ public class ChooserActivity extends ResolverActivity implements String action = targetIntent.getAction(); if (Intent.ACTION_SEND.equals(action)) { Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM, android.net.Uri.class); + if (!validForContentPreview(uri)) { + imagePreview.setVisibility(View.GONE); + return contentPreviewLayout; + } imagePreview.findViewById(R.id.content_preview_image_1_large) .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0); @@ -1436,7 +1442,7 @@ public class ChooserActivity extends ResolverActivity implements List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, android.net.Uri.class); List<Uri> imageUris = new ArrayList<>(); for (Uri uri : uris) { - if (isImageType(resolver.getType(uri))) { + if (validForContentPreview(uri) && isImageType(resolver.getType(uri))) { imageUris.add(uri); } } @@ -1546,9 +1552,16 @@ public class ChooserActivity extends ResolverActivity implements String action = targetIntent.getAction(); if (Intent.ACTION_SEND.equals(action)) { Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM, android.net.Uri.class); + if (!validForContentPreview(uri)) { + contentPreviewLayout.setVisibility(View.GONE); + return contentPreviewLayout; + } loadFileUriIntoView(uri, contentPreviewLayout); } else { List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, android.net.Uri.class); + uris = uris.stream() + .filter(ChooserActivity::validForContentPreview) + .collect(Collectors.toList()); int uriCount = uris.size(); if (uriCount == 0) { @@ -1607,6 +1620,24 @@ public class ChooserActivity extends ResolverActivity implements } } + /** + * Indicate if the incoming content URI should be allowed. + * + * @param uri the uri to test + * @return true if the URI is allowed for content preview + */ + private static boolean validForContentPreview(Uri uri) throws SecurityException { + if (uri == null) { + return false; + } + int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT); + if (userId != UserHandle.USER_CURRENT && userId != UserHandle.myUserId()) { + Log.e(TAG, "dropped invalid content URI belonging to user " + userId); + return false; + } + return true; + } + @VisibleForTesting protected boolean isImageType(String mimeType) { return mimeType != null && mimeType.startsWith("image/"); diff --git a/core/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl index b062ca7df921..c7418ee36fff 100644 --- a/core/java/com/android/internal/view/IImeTracker.aidl +++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl @@ -14,43 +14,45 @@ * limitations under the License. */ -package com.android.internal.view; +package com.android.internal.inputmethod; import android.view.inputmethod.ImeTracker; /** - * Interface to the global Ime tracker, used by all client applications. + * Interface to the global IME tracker service, used by all client applications. * {@hide} */ interface IImeTracker { /** - * Called when an IME show request is created, - * returns a new Binder to be associated with the IME tracking token. + * Called when an IME show request is created. * + * @param tag the logging tag. * @param uid the uid of the client that requested the IME. * @param origin the origin of the IME show request. * @param reason the reason why the IME show request was created. + * @return A new IME tracking token. */ - IBinder onRequestShow(int uid, int origin, int reason); + ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason); /** - * Called when an IME hide request is created, - * returns a new Binder to be associated with the IME tracking token. + * Called when an IME hide request is created. * + * @param tag the logging tag. * @param uid the uid of the client that requested the IME. * @param origin the origin of the IME hide request. * @param reason the reason why the IME hide request was created. + * @return A new IME tracking token. */ - IBinder onRequestHide(int uid, int origin, int reason); + ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason); /** * Called when the IME request progresses to a further phase. * - * @param statsToken the token tracking the current IME request. + * @param binder the binder of token tracking the current IME request. * @param phase the new phase the IME request reached. */ - oneway void onProgress(in IBinder statsToken, int phase); + oneway void onProgress(in IBinder binder, int phase); /** * Called when the IME request fails. @@ -58,7 +60,7 @@ interface IImeTracker { * @param statsToken the token tracking the current IME request. * @param phase the phase the IME request failed at. */ - oneway void onFailed(in IBinder statsToken, int phase); + oneway void onFailed(in ImeTracker.Token statsToken, int phase); /** * Called when the IME request is cancelled. @@ -66,21 +68,21 @@ interface IImeTracker { * @param statsToken the token tracking the current IME request. * @param phase the phase the IME request was cancelled at. */ - oneway void onCancelled(in IBinder statsToken, int phase); + oneway void onCancelled(in ImeTracker.Token statsToken, int phase); /** * Called when the IME show request is successful. * * @param statsToken the token tracking the current IME request. */ - oneway void onShown(in IBinder statsToken); + oneway void onShown(in ImeTracker.Token statsToken); /** * Called when the IME hide request is successful. * * @param statsToken the token tracking the current IME request. */ - oneway void onHidden(in IBinder statsToken); + oneway void onHidden(in ImeTracker.Token statsToken); /** * Checks whether there are any pending IME visibility requests. diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java index 506902f61fd6..426cd74074eb 100644 --- a/core/java/com/android/internal/midi/EventScheduler.java +++ b/core/java/com/android/internal/midi/EventScheduler.java @@ -16,7 +16,6 @@ package com.android.internal.midi; -import java.util.Iterator; import java.util.SortedMap; import java.util.TreeMap; @@ -26,11 +25,11 @@ import java.util.TreeMap; * And only one Thread can read from the buffer. */ public class EventScheduler { - private static final long NANOS_PER_MILLI = 1000000; + public static final long NANOS_PER_MILLI = 1000000; private final Object mLock = new Object(); - volatile private SortedMap<Long, FastEventQueue> mEventBuffer; - private FastEventQueue mEventPool = null; + protected volatile SortedMap<Long, FastEventQueue> mEventBuffer; + protected FastEventQueue mEventPool = null; private int mMaxPoolSize = 200; private boolean mClosed; @@ -38,9 +37,13 @@ public class EventScheduler { mEventBuffer = new TreeMap<Long, FastEventQueue>(); } - // If we keep at least one node in the list then it can be atomic - // and non-blocking. - private class FastEventQueue { + /** + * Class for a fast event queue. + * + * If we keep at least one node in the list then it can be atomic + * and non-blocking. + */ + public static class FastEventQueue { // One thread takes from the beginning of the list. volatile SchedulableEvent mFirst; // A second thread returns events to the end of the list. @@ -48,7 +51,7 @@ public class EventScheduler { volatile long mEventsAdded; volatile long mEventsRemoved; - FastEventQueue(SchedulableEvent event) { + public FastEventQueue(SchedulableEvent event) { mFirst = event; mLast = mFirst; mEventsAdded = 1; @@ -149,7 +152,8 @@ public class EventScheduler { * @param event */ public void add(SchedulableEvent event) { - synchronized (mLock) { + Object lock = getLock(); + synchronized (lock) { FastEventQueue list = mEventBuffer.get(event.getTimestamp()); if (list == null) { long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE @@ -159,7 +163,7 @@ public class EventScheduler { // If the event we added is earlier than the previous earliest // event then notify any threads waiting for the next event. if (event.getTimestamp() < lowestTime) { - mLock.notify(); + lock.notify(); } } else { list.add(event); @@ -167,7 +171,7 @@ public class EventScheduler { } } - private SchedulableEvent removeNextEventLocked(long lowestTime) { + protected SchedulableEvent removeNextEventLocked(long lowestTime) { SchedulableEvent event; FastEventQueue list = mEventBuffer.get(lowestTime); // Remove list from tree if this is the last node. @@ -186,7 +190,8 @@ public class EventScheduler { */ public SchedulableEvent getNextEvent(long time) { SchedulableEvent event = null; - synchronized (mLock) { + Object lock = getLock(); + synchronized (lock) { if (!mEventBuffer.isEmpty()) { long lowestTime = mEventBuffer.firstKey(); // Is it time for this list to be processed? @@ -209,7 +214,8 @@ public class EventScheduler { */ public SchedulableEvent waitNextEvent() throws InterruptedException { SchedulableEvent event = null; - synchronized (mLock) { + Object lock = getLock(); + synchronized (lock) { while (!mClosed) { long millisToWait = Integer.MAX_VALUE; if (!mEventBuffer.isEmpty()) { @@ -231,7 +237,7 @@ public class EventScheduler { } } } - mLock.wait((int) millisToWait); + lock.wait((int) millisToWait); } } return event; @@ -242,10 +248,25 @@ public class EventScheduler { mEventBuffer = new TreeMap<Long, FastEventQueue>(); } + /** + * Stops the EventScheduler. + * The subscriber calling waitNextEvent() will get one final SchedulableEvent returning null. + */ public void close() { - synchronized (mLock) { + Object lock = getLock(); + synchronized (lock) { mClosed = true; - mLock.notify(); + lock.notify(); } } + + /** + * Gets the lock. This doesn't lock it in anyway. + * Subclasses can override this. + * + * @return Object + */ + protected Object getLock() { + return mLock; + } } diff --git a/core/java/com/android/internal/midi/MidiEventMultiScheduler.java b/core/java/com/android/internal/midi/MidiEventMultiScheduler.java new file mode 100644 index 000000000000..16e4abecebdd --- /dev/null +++ b/core/java/com/android/internal/midi/MidiEventMultiScheduler.java @@ -0,0 +1,128 @@ +/* + * 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.internal.midi; + +/** + * Uses multiple MidiEventSchedulers for waiting for events. + * + */ +public class MidiEventMultiScheduler { + private MultiLockMidiEventScheduler[] mMidiEventSchedulers; + private int mNumEventSchedulers; + private int mNumClosedSchedulers = 0; + private final Object mMultiLock = new Object(); + + private class MultiLockMidiEventScheduler extends MidiEventScheduler { + @Override + public void close() { + synchronized (mMultiLock) { + mNumClosedSchedulers++; + } + super.close(); + } + + @Override + protected Object getLock() { + return mMultiLock; + } + + public boolean isEventBufferEmptyLocked() { + return mEventBuffer.isEmpty(); + } + + public long getLowestTimeLocked() { + return mEventBuffer.firstKey(); + } + } + + /** + * MidiEventMultiScheduler constructor + * + * @param numSchedulers the number of schedulers to create + */ + public MidiEventMultiScheduler(int numSchedulers) { + mNumEventSchedulers = numSchedulers; + mMidiEventSchedulers = new MultiLockMidiEventScheduler[numSchedulers]; + for (int i = 0; i < numSchedulers; i++) { + mMidiEventSchedulers[i] = new MultiLockMidiEventScheduler(); + } + } + + /** + * Waits for the next MIDI event. This will return true when it receives it. + * If all MidiEventSchedulers have been closed, this will return false. + * + * @return true if a MIDI event is received and false if all schedulers are closed. + */ + public boolean waitNextEvent() throws InterruptedException { + synchronized (mMultiLock) { + while (true) { + if (mNumClosedSchedulers >= mNumEventSchedulers) { + return false; + } + long lowestTime = Long.MAX_VALUE; + long now = System.nanoTime(); + for (MultiLockMidiEventScheduler eventScheduler : mMidiEventSchedulers) { + if (!eventScheduler.isEventBufferEmptyLocked()) { + lowestTime = Math.min(lowestTime, + eventScheduler.getLowestTimeLocked()); + } + } + if (lowestTime <= now) { + return true; + } + long nanosToWait = lowestTime - now; + // Add 1 millisecond so we don't wake up before it is + // ready. + long millisToWait = 1 + (nanosToWait / EventScheduler.NANOS_PER_MILLI); + // Clip 64-bit value to 32-bit max. + if (millisToWait > Integer.MAX_VALUE) { + millisToWait = Integer.MAX_VALUE; + } + mMultiLock.wait(millisToWait); + } + } + } + + /** + * Gets the number of MidiEventSchedulers. + * + * @return the number of MidiEventSchedulers. + */ + public int getNumEventSchedulers() { + return mNumEventSchedulers; + } + + /** + * Gets a specific MidiEventScheduler based on the index. + * + * @param index the zero indexed index of a MIDI event scheduler + * @return a MidiEventScheduler + */ + public MidiEventScheduler getEventScheduler(int index) { + return mMidiEventSchedulers[index]; + } + + /** + * Closes all event schedulers. + */ + public void close() { + for (MidiEventScheduler eventScheduler : mMidiEventSchedulers) { + eventScheduler.close(); + } + } +} diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java index 7b019044110f..1b2934d519fd 100644 --- a/core/java/com/android/internal/midi/MidiEventScheduler.java +++ b/core/java/com/android/internal/midi/MidiEventScheduler.java @@ -79,7 +79,7 @@ public class MidiEventScheduler extends EventScheduler { /** * Create an event that contains the message. */ - private MidiEvent createScheduledEvent(byte[] msg, int offset, int count, + public MidiEvent createScheduledEvent(byte[] msg, int offset, int count, long timestamp) { MidiEvent event; if (count > POOL_EVENT_SIZE) { diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 8a9445d8554a..28b98d6fab06 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -16,14 +16,10 @@ package com.android.internal.os; -import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL; - -import android.annotation.TestApi; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.ApplicationErrorReport; import android.app.IActivityManager; -import android.app.compat.CompatChanges; import android.compat.annotation.UnsupportedAppUsage; import android.content.type.DefaultMimeMapFactory; import android.net.TrafficStats; @@ -40,7 +36,6 @@ import com.android.internal.logging.AndroidConfig; import dalvik.system.RuntimeHooks; import dalvik.system.VMRuntime; -import dalvik.system.ZipPathValidator; import libcore.content.type.MimeMap; @@ -265,31 +260,10 @@ public class RuntimeInit { */ TrafficStats.attachSocketTagger(); - /* - * Initialize the zip path validator callback depending on the targetSdk. - */ - initZipPathValidatorCallback(); - initialized = true; } /** - * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip - * entry names. - * Otherwise: clear the callback to the default validation. - * - * @hide - */ - @TestApi - public static void initZipPathValidatorCallback() { - if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) { - ZipPathValidator.setCallback(new SafeZipPathValidatorCallback()); - } else { - ZipPathValidator.clearCallback(); - } - } - - /** * Returns an HTTP user agent of the form * "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MAIN)". */ diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 5805d0e050d1..9a4610e8c0a1 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -23,11 +23,11 @@ import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.EditorInfo; import android.window.ImeOnBackInvokedDispatcher; +import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputBindResult; -import com.android.internal.view.IImeTracker; /** * Public interface to the global input method manager, used by all client diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index 939a0e411913..9c6a534c3bbb 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -266,7 +266,8 @@ static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong s } static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, - jlong size, jint channelType, jint fd, jobject hardwareBufferObj) { + jint deviceId, jlong size, jint channelType, jint fd, + jobject hardwareBufferObj) { const native_handle_t *nativeHandle = nullptr; NATIVE_HANDLE_DECLARE_STORAGE(ashmemHandle, 1, 0); @@ -287,7 +288,7 @@ static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorMa } SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); - return mgr->createDirectChannel(size, channelType, nativeHandle); + return mgr->createDirectChannel(deviceId, size, channelType, nativeHandle); } static void nativeDestroyDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager, @@ -532,7 +533,7 @@ static const JNINativeMethod gSystemSensorManagerMethods[] = { {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled}, - {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I", + {"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I", (void *)nativeCreateDirectChannel}, {"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel}, diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 12eff6723d88..209c78551947 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1304,6 +1304,13 @@ <!-- Default LED off time for notification LED in milliseconds. --> <integer name="config_defaultNotificationLedOff">2000</integer> + <!-- LED behavior when battery is low. + Color for solid is taken from config_notificationsBatteryLowARGB + 0 - default, solid when charging, flashing when not charging + 1 - always solid when battery is low + 2 - always flashing when battery is low --> + <integer name="config_notificationsBatteryLowBehavior">0</integer> + <!-- Default value for led color when battery is low on charge --> <integer name="config_notificationsBatteryLowARGB">0xFFFF0000</integer> @@ -3065,6 +3072,10 @@ <string name="config_credentialManagerDialogComponent" translatable="false" >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string> + <!-- Name of the broadcast receiver that is used to receive provider change events --> + <string name="config_credentialManagerReceiverComponent" translatable="false" + >com.android.credentialmanager/com.android.credentialmanager.CredentialProviderReceiver</string> + <!-- Apps that are authorized to access shared accounts, overridden by product overlays --> <string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7e89fc8b5e46..8d56e7a163a7 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2004,6 +2004,7 @@ <java-symbol type="integer" name="config_notificationsBatteryFullARGB" /> <java-symbol type="integer" name="config_notificationsBatteryLedOff" /> <java-symbol type="integer" name="config_notificationsBatteryLedOn" /> + <java-symbol type="integer" name="config_notificationsBatteryLowBehavior" /> <java-symbol type="integer" name="config_notificationsBatteryLowARGB" /> <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" /> <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" /> @@ -2210,6 +2211,7 @@ <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" /> <java-symbol type="string" name="config_carrierAppInstallDialogComponent" /> <java-symbol type="string" name="config_credentialManagerDialogComponent" /> + <java-symbol type="string" name="config_credentialManagerReceiverComponent" /> <java-symbol type="string" name="config_defaultNetworkScorerPackageName" /> <java-symbol type="string" name="config_persistentDataPackageName" /> <java-symbol type="string" name="config_deviceConfiguratorPackageName" /> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index e164e08e66e6..4f91e7a3545a 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -16,7 +16,7 @@ package android.app.activity; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID; +import static android.content.Context.DEVICE_ID_INVALID; import static android.content.Intent.ACTION_EDIT; import static android.content.Intent.ACTION_VIEW; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java index f97099d04572..16ed3ef42da3 100644 --- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java +++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java @@ -17,9 +17,15 @@ package android.companion.virtual.sensor; import static android.hardware.Sensor.TYPE_ACCELEROMETER; +import static android.hardware.SensorDirectChannel.RATE_STOP; +import static android.hardware.SensorDirectChannel.RATE_VERY_FAST; +import static android.hardware.SensorDirectChannel.TYPE_HARDWARE_BUFFER; +import static android.hardware.SensorDirectChannel.TYPE_MEMORY_FILE; import static com.google.common.truth.Truth.assertThat; +import static org.testng.Assert.assertThrows; + import android.os.Parcel; import android.platform.test.annotations.Presubmit; @@ -40,6 +46,8 @@ public class VirtualSensorConfigTest { final VirtualSensorConfig originalConfig = new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME) .setVendor(SENSOR_VENDOR) + .setHighestDirectReportRateLevel(RATE_VERY_FAST) + .setDirectChannelTypesSupported(TYPE_MEMORY_FILE) .build(); final Parcel parcel = Parcel.obtain(); originalConfig.writeToParcel(parcel, /* flags= */ 0); @@ -49,6 +57,39 @@ public class VirtualSensorConfigTest { assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType()); assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName()); assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor()); + assertThat(recreatedConfig.getHighestDirectReportRateLevel()).isEqualTo(RATE_VERY_FAST); + assertThat(recreatedConfig.getDirectChannelTypesSupported()).isEqualTo(TYPE_MEMORY_FILE); + // From hardware/libhardware/include/hardware/sensors-base.h: + // 0x400 is SENSOR_FLAG_DIRECT_CHANNEL_ASHMEM (i.e. TYPE_MEMORY_FILE) + // 0x800 is SENSOR_FLAG_DIRECT_CHANNEL_GRALLOC (i.e. TYPE_HARDWARE_BUFFER) + // 7 is SENSOR_FLAG_SHIFT_DIRECT_REPORT + assertThat(recreatedConfig.getFlags()).isEqualTo(0x400 | RATE_VERY_FAST << 7); + } + + @Test + public void hardwareBufferDirectChannelTypeSupported_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME) + .setDirectChannelTypesSupported(TYPE_HARDWARE_BUFFER | TYPE_MEMORY_FILE)); + } + + @Test + public void directChannelTypeSupported_missingHighestReportRateLevel_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME) + .setDirectChannelTypesSupported(TYPE_MEMORY_FILE) + .build()); + } + + @Test + public void directChannelTypeSupported_missingDirectChannelTypeSupported_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME) + .setHighestDirectReportRateLevel(RATE_VERY_FAST) + .build()); } @Test @@ -56,5 +97,8 @@ public class VirtualSensorConfigTest { final VirtualSensorConfig config = new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build(); assertThat(config.getVendor()).isNull(); + assertThat(config.getHighestDirectReportRateLevel()).isEqualTo(RATE_STOP); + assertThat(config.getDirectChannelTypesSupported()).isEqualTo(0); + assertThat(config.getFlags()).isEqualTo(0); } } diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java index 324f81084f98..d4784374745d 100644 --- a/core/tests/coretests/src/android/content/ContextTest.java +++ b/core/tests/coretests/src/android/content/ContextTest.java @@ -16,7 +16,7 @@ package android.content; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; +import static android.content.Context.DEVICE_ID_DEFAULT; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.view.Display.DEFAULT_DISPLAY; diff --git a/core/tests/coretests/src/android/content/pm/UserPackageTest.java b/core/tests/coretests/src/android/content/pm/UserPackageTest.java new file mode 100644 index 000000000000..5114e2cf9327 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/UserPackageTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.platform.test.annotations.Presubmit; + +import junit.framework.TestCase; + +@Presubmit +public class UserPackageTest extends TestCase { + public void testCacheLimit() { + UserPackage.setValidUserIds(new int[]{0}); + for (int i = 0; i < UserPackage.MAX_NUM_CACHED_ENTRIES_PER_USER; ++i) { + UserPackage.of(0, "app" + i); + assertEquals(i + 1, UserPackage.numEntriesForUser(0)); + } + + for (int i = 0; i < UserPackage.MAX_NUM_CACHED_ENTRIES_PER_USER; ++i) { + UserPackage.of(0, "appOverLimit" + i); + final int numCached = UserPackage.numEntriesForUser(0); + assertTrue(numCached >= 1); + assertTrue(numCached <= UserPackage.MAX_NUM_CACHED_ENTRIES_PER_USER); + } + } +} diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt index ef106bc7cb73..ee1b2aa9a259 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt @@ -43,14 +43,56 @@ class FontScaleConverterFactoryTest { assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f) } - @SmallTest - fun missingLookupTableReturnsNull() { - assertThat(FontScaleConverterFactory.forScale(3F)).isNull() + @LargeTest + @Test + fun missingLookupTablePastEnd_returnsLinear() { + val table = FontScaleConverterFactory.forScale(3F)!! + generateSequenceOfFractions(-10000f..10000f, step = 0.01f) + .map { + assertThat(table.convertSpToDp(it)).isWithin(CONVERSION_TOLERANCE).of(it * 3f) + } + assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(3f) + assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(24f) + assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(30f) + assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(15f) + assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f) + assertThat(table.convertSpToDp(50F)).isWithin(CONVERSION_TOLERANCE).of(150f) + assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(300f) } @SmallTest - fun missingLookupTable105ReturnsNull() { - assertThat(FontScaleConverterFactory.forScale(1.05F)).isNull() + fun missingLookupTable110_returnsInterpolated() { + val table = FontScaleConverterFactory.forScale(1.1F)!! + + assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.1f) + assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.1f) + assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(11f) + assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.1f) + assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f) + assertThat(table.convertSpToDp(50F)).isLessThan(50f * 1.1f) + assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.1f) + } + + @Test + fun missingLookupTable199_returnsInterpolated() { + val table = FontScaleConverterFactory.forScale(1.9999F)!! + assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f) + assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f) + assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f) + assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f) + assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f) + } + + @Test + fun missingLookupTable160_returnsInterpolated() { + val table = FontScaleConverterFactory.forScale(1.6F)!! + assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1f * 1.6F) + assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f * 1.6F) + assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(10f * 1.6F) + assertThat(table.convertSpToDp(20F)).isLessThan(20f * 1.6F) + assertThat(table.convertSpToDp(100F)).isLessThan(100f * 1.6F) + assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f * 1.6F) + assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f) } @SmallTest @@ -83,7 +125,7 @@ class FontScaleConverterFactoryTest { @Test fun allFeasibleScalesAndConversionsDoNotCrash() { generateSequenceOfFractions(-10f..10f, step = 0.01f) - .mapNotNull{ FontScaleConverterFactory.forScale(it) } + .mapNotNull{ FontScaleConverterFactory.forScale(it) }!! .flatMap{ table -> generateSequenceOfFractions(-2000f..2000f, step = 0.01f) .map{ Pair(table, it) } diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java new file mode 100644 index 000000000000..42c97f3876a6 --- /dev/null +++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import static android.os.CancellationSignalBeamer.Sender; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.CancellationSignalBeamer.Receiver; +import android.util.PollingCheck; +import android.util.PollingCheck.PollingCheckCondition; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.Cleaner; +import java.lang.ref.Reference; +import java.util.concurrent.CountDownLatch; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class CancellationSignalBeamerTest { + + private CancellationSignal mSenderSignal = new CancellationSignal(); + private CancellationSignal mReceivedSignal; + private Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + + @Test + public void testBeam_null() { + try (var token = mSender.beam(null)) { + assertThat(token).isNull(); + invokeGenericService(token); + } + assertThat(mReceivedSignal).isNull(); + } + + @Test + public void testBeam_nonNull() { + try (var token = mSender.beam(mSenderSignal)) { + assertThat(token).isNotNull(); + invokeGenericService(token); + } + assertThat(mReceivedSignal).isNotNull(); + } + + @Test + public void testBeam_async() { + IBinder outerToken; + try (var token = mSender.beam(mSenderSignal)) { + assertThat(token).isNotNull(); + outerToken = token; + } + invokeGenericService(outerToken); + assertThat(mReceivedSignal).isNotNull(); + } + + @Test + public void testCancelOnSentSignal_cancelsReceivedSignal() { + try (var token = mSender.beam(mSenderSignal)) { + invokeGenericService(token); + } + mSenderSignal.cancel(); + assertThat(mReceivedSignal.isCanceled()).isTrue(); + } + + @Test + public void testSendingCancelledSignal_cancelsReceivedSignal() { + mSenderSignal.cancel(); + try (var token = mSender.beam(mSenderSignal)) { + invokeGenericService(token); + } + assertThat(mReceivedSignal.isCanceled()).isTrue(); + } + + @Test + public void testUnbeam_null() { + assertThat(mReceiver.unbeam(null)).isNull(); + } + + @Test + public void testForget_null() { + mReceiver.forget(null); + } + + @Test + public void testCancel_null() { + mReceiver.cancel(null); + } + + @Test + public void testForget_withUnknownToken() { + mReceiver.forget(new Binder()); + } + + @Test + public void testCancel_withUnknownToken() { + mReceiver.cancel(new Binder()); + } + + @Test + public void testBinderDied_withUnknownToken() { + mReceiver.binderDied(new Binder()); + } + + @Test + public void testReceiverWithCancelOnSenderDead_cancelsOnSenderDeath() { + var receiver = new Receiver(true /* cancelOnSenderDeath */); + var token = new Binder(); + var signal = receiver.unbeam(token); + receiver.binderDied(token); + assertThat(signal.isCanceled()).isTrue(); + } + + @Test + public void testReceiverWithoutCancelOnSenderDead_doesntCancelOnSenderDeath() { + var receiver = new Receiver(false /* cancelOnSenderDeath */); + var token = new Binder(); + var signal = receiver.unbeam(token); + receiver.binderDied(token); + assertThat(signal.isCanceled()).isFalse(); + } + + @Test + public void testDroppingSentSignal_dropsReceivedSignal() throws Exception { + // In a multiprocess scenario, sending token over Binder might leak the token + // on both ends if we create a reference cycle. Simulate that worst-case scenario + // here by leaking it directly, then test that cleanup of the signals still works. + var receivedSignalCleaned = new CountDownLatch(1); + var tokenRef = new Object[1]; + // Reference the cancellation signals in a separate method scope, so we don't + // accidentally leak them on the stack / in a register. + Runnable r = () -> { + try (var token = mSender.beam(mSenderSignal)) { + tokenRef[0] = token; + invokeGenericService(token); + } + mSenderSignal = null; + + Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown); + mReceivedSignal = null; + }; + r.run(); + + waitForWithGc(() -> receivedSignalCleaned.getCount() == 0); + + Reference.reachabilityFence(tokenRef[0]); + } + + @Test + public void testRepeatedBeaming_doesntLeak() throws Exception { + var receivedSignalCleaned = new CountDownLatch(1); + var tokenRef = new Object[1]; + // Reference the cancellation signals in a separate method scope, so we don't + // accidentally leak them on the stack / in a register. + Runnable r = () -> { + try (var token = mSender.beam(mSenderSignal)) { + tokenRef[0] = token; + invokeGenericService(token); + } + // Beaming again leaves mReceivedSignal dangling, so it should be collected. + mSender.beam(mSenderSignal).close(); + + Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown); + mReceivedSignal = null; + }; + r.run(); + + waitForWithGc(() -> receivedSignalCleaned.getCount() == 0); + + Reference.reachabilityFence(tokenRef[0]); + } + + private void waitForWithGc(PollingCheckCondition condition) throws IOException { + try { + PollingCheck.waitFor(() -> { + Runtime.getRuntime().gc(); + return condition.canProceed(); + }); + } catch (AssertionError e) { + File heap = new File(mContext.getExternalFilesDir(null), "dump.hprof"); + Debug.dumpHprofData(heap.getAbsolutePath()); + throw e; + } + } + + private void invokeGenericService(IBinder cancellationSignalToken) { + mReceivedSignal = mReceiver.unbeam(cancellationSignalToken); + } + + private final Sender mSender = new Sender() { + @Override + public void onCancel(IBinder token) { + mReceiver.cancel(token); + } + + @Override + public void onForget(IBinder token) { + mReceiver.forget(token); + } + }; + + private final Receiver mReceiver = new Receiver(false); +} diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index ca1367a710ec..06920524acfc 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -248,17 +248,22 @@ public class InsetsControllerTest { @Test public void testSystemDrivenInsetsAnimationLoggingListener_onReady() { + var loggingListener = mock(WindowInsetsAnimationControlListener.class); + prepareControls(); // only the original thread that created view hierarchy can touch its views InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - WindowInsetsAnimationControlListener loggingListener = - mock(WindowInsetsAnimationControlListener.class); mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener); mController.getSourceConsumer(mImeSource).onWindowFocusGained(true); // since there is no focused view, forcefully make IME visible. mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); - verify(loggingListener).onReady(notNull(), anyInt()); + // When using the animation thread, this must not invoke onReady() + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); }); + // Wait for onReady() being dispatched on the animation thread. + InsetsAnimationThread.get().getThreadHandler().runWithScissors(() -> {}, 500); + + verify(loggingListener).onReady(notNull(), anyInt()); } @Test diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java index 1db6587e1283..6fa8f1117343 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java @@ -19,6 +19,7 @@ package android.view; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.LAST; import static android.view.WindowInsets.Type.SIZE; +import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.navigationBars; @@ -51,11 +52,13 @@ public class InsetsSourceTest { private final InsetsSource mSource = new InsetsSource(0 /* id */, navigationBars()); private final InsetsSource mImeSource = new InsetsSource(1 /* id */, ime()); + private final InsetsSource mCaptionSource = new InsetsSource(2 /* id */, captionBar()); @Before public void setUp() { mSource.setVisible(true); mImeSource.setVisible(true); + mCaptionSource.setVisible(true); } @Test @@ -107,6 +110,17 @@ public class InsetsSourceTest { } @Test + public void testCalculateInsets_caption_resizing() { + mCaptionSource.setFrame(new Rect(0, 0, 100, 100)); + Insets insets = mCaptionSource.calculateInsets(new Rect(0, 0, 200, 200), false); + assertEquals(Insets.of(0, 100, 0, 0), insets); + insets = mCaptionSource.calculateInsets(new Rect(0, 0, 50, 200), false); + assertEquals(Insets.of(0, 100, 0, 0), insets); + insets = mCaptionSource.calculateInsets(new Rect(100, 100, 200, 500), false); + assertEquals(Insets.of(0, 100, 0, 0), insets); + } + + @Test public void testCalculateInsets_invisible() { mSource.setFrame(new Rect(0, 0, 500, 100)); mSource.setVisible(false); diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index b035c23cb98b..fde1a6d7b04c 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -248,6 +248,18 @@ public class InsetsStateTest { } @Test + public void testCalculateInsets_captionBarOffset() { + mState.getOrCreateSource(ID_CAPTION_BAR, captionBar()) + .setFrame(new Rect(0, 0, 100, 300)) + .setVisible(true); + + Insets visibleInsets = mState.calculateVisibleInsets( + new Rect(0, 0, 150, 400), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, + SOFT_INPUT_ADJUST_NOTHING, 0 /* windowFlags */); + assertEquals(Insets.of(0, 300, 0, 0), visibleInsets); + } + + @Test public void testCalculateInsets_extraNavRightStatusTop() { mState.getOrCreateSource(ID_STATUS_BAR, statusBars()) .setFrame(new Rect(0, 0, 100, 100)) diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java index 0c7550e79cfa..777246b83a57 100644 --- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java +++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java @@ -33,6 +33,8 @@ import android.app.Activity; import android.app.PendingIntent; import android.app.RemoteAction; import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; import android.view.ContextMenu; import android.view.MenuItem; @@ -167,4 +169,75 @@ public class TextViewContextMenuTest { assertThat(idCaptor.getValue()).isEqualTo(TextView.ID_ASSIST); assertThat(titleCaptor.getValue().toString()).isEqualTo(ACTION_TITLE); } + + @UiThreadTest + @Test + public void testAdjustIconSpaces() { + GradientDrawable gd = new GradientDrawable(); + gd.setSize(128, 256); + + // Setup mocks + ContextMenu menu = mock(ContextMenu.class); + + MenuItem mockIconMenu = newMockMenuItem(); + when(mockIconMenu.getIcon()).thenReturn(gd); + + MenuItem mockNoIconMenu = newMockMenuItem(); + when(mockNoIconMenu.getIcon()).thenReturn(null); + + MenuItem mockNoIconMenu2 = newMockMenuItem(); + when(mockNoIconMenu2.getIcon()).thenReturn(null); + + when(menu.size()).thenReturn(3); + when(menu.getItem(0)).thenReturn(mockIconMenu); + when(menu.getItem(1)).thenReturn(mockNoIconMenu); + when(menu.getItem(2)).thenReturn(mockNoIconMenu2); + + + // Execute the test method + EditText et = mActivity.findViewById(R.id.editText); + Editor editor = et.getEditorForTesting(); + editor.adjustIconSpacing(menu); + + // Verify + ArgumentCaptor<Drawable> drawableCaptor = ArgumentCaptor.forClass(Drawable.class); + verify(mockNoIconMenu).setIcon(drawableCaptor.capture()); + + Drawable paddingDrawable = drawableCaptor.getValue(); + assertThat(paddingDrawable).isNotNull(); + assertThat(paddingDrawable.getIntrinsicWidth()).isEqualTo(128); + assertThat(paddingDrawable.getIntrinsicHeight()).isEqualTo(256); + + ArgumentCaptor<Drawable> drawableCaptor2 = ArgumentCaptor.forClass(Drawable.class); + verify(mockNoIconMenu2).setIcon(drawableCaptor2.capture()); + + Drawable paddingDrawable2 = drawableCaptor2.getValue(); + assertThat(paddingDrawable2).isSameInstanceAs(paddingDrawable); + } + + @UiThreadTest + @Test + public void testAdjustIconSpacesNoIconCase() { + // Setup mocks + ContextMenu menu = mock(ContextMenu.class); + + MenuItem mockNoIconMenu = newMockMenuItem(); + when(mockNoIconMenu.getIcon()).thenReturn(null); + + MenuItem mockNoIconMenu2 = newMockMenuItem(); + when(mockNoIconMenu2.getIcon()).thenReturn(null); + + when(menu.size()).thenReturn(2); + when(menu.getItem(0)).thenReturn(mockNoIconMenu); + when(menu.getItem(1)).thenReturn(mockNoIconMenu2); + + // Execute the test method + EditText et = mActivity.findViewById(R.id.editText); + Editor editor = et.getEditorForTesting(); + editor.adjustIconSpacing(menu); + + // Verify + verify(mockNoIconMenu, times(0)).setIcon(any()); + verify(mockNoIconMenu2, times(0)).setIcon(any()); + } } diff --git a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java deleted file mode 100644 index c540a150bf35..000000000000 --- a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import static org.junit.Assert.assertThrows; - -import android.compat.testing.PlatformCompatChangeRule; - -import androidx.test.runner.AndroidJUnit4; - -import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; -import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.junit.runner.RunWith; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -/** - * Test SafeZipPathCallback. - */ -@RunWith(AndroidJUnit4.class) -public class SafeZipPathValidatorCallbackTest { - @Rule - public TestRule mCompatChangeRule = new PlatformCompatChangeRule(); - - @Before - public void setUp() { - RuntimeInit.initZipPathValidatorCallback(); - } - - @Test - @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL}) - public void testNewZipFile_whenZipFileHasDangerousEntriesAndChangeEnabled_throws() - throws Exception { - final String[] dangerousEntryNames = { - "../foo.bar", - "foo/../bar.baz", - "foo/../../bar.baz", - "foo.bar/..", - "foo.bar/../", - "..", - "../", - "/foo", - }; - for (String entryName : dangerousEntryNames) { - final File tempFile = File.createTempFile("smdc", "zip"); - try { - writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName); - - assertThrows( - "ZipException expected for entry: " + entryName, - ZipException.class, - () -> { - new ZipFile(tempFile); - }); - } finally { - tempFile.delete(); - } - } - } - - @Test - @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL}) - public void - testZipInputStreamGetNextEntry_whenZipFileHasDangerousEntriesAndChangeEnabled_throws() - throws Exception { - final String[] dangerousEntryNames = { - "../foo.bar", - "foo/../bar.baz", - "foo/../../bar.baz", - "foo.bar/..", - "foo.bar/../", - "..", - "../", - "/foo", - }; - for (String entryName : dangerousEntryNames) { - byte[] badZipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName); - try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(badZipBytes))) { - assertThrows( - "ZipException expected for entry: " + entryName, - ZipException.class, - () -> { - zis.getNextEntry(); - }); - } - } - } - - @Test - @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL}) - public void testNewZipFile_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow() - throws Exception { - final String[] normalEntryNames = { - "foo", "foo.bar", "foo..bar", - }; - for (String entryName : normalEntryNames) { - final File tempFile = File.createTempFile("smdc", "zip"); - try { - writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName); - try { - new ZipFile((tempFile)); - } catch (ZipException e) { - throw new AssertionError("ZipException not expected for entry: " + entryName); - } - } finally { - tempFile.delete(); - } - } - } - - @Test - @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL}) - public void - testZipInputStreamGetNextEntry_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow() - throws Exception { - final String[] normalEntryNames = { - "foo", "foo.bar", "foo..bar", - }; - for (String entryName : normalEntryNames) { - byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName); - try { - ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes)); - zis.getNextEntry(); - } catch (ZipException e) { - throw new AssertionError("ZipException not expected for entry: " + entryName); - } - } - } - - @Test - @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL}) - public void - testNewZipFile_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow() - throws Exception { - final String[] entryNames = { - "../foo.bar", - "foo/../bar.baz", - "foo/../../bar.baz", - "foo.bar/..", - "foo.bar/../", - "..", - "../", - "/foo", - "foo", - "foo.bar", - "foo..bar", - }; - for (String entryName : entryNames) { - final File tempFile = File.createTempFile("smdc", "zip"); - try { - writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName); - try { - new ZipFile((tempFile)); - } catch (ZipException e) { - throw new AssertionError("ZipException not expected for entry: " + entryName); - } - } finally { - tempFile.delete(); - } - } - } - - @Test - @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL}) - public void - testZipInputStreamGetNextEntry_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow() - throws Exception { - final String[] entryNames = { - "../foo.bar", - "foo/../bar.baz", - "foo/../../bar.baz", - "foo.bar/..", - "foo.bar/../", - "..", - "../", - "/foo", - "foo", - "foo.bar", - "foo..bar", - }; - for (String entryName : entryNames) { - byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName); - try { - ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes)); - zis.getNextEntry(); - } catch (ZipException e) { - throw new AssertionError("ZipException not expected for entry: " + entryName); - } - } - } - - private void writeZipFileOutputStreamWithEmptyEntry(File tempFile, String entryName) - throws IOException { - FileOutputStream tempFileStream = new FileOutputStream(tempFile); - writeZipOutputStreamWithEmptyEntry(tempFileStream, entryName); - tempFileStream.close(); - } - - private byte[] getZipBytesFromZipOutputStreamWithEmptyEntry(String entryName) - throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - writeZipOutputStreamWithEmptyEntry(bos, entryName); - return bos.toByteArray(); - } - - private void writeZipOutputStreamWithEmptyEntry(OutputStream os, String entryName) - throws IOException { - ZipOutputStream zos = new ZipOutputStream(os); - ZipEntry entry = new ZipEntry(entryName); - zos.putNextEntry(entry); - zos.write(new byte[2]); - zos.closeEntry(); - zos.close(); - } -} diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java index 9ccf3b3c9d05..3b8b8c797241 100644 --- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java +++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java @@ -117,6 +117,31 @@ public class DisplayTest { } @Test + public void testGetReportedHdrTypes_returns_mode_specific_hdr_types() { + setDisplayInfoPortrait(mDisplayInfo); + float[] alternativeRefreshRates = new float[0]; + int[] hdrTypesWithDv = new int[] {1, 2, 3, 4}; + Display.Mode modeWithDv = new Display.Mode(/* modeId= */ 0, 0, 0, 0f, + alternativeRefreshRates, hdrTypesWithDv); + + int[] hdrTypesWithoutDv = new int[]{2, 3, 4}; + Display.Mode modeWithoutDv = new Display.Mode(/* modeId= */ 1, 0, 0, 0f, + alternativeRefreshRates, hdrTypesWithoutDv); + + mDisplayInfo.supportedModes = new Display.Mode[] {modeWithoutDv, modeWithDv}; + mDisplayInfo.hdrCapabilities = new Display.HdrCapabilities(hdrTypesWithDv, 0, 0, 0); + + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + + mDisplayInfo.modeId = 0; + assertArrayEquals(hdrTypesWithDv, display.getReportedHdrTypes()); + + mDisplayInfo.modeId = 1; + assertArrayEquals(hdrTypesWithoutDv, display.getReportedHdrTypes()); + } + + @Test public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() { setDisplayInfoPortrait(mDisplayInfo); final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index f3318f400e21..5cb5ffa03e00 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -471,7 +471,7 @@ applications that come with the platform <permission name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED"/> <!-- Permission required for CTS test - CallAudioInterceptionTest --> <permission name="android.permission.CALL_AUDIO_INTERCEPTION"/> - <!-- Permission required for CTS test - CtsPermission5TestCases --> + <!-- Permission required for CTS test - CtsAttributionSourceTestCases --> <permission name="android.permission.RENOUNCE_PERMISSIONS" /> <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" /> <permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" /> diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index 6a1313e16ce5..66fabec91924 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -341,7 +341,6 @@ public class Mesh { * @hide so only calls from module can utilize it */ long getNativeWrapperInstance() { - nativeUpdateMesh(mNativeMeshWrapper, mIsIndexed); return mNativeMeshWrapper; } @@ -383,5 +382,4 @@ public class Mesh { private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values); - private static native void nativeUpdateMesh(long nativeMeshWrapper, boolean mIsIndexed); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index b6fd0bbafc71..585f81c81a36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -428,9 +428,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } @Override - public void addStartingWindow(StartingWindowInfo info) { + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { if (mStartingWindow != null) { - mStartingWindow.addStartingWindow(info); + mStartingWindow.addStartingWindow(info, appToken); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java index 04bff97bc4ce..306d6196c553 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java @@ -32,6 +32,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; @@ -115,7 +116,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { boolean latest) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; - if (Transitions.isClosingType(mPending.get(i).mType) == closing) { + if (TransitionUtil.isClosingType(mPending.get(i).mType) == closing) { return mPending.get(i); } if (latest) { @@ -148,7 +149,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { final TaskViewTaskController taskView = findTaskView(triggerTask); if (taskView == null) return null; // Opening types should all be initiated by shell - if (!Transitions.isClosingType(request.getType())) return null; + if (!TransitionUtil.isClosingType(request.getType())) return null; PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */); if (pending == null) { pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */); @@ -238,7 +239,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { for (int i = 0; i < info.getChanges().size(); ++i) { final TransitionInfo.Change chg = info.getChanges().get(i); if (chg.getTaskInfo() == null) continue; - if (Transitions.isClosingType(chg.getMode())) { + if (TransitionUtil.isClosingType(chg.getMode())) { final boolean isHide = chg.getMode() == TRANSIT_TO_BACK; TaskViewTaskController tv = findTaskView(chg.getTaskInfo()); if (tv == null) { @@ -255,7 +256,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { tv.prepareCloseAnimation(); } changesHandled++; - } else if (Transitions.isOpeningType(chg.getMode())) { + } else if (TransitionUtil.isOpeningType(chg.getMode())) { final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN; final TaskViewTaskController tv; if (taskIsNew) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java index 579f7aae9321..c767376d4f29 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java @@ -29,7 +29,7 @@ import android.window.TransitionInfo; import androidx.annotation.NonNull; -import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; /** * Wrapper to handle the ActivityEmbedding animation update in one @@ -90,7 +90,7 @@ class ActivityEmbeddingAnimationAdapter { mChange = change; mLeash = leash; mWholeAnimationBounds.set(wholeAnimationBounds); - if (Transitions.isClosingType(change.getMode())) { + if (TransitionUtil.isClosingType(change.getMode())) { // When it is closing, we want to show the content at the start position in case the // window is resizing as well. For example, when the activities is changing from split // to stack, the bottom TaskFragment will be resized to fullscreen when hiding. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index fe3c4ea3fee9..1df6ecda78c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -43,7 +43,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.common.ScreenshotUtils; -import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; import java.util.List; @@ -184,7 +184,7 @@ class ActivityEmbeddingAnimationRunner { if (isChangeTransition) { return createChangeAnimationAdapters(info, startTransaction); } - if (Transitions.isClosingType(info.getType())) { + if (TransitionUtil.isClosingType(info.getType())) { return createCloseAnimationAdapters(info); } return createOpenAnimationAdapters(info); @@ -219,7 +219,7 @@ class ActivityEmbeddingAnimationRunner { final Rect openingWholeScreenBounds = new Rect(); final Rect closingWholeScreenBounds = new Rect(); for (TransitionInfo.Change change : info.getChanges()) { - if (Transitions.isOpeningType(change.getMode())) { + if (TransitionUtil.isOpeningType(change.getMode())) { openingChanges.add(change); openingWholeScreenBounds.union(change.getEndAbsBounds()); } else { @@ -271,7 +271,7 @@ class ActivityEmbeddingAnimationRunner { continue; } final TransitionInfo.Change change = adapter.mChange; - if (Transitions.isOpeningType(adapter.mChange.getMode())) { + if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) { // Need to screenshot after startTransaction is applied otherwise activity // may not be visible or ready yet. postStartTransactionCallbacks.add( @@ -343,7 +343,7 @@ class ActivityEmbeddingAnimationRunner { // When the parent window is also included in the transition as an opening window, // we would like to animate the parent window instead. final TransitionInfo.Change parentChange = info.getChange(parentToken); - if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) { + if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) { // We won't create a separate animation for the parent, but to animate the // parent for the child resizing. handledChanges.add(parentChange); @@ -404,7 +404,7 @@ class ActivityEmbeddingAnimationRunner { // No-op if it will be covered by the changing parent window, or it is a changing // window without bounds change. animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); - } else if (Transitions.isClosingType(change.getMode())) { + } else if (TransitionUtil.isClosingType(change.getMode())) { animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds); shouldShouldBackgroundColor = false; } else { @@ -469,7 +469,7 @@ class ActivityEmbeddingAnimationRunner { // When the parent window is also included in the transition as an opening window, // we would like to animate the parent window instead. final TransitionInfo.Change parentChange = info.getChange(parentToken); - if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) { + if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) { changingChanges.add(parentChange); } } @@ -491,8 +491,8 @@ class ActivityEmbeddingAnimationRunner { // No-op if it will be covered by the changing parent window. continue; } - hasOpeningWindow |= Transitions.isOpeningType(change.getMode()); - hasClosingWindow |= Transitions.isClosingType(change.getMode()); + hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode()); + hasClosingWindow |= TransitionUtil.isClosingType(change.getMode()); } return hasOpeningWindow && hasClosingWindow; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index d10a6744b5f1..cb8342a10a6a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -36,7 +36,7 @@ import android.window.TransitionInfo; import androidx.annotation.NonNull; import com.android.internal.policy.TransitionAnimation; -import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; /** Animation spec for ActivityEmbedding transition. */ // TODO(b/206557124): provide an easier way to customize animation @@ -73,7 +73,7 @@ class ActivityEmbeddingAnimationSpec { @NonNull static Animation createNoopAnimation(@NonNull TransitionInfo.Change change) { // Noop but just keep the window showing/hiding. - final float alpha = Transitions.isClosingType(change.getMode()) ? 0f : 1f; + final float alpha = TransitionUtil.isClosingType(change.getMode()) ? 0f : 1f; return new AlphaAnimation(alpha, alpha); } @@ -198,7 +198,7 @@ class ActivityEmbeddingAnimationSpec { @NonNull Animation loadOpenAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { - final boolean isEnter = Transitions.isOpeningType(change.getMode()); + final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); final Animation animation; if (shouldShowBackdrop(info, change)) { animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter @@ -222,7 +222,7 @@ class ActivityEmbeddingAnimationSpec { @NonNull Animation loadCloseAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { - final boolean isEnter = Transitions.isOpeningType(change.getMode()); + final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); final Animation animation; if (shouldShowBackdrop(info, change)) { animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter 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 e5c0570841f4..6b0337d3fb4a 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 @@ -40,7 +40,6 @@ import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; -import static com.android.wm.shell.transition.Transitions.isOpeningType; import android.animation.Animator; import android.app.ActivityManager; @@ -72,6 +71,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.CounterRotatorHelper; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; import java.util.Optional; @@ -702,7 +702,7 @@ public class PipTransition extends PipTransitionController { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change == enterPip) continue; - if (isOpeningType(change.getMode())) { + if (TransitionUtil.isOpeningType(change.getMode())) { final SurfaceControl leash = change.getLeash(); startTransaction.show(leash).setAlpha(leash, 1.f); } @@ -873,7 +873,7 @@ public class PipTransition extends PipTransitionController { continue; } - if (isOpeningType(mode) && change.getParent() == null) { + if (TransitionUtil.isOpeningType(mode) && change.getParent() == null) { final SurfaceControl leash = change.getLeash(); final Rect endBounds = change.getEndAbsBounds(); startTransaction diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 520da92fad72..c96323a340b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -53,6 +53,7 @@ import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.transition.OneShotRemoteHandler; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; @@ -531,7 +532,7 @@ class SplitScreenTransitions { } private boolean isOpeningTransition(TransitionInfo info) { - return Transitions.isOpeningType(info.getType()) + return TransitionUtil.isOpeningType(info.getType()) || info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN; } 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 7833cfe7df9d..8b24d86a568f 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 @@ -61,8 +61,8 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonT 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; -import static com.android.wm.shell.transition.Transitions.isClosingType; -import static com.android.wm.shell.transition.Transitions.isOpeningType; +import static com.android.wm.shell.util.TransitionUtil.isClosingType; +import static com.android.wm.shell.util.TransitionUtil.isOpeningType; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -135,6 +135,7 @@ import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.SplitBounds; +import com.android.wm.shell.util.TransitionUtil; import java.io.PrintWriter; import java.util.ArrayList; @@ -2271,7 +2272,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Use normal animations. return false; - } else if (mMixedHandler != null && Transitions.hasDisplayChange(info)) { + } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) { // A display-change has been un-expectedly inserted into the transition. Redirect // handling to the mixed-handler to deal with splitting it up. if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java deleted file mode 100644 index 1ddd8f9a3a14..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.startingsurface; - -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.hardware.display.DisplayManager; -import android.view.Display; - -import com.android.wm.shell.common.ShellExecutor; - -// abstract class to create splash screen window(or windowless window) -abstract class AbsSplashWindowCreator { - protected static final String TAG = StartingWindowController.TAG; - protected final SplashscreenContentDrawer mSplashscreenContentDrawer; - protected final Context mContext; - protected final DisplayManager mDisplayManager; - protected final ShellExecutor mSplashScreenExecutor; - protected final StartingSurfaceDrawer.StartingWindowRecordManager mStartingWindowRecordManager; - - private StartingSurface.SysuiProxy mSysuiProxy; - - AbsSplashWindowCreator(SplashscreenContentDrawer contentDrawer, Context context, - ShellExecutor splashScreenExecutor, DisplayManager displayManager, - StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) { - mSplashscreenContentDrawer = contentDrawer; - mContext = context; - mSplashScreenExecutor = splashScreenExecutor; - mDisplayManager = displayManager; - mStartingWindowRecordManager = startingWindowRecordManager; - } - - int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) { - return splashScreenThemeResId != 0 - ? splashScreenThemeResId - : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource() - : com.android.internal.R.style.Theme_DeviceDefault_DayNight; - } - - protected Display getDisplay(int displayId) { - return mDisplayManager.getDisplay(displayId); - } - - void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) { - mSysuiProxy = sysuiProxy; - } - - protected void requestTopUi(boolean requestTopUi) { - if (mSysuiProxy != null) { - mSysuiProxy.requestTopUi(requestTopUi, TAG); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java deleted file mode 100644 index 20c4d5ae5f58..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.startingsurface; - -import android.window.StartingWindowInfo; -import android.window.TaskSnapshot; - -import com.android.wm.shell.common.ShellExecutor; - -class SnapshotWindowCreator { - private final ShellExecutor mMainExecutor; - private final StartingSurfaceDrawer.StartingWindowRecordManager - mStartingWindowRecordManager; - - SnapshotWindowCreator(ShellExecutor mainExecutor, - StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) { - mMainExecutor = mainExecutor; - mStartingWindowRecordManager = startingWindowRecordManager; - } - - void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) { - final int taskId = startingWindowInfo.taskInfo.taskId; - // Remove any existing starting window for this task before adding. - mStartingWindowRecordManager.removeWindow(taskId, true); - final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, - startingWindowInfo.appToken, snapshot, mMainExecutor, - () -> mStartingWindowRecordManager.removeWindow(taskId, true)); - if (surface != null) { - final SnapshotWindowRecord tView = new SnapshotWindowRecord(surface, - startingWindowInfo.taskInfo.topActivityType, mMainExecutor); - mStartingWindowRecordManager.addRecord(taskId, tView); - } - } - - private static class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord { - private final TaskSnapshotWindow mTaskSnapshotWindow; - - SnapshotWindowRecord(TaskSnapshotWindow taskSnapshotWindow, - int activityType, ShellExecutor removeExecutor) { - super(activityType, removeExecutor); - mTaskSnapshotWindow = taskSnapshotWindow; - mBGColor = mTaskSnapshotWindow.getBackgroundColor(); - } - - @Override - protected void removeImmediately() { - super.removeImmediately(); - mTaskSnapshotWindow.removeImmediately(); - } - - @Override - protected boolean hasImeSurface() { - return mTaskSnapshotWindow.hasImeSurface(); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 79cd891741d6..ebb957b2201b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -24,6 +24,9 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLA import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; +import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION; +import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION; + import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; @@ -93,25 +96,6 @@ import java.util.function.UnaryOperator; public class SplashscreenContentDrawer { private static final String TAG = StartingWindowController.TAG; - /** - * The minimum duration during which the splash screen is shown when the splash screen icon is - * animated. - */ - static final long MINIMAL_ANIMATION_DURATION = 400L; - - /** - * Allow the icon style splash screen to be displayed for longer to give time for the animation - * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly - * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration. - */ - static final long TIME_WINDOW_DURATION = 100L; - - /** - * The maximum duration during which the splash screen will be shown if the application is ready - * to show before the icon animation finishes. - */ - static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION; - // The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an // icon which it's non-transparent foreground area is similar to it's background area, then // do not enlarge the foreground drawable. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java deleted file mode 100644 index 4db81e232f20..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java +++ /dev/null @@ -1,506 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.startingsurface; - -import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION; -import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; - -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.ActivityTaskManager; -import android.app.ActivityThread; -import android.app.TaskInfo; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.hardware.display.DisplayManager; -import android.os.IBinder; -import android.os.RemoteCallback; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.Trace; -import android.os.UserHandle; -import android.util.Slog; -import android.util.SparseArray; -import android.view.Choreographer; -import android.view.Display; -import android.view.SurfaceControlViewHost; -import android.view.View; -import android.view.WindowInsetsController; -import android.view.WindowManager; -import android.view.WindowManagerGlobal; -import android.widget.FrameLayout; -import android.window.SplashScreenView; -import android.window.StartingWindowInfo; -import android.window.StartingWindowRemovalInfo; - -import com.android.internal.R; -import com.android.internal.protolog.common.ProtoLog; -import com.android.internal.util.ContrastColorUtil; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.protolog.ShellProtoLogGroup; - -import java.util.function.Supplier; - -/** - * A class which able to draw splash screen as the starting window for a task. - * - * In order to speed up, there will use two threads to creating a splash screen in parallel. - * Right now we are still using PhoneWindow to create splash screen window, so the view is added to - * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call - * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view - * can synchronize on each frame. - * - * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing - * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background - * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after - * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very - * quickly. - * - * So basically we are using the spare time to prepare the SplashScreenView while splash screen - * thread is waiting for - * 1. WindowManager#addView(binder call to WM), - * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device), - * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will - * always happen before #draw). - * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on - * splash-screen background tread can make they execute in parallel, which ensure it is faster then - * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame. - * - * Here is the sequence to compare the difference between using single and two thread. - * - * Single thread: - * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout - * -> draw -> AdaptiveIconDrawable#draw - * - * Two threads: - * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw) - * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint - * directly). - */ -class SplashscreenWindowCreator extends AbsSplashWindowCreator { - private static final int LIGHT_BARS_MASK = - WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS - | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; - - private final WindowManagerGlobal mWindowManagerGlobal; - private Choreographer mChoreographer; - - /** - * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is - * rendered and that have not yet been removed by their client. - */ - private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts = - new SparseArray<>(1); - - SplashscreenWindowCreator(SplashscreenContentDrawer contentDrawer, Context context, - ShellExecutor splashScreenExecutor, DisplayManager displayManager, - StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) { - super(contentDrawer, context, splashScreenExecutor, displayManager, - startingWindowRecordManager); - mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance()); - mWindowManagerGlobal = WindowManagerGlobal.getInstance(); - } - - void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, - @StartingWindowInfo.StartingWindowType int suggestType) { - final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo; - final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null - ? windowInfo.targetActivityInfo - : taskInfo.topActivityInfo; - if (activityInfo == null || activityInfo.packageName == null) { - return; - } - // replace with the default theme if the application didn't set - final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo); - final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme, - suggestType, mDisplayManager); - if (context == null) { - return; - } - final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters( - context, windowInfo, suggestType, activityInfo.packageName, - suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN - ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, windowInfo.appToken); - - final int displayId = taskInfo.displayId; - final int taskId = taskInfo.taskId; - final Display display = getDisplay(displayId); - - // TODO(b/173975965) tracking performance - // Prepare the splash screen content view on splash screen worker thread in parallel, so the - // content view won't be blocked by binder call like addWindow and relayout. - // 1. Trigger splash screen worker thread to create SplashScreenView before/while - // Session#addWindow. - // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start - // traversal, which will call Session#relayout on splash screen thread. - // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at - // the same time the splash screen thread should be executing Session#relayout. Blocking the - // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready. - - // Record whether create splash screen view success, notify to current thread after - // create splash screen view finished. - final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier(); - final FrameLayout rootLayout = new FrameLayout( - mSplashscreenContentDrawer.createViewContextWrapper(context)); - rootLayout.setPadding(0, 0, 0, 0); - rootLayout.setFitsSystemWindows(false); - final Runnable setViewSynchronized = () -> { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView"); - // waiting for setContentView before relayoutWindow - SplashScreenView contentView = viewSupplier.get(); - final StartingSurfaceDrawer.StartingWindowRecord sRecord = - mStartingWindowRecordManager.getRecord(taskId); - final SplashWindowRecord record = sRecord instanceof SplashWindowRecord - ? (SplashWindowRecord) sRecord : null; - // If record == null, either the starting window added fail or removed already. - // Do not add this view if the token is mismatch. - if (record != null && windowInfo.appToken == record.mAppToken) { - // if view == null then creation of content view was failed. - if (contentView != null) { - try { - rootLayout.addView(contentView); - } catch (RuntimeException e) { - Slog.w(TAG, "failed set content view to starting window " - + "at taskId: " + taskId, e); - contentView = null; - } - } - record.setSplashScreenView(contentView); - } - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - }; - requestTopUi(true); - mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo, - viewSupplier::setView, viewSupplier::setUiThreadInitTask); - try { - if (addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType)) { - // We use the splash screen worker thread to create SplashScreenView while adding - // the window, as otherwise Choreographer#doFrame might be delayed on this thread. - // And since Choreographer#doFrame won't happen immediately after adding the window, - // if the view is not added to the PhoneWindow on the first #doFrame, the view will - // not be rendered on the first frame. So here we need to synchronize the view on - // the window before first round relayoutWindow, which will happen after insets - // animation. - mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null); - final SplashWindowRecord record = - (SplashWindowRecord) mStartingWindowRecordManager.getRecord(taskId); - if (record != null) { - record.parseAppSystemBarColor(context); - // Block until we get the background color. - final SplashScreenView contentView = viewSupplier.get(); - if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { - contentView.addOnAttachStateChangeListener( - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - final int lightBarAppearance = - ContrastColorUtil.isColorLight( - contentView.getInitBackgroundColor()) - ? LIGHT_BARS_MASK : 0; - contentView.getWindowInsetsController() - .setSystemBarsAppearance( - lightBarAppearance, LIGHT_BARS_MASK); - } - - @Override - public void onViewDetachedFromWindow(View v) { - } - }); - } - } - } else { - // release the icon view host - final SplashScreenView contentView = viewSupplier.get(); - if (contentView.getSurfaceHost() != null) { - SplashScreenView.releaseIconHost(contentView.getSurfaceHost()); - } - } - } catch (RuntimeException e) { - // don't crash if something else bad happens, for example a - // failure loading resources because we are loading from an app - // on external storage that has been unmounted. - Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e); - } - } - - int estimateTaskBackgroundColor(TaskInfo taskInfo) { - if (taskInfo.topActivityInfo == null) { - return Color.TRANSPARENT; - } - final ActivityInfo activityInfo = taskInfo.topActivityInfo; - final String packageName = activityInfo.packageName; - final int userId = taskInfo.userId; - final Context windowContext; - try { - windowContext = mContext.createPackageContextAsUser( - packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId)); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Failed creating package context with package name " - + packageName + " for user " + taskInfo.userId, e); - return Color.TRANSPARENT; - } - try { - final IPackageManager packageManager = ActivityThread.getPackageManager(); - final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName, - userId); - final int splashScreenThemeId = splashScreenThemeName != null - ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null) - : 0; - - final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo); - - if (theme != windowContext.getThemeResId()) { - windowContext.setTheme(theme); - } - return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext); - } catch (RuntimeException | RemoteException e) { - Slog.w(TAG, "failed get starting window background color at taskId: " - + taskInfo.taskId, e); - } - return Color.TRANSPARENT; - } - - /** - * Called when the Task wants to copy the splash screen. - */ - public void copySplashScreenView(int taskId) { - final StartingSurfaceDrawer.StartingWindowRecord record = - mStartingWindowRecordManager.getRecord(taskId); - final SplashWindowRecord preView = record instanceof SplashWindowRecord - ? (SplashWindowRecord) record : null; - SplashScreenView.SplashScreenViewParcelable parcelable; - SplashScreenView splashScreenView = preView != null ? preView.mSplashView : null; - if (splashScreenView != null && splashScreenView.isCopyable()) { - parcelable = new SplashScreenView.SplashScreenViewParcelable(splashScreenView); - parcelable.setClientCallback( - new RemoteCallback((bundle) -> mSplashScreenExecutor.execute( - () -> onAppSplashScreenViewRemoved(taskId, false)))); - splashScreenView.onCopied(); - mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost()); - } else { - parcelable = null; - } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, - "Copying splash screen window view for task: %d with parcelable %b", - taskId, parcelable != null); - ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable); - } - - /** - * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy - * or when the Activity is clean up. - * - * @param taskId The Task id on which the splash screen was attached - */ - public void onAppSplashScreenViewRemoved(int taskId) { - onAppSplashScreenViewRemoved(taskId, true /* fromServer */); - } - - /** - * @param fromServer If true, this means the removal was notified by the server. This is only - * used for debugging purposes. - * @see #onAppSplashScreenViewRemoved(int) - */ - private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) { - SurfaceControlViewHost viewHost = - mAnimatedSplashScreenSurfaceHosts.get(taskId); - if (viewHost == null) { - return; - } - mAnimatedSplashScreenSurfaceHosts.remove(taskId); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, - "%s the splash screen. Releasing SurfaceControlViewHost for task: %d", - fromServer ? "Server cleaned up" : "App removed", taskId); - SplashScreenView.releaseIconHost(viewHost); - } - - protected boolean addWindow(int taskId, IBinder appToken, View view, Display display, - WindowManager.LayoutParams params, - @StartingWindowInfo.StartingWindowType int suggestType) { - boolean shouldSaveView = true; - final Context context = view.getContext(); - try { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView"); - mWindowManagerGlobal.addView(view, params, display, - null /* parentWindow */, context.getUserId()); - } catch (WindowManager.BadTokenException e) { - // ignore - Slog.w(TAG, appToken + " already running, starting window not displayed. " - + e.getMessage()); - shouldSaveView = false; - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - if (view.getParent() == null) { - Slog.w(TAG, "view not successfully added to wm, removing view"); - mWindowManagerGlobal.removeView(view, true /* immediate */); - shouldSaveView = false; - } - } - if (shouldSaveView) { - mStartingWindowRecordManager.removeWindow(taskId, true); - saveSplashScreenRecord(appToken, taskId, view, suggestType); - } - return shouldSaveView; - } - - private void saveSplashScreenRecord(IBinder appToken, int taskId, View view, - @StartingWindowInfo.StartingWindowType int suggestType) { - final SplashWindowRecord tView = - new SplashWindowRecord(appToken, view, suggestType); - mStartingWindowRecordManager.addRecord(taskId, tView); - } - - private void removeWindowInner(View decorView, boolean hideView) { - requestTopUi(false); - if (hideView) { - decorView.setVisibility(View.GONE); - } - mWindowManagerGlobal.removeView(decorView, false /* immediate */); - } - - private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> { - private SplashScreenView mView; - private boolean mIsViewSet; - private Runnable mUiThreadInitTask; - void setView(SplashScreenView view) { - synchronized (this) { - mView = view; - mIsViewSet = true; - notify(); - } - } - - void setUiThreadInitTask(Runnable initTask) { - synchronized (this) { - mUiThreadInitTask = initTask; - } - } - - @Override - @Nullable - public SplashScreenView get() { - synchronized (this) { - while (!mIsViewSet) { - try { - wait(); - } catch (InterruptedException ignored) { - } - } - if (mUiThreadInitTask != null) { - mUiThreadInitTask.run(); - mUiThreadInitTask = null; - } - return mView; - } - } - } - - private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord { - private final IBinder mAppToken; - private final View mRootView; - @StartingWindowInfo.StartingWindowType private final int mSuggestType; - private final long mCreateTime; - - private boolean mSetSplashScreen; - private SplashScreenView mSplashView; - private int mSystemBarAppearance; - private boolean mDrawsSystemBarBackgrounds; - - SplashWindowRecord(IBinder appToken, View decorView, - @StartingWindowInfo.StartingWindowType int suggestType) { - mAppToken = appToken; - mRootView = decorView; - mSuggestType = suggestType; - mCreateTime = SystemClock.uptimeMillis(); - } - - void setSplashScreenView(SplashScreenView splashScreenView) { - if (mSetSplashScreen) { - return; - } - mSplashView = splashScreenView; - mBGColor = mSplashView.getInitBackgroundColor(); - mSetSplashScreen = true; - } - - void parseAppSystemBarColor(Context context) { - final TypedArray a = context.obtainStyledAttributes(R.styleable.Window); - mDrawsSystemBarBackgrounds = a.getBoolean( - R.styleable.Window_windowDrawsSystemBarBackgrounds, false); - if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) { - mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; - } - if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) { - mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; - } - a.recycle(); - } - - // Reset the system bar color which set by splash screen, make it align to the app. - void clearSystemBarColor() { - if (mRootView == null || !mRootView.isAttachedToWindow()) { - return; - } - if (mRootView.getLayoutParams() instanceof WindowManager.LayoutParams) { - final WindowManager.LayoutParams lp = - (WindowManager.LayoutParams) mRootView.getLayoutParams(); - if (mDrawsSystemBarBackgrounds) { - lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - } else { - lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - } - mRootView.setLayoutParams(lp); - } - mRootView.getWindowInsetsController().setSystemBarsAppearance( - mSystemBarAppearance, LIGHT_BARS_MASK); - } - - @Override - public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { - if (mRootView != null) { - if (mSplashView != null) { - clearSystemBarColor(); - if (immediately - || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { - removeWindowInner(mRootView, false); - } else { - if (info.playRevealAnimation) { - mSplashscreenContentDrawer.applyExitAnimation(mSplashView, - info.windowAnimationLeash, info.mainFrame, - () -> removeWindowInner(mRootView, true), - mCreateTime, info.roundedCornerRadius); - } else { - // the SplashScreenView has been copied to client, hide the view to skip - // default exit animation - removeWindowInner(mRootView, true); - } - } - } else { - // shouldn't happen - Slog.e(TAG, "Found empty splash screen, remove!"); - removeWindowInner(mRootView, false); - } - } - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index ff06db370d1a..4f07bfeacce5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -16,80 +16,169 @@ package com.android.wm.shell.startingsurface; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION; import static android.view.Display.DEFAULT_DISPLAY; +import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; +import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT; -import android.annotation.CallSuper; +import android.annotation.Nullable; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; +import android.app.ActivityThread; import android.app.TaskInfo; -import android.app.WindowConfiguration; import android.content.Context; -import android.content.res.Configuration; +import android.content.pm.ActivityInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; +import android.os.IBinder; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.Trace; +import android.os.UserHandle; +import android.util.Slog; import android.util.SparseArray; -import android.view.IWindow; -import android.view.SurfaceControl; -import android.view.SurfaceSession; +import android.view.Choreographer; +import android.view.Display; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.WindowInsetsController; import android.view.WindowManager; -import android.view.WindowlessWindowManager; +import android.view.WindowManagerGlobal; +import android.widget.FrameLayout; import android.window.SplashScreenView; +import android.window.SplashScreenView.SplashScreenViewParcelable; import android.window.StartingWindowInfo; import android.window.StartingWindowInfo.StartingWindowType; import android.window.StartingWindowRemovalInfo; import android.window.TaskSnapshot; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.ContrastColorUtil; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import java.util.function.Supplier; + /** * A class which able to draw splash screen or snapshot as the starting window for a task. + * + * In order to speed up, there will use two threads to creating a splash screen in parallel. + * Right now we are still using PhoneWindow to create splash screen window, so the view is added to + * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call + * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view + * can synchronize on each frame. + * + * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing + * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background + * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after + * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very + * quickly. + * + * So basically we are using the spare time to prepare the SplashScreenView while splash screen + * thread is waiting for + * 1. WindowManager#addView(binder call to WM), + * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device), + * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will + * always happen before #draw). + * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on + * splash-screen background tread can make they execute in parallel, which ensure it is faster then + * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame. + * + * Here is the sequence to compare the difference between using single and two thread. + * + * Single thread: + * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout + * -> draw -> AdaptiveIconDrawable#draw + * + * Two threads: + * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw) + * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint + * directly). */ @ShellSplashscreenThread public class StartingSurfaceDrawer { + private static final String TAG = StartingWindowController.TAG; + private final Context mContext; + private final DisplayManager mDisplayManager; private final ShellExecutor mSplashScreenExecutor; @VisibleForTesting final SplashscreenContentDrawer mSplashscreenContentDrawer; - @VisibleForTesting - final SplashscreenWindowCreator mSplashscreenWindowCreator; - private final SnapshotWindowCreator mSnapshotWindowCreator; - private final WindowlessSplashWindowCreator mWindowlessSplashWindowCreator; - private final WindowlessSnapshotWindowCreator mWindowlessSnapshotWindowCreator; + private Choreographer mChoreographer; + private final WindowManagerGlobal mWindowManagerGlobal; + private StartingSurface.SysuiProxy mSysuiProxy; + private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo(); + + private static final int LIGHT_BARS_MASK = + WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS + | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; + /** + * The minimum duration during which the splash screen is shown when the splash screen icon is + * animated. + */ + static final long MINIMAL_ANIMATION_DURATION = 400L; + + /** + * Allow the icon style splash screen to be displayed for longer to give time for the animation + * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly + * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration. + */ + static final long TIME_WINDOW_DURATION = 100L; + + /** + * The maximum duration during which the splash screen will be shown if the application is ready + * to show before the icon animation finishes. + */ + static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION; - @VisibleForTesting - final StartingWindowRecordManager mWindowRecords = new StartingWindowRecordManager(); - // Windowless surface could co-exist with starting window in a task. - @VisibleForTesting - final StartingWindowRecordManager mWindowlessRecords = new StartingWindowRecordManager(); /** * @param splashScreenExecutor The thread used to control add and remove starting window. */ public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, IconProvider iconProvider, TransactionPool pool) { + mContext = context; + mDisplayManager = mContext.getSystemService(DisplayManager.class); mSplashScreenExecutor = splashScreenExecutor; - final DisplayManager displayManager = context.getSystemService(DisplayManager.class); - mSplashscreenContentDrawer = new SplashscreenContentDrawer(context, iconProvider, pool); - displayManager.getDisplay(DEFAULT_DISPLAY); - - mSplashscreenWindowCreator = new SplashscreenWindowCreator(mSplashscreenContentDrawer, - context, splashScreenExecutor, displayManager, mWindowRecords); - mSnapshotWindowCreator = new SnapshotWindowCreator(splashScreenExecutor, - mWindowRecords); - mWindowlessSplashWindowCreator = new WindowlessSplashWindowCreator( - mSplashscreenContentDrawer, context, splashScreenExecutor, displayManager, - mWindowlessRecords, pool); - mWindowlessSnapshotWindowCreator = new WindowlessSnapshotWindowCreator( - mWindowlessRecords, context, displayManager, mSplashscreenContentDrawer, pool); + mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool); + mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance()); + mWindowManagerGlobal = WindowManagerGlobal.getInstance(); + mDisplayManager.getDisplay(DEFAULT_DISPLAY); + } + + @VisibleForTesting + final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); + + /** + * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is + * rendered and that have not yet been removed by their client. + */ + private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts = + new SparseArray<>(1); + + private Display getDisplay(int displayId) { + return mDisplayManager.getDisplay(displayId); + } + + int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) { + return splashScreenThemeResId != 0 + ? splashScreenThemeResId + : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource() + : com.android.internal.R.style.Theme_DeviceDefault_DayNight; } void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) { - mSplashscreenWindowCreator.setSysuiProxy(sysuiProxy); - mWindowlessSplashWindowCreator.setSysuiProxy(sysuiProxy); + mSysuiProxy = sysuiProxy; } /** @@ -97,55 +186,231 @@ public class StartingSurfaceDrawer { * * @param suggestType The suggestion type to draw the splash screen. */ - void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, + void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken, @StartingWindowType int suggestType) { - mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType); + final RunningTaskInfo taskInfo = windowInfo.taskInfo; + final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null + ? windowInfo.targetActivityInfo + : taskInfo.topActivityInfo; + if (activityInfo == null || activityInfo.packageName == null) { + return; + } + // replace with the default theme if the application didn't set + final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo); + final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme, + suggestType, mDisplayManager); + if (context == null) { + return; + } + final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters( + context, windowInfo, suggestType, activityInfo.packageName, + suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN + ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, appToken); + + final int displayId = taskInfo.displayId; + final int taskId = taskInfo.taskId; + final Display display = getDisplay(displayId); + + // TODO(b/173975965) tracking performance + // Prepare the splash screen content view on splash screen worker thread in parallel, so the + // content view won't be blocked by binder call like addWindow and relayout. + // 1. Trigger splash screen worker thread to create SplashScreenView before/while + // Session#addWindow. + // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start + // traversal, which will call Session#relayout on splash screen thread. + // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at + // the same time the splash screen thread should be executing Session#relayout. Blocking the + // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready. + + // Record whether create splash screen view success, notify to current thread after + // create splash screen view finished. + final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier(); + final FrameLayout rootLayout = new FrameLayout( + mSplashscreenContentDrawer.createViewContextWrapper(context)); + rootLayout.setPadding(0, 0, 0, 0); + rootLayout.setFitsSystemWindows(false); + final Runnable setViewSynchronized = () -> { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView"); + // waiting for setContentView before relayoutWindow + SplashScreenView contentView = viewSupplier.get(); + final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + // If record == null, either the starting window added fail or removed already. + // Do not add this view if the token is mismatch. + if (record != null && appToken == record.mAppToken) { + // if view == null then creation of content view was failed. + if (contentView != null) { + try { + rootLayout.addView(contentView); + } catch (RuntimeException e) { + Slog.w(TAG, "failed set content view to starting window " + + "at taskId: " + taskId, e); + contentView = null; + } + } + record.setSplashScreenView(contentView); + } + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + }; + if (mSysuiProxy != null) { + mSysuiProxy.requestTopUi(true, TAG); + } + mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo, + viewSupplier::setView, viewSupplier::setUiThreadInitTask); + try { + if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) { + // We use the splash screen worker thread to create SplashScreenView while adding + // the window, as otherwise Choreographer#doFrame might be delayed on this thread. + // And since Choreographer#doFrame won't happen immediately after adding the window, + // if the view is not added to the PhoneWindow on the first #doFrame, the view will + // not be rendered on the first frame. So here we need to synchronize the view on + // the window before first round relayoutWindow, which will happen after insets + // animation. + mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null); + final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + record.parseAppSystemBarColor(context); + // Block until we get the background color. + final SplashScreenView contentView = viewSupplier.get(); + if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { + contentView.addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + final int lightBarAppearance = ContrastColorUtil.isColorLight( + contentView.getInitBackgroundColor()) + ? LIGHT_BARS_MASK : 0; + contentView.getWindowInsetsController().setSystemBarsAppearance( + lightBarAppearance, LIGHT_BARS_MASK); + } + + @Override + public void onViewDetachedFromWindow(View v) { + } + }); + } + record.mBGColor = contentView.getInitBackgroundColor(); + } else { + // release the icon view host + final SplashScreenView contentView = viewSupplier.get(); + if (contentView.getSurfaceHost() != null) { + SplashScreenView.releaseIconHost(contentView.getSurfaceHost()); + } + } + } catch (RuntimeException e) { + // don't crash if something else bad happens, for example a + // failure loading resources because we are loading from an app + // on external storage that has been unmounted. + Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e); + } } int getStartingWindowBackgroundColorForTask(int taskId) { - final StartingWindowRecord startingWindowRecord = mWindowRecords.getRecord(taskId); + final StartingWindowRecord startingWindowRecord = mStartingWindowRecords.get(taskId); if (startingWindowRecord == null) { return Color.TRANSPARENT; } - return startingWindowRecord.getBGColor(); + return startingWindowRecord.mBGColor; + } + + private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> { + private SplashScreenView mView; + private boolean mIsViewSet; + private Runnable mUiThreadInitTask; + void setView(SplashScreenView view) { + synchronized (this) { + mView = view; + mIsViewSet = true; + notify(); + } + } + + void setUiThreadInitTask(Runnable initTask) { + synchronized (this) { + mUiThreadInitTask = initTask; + } + } + + @Override + @Nullable + public SplashScreenView get() { + synchronized (this) { + while (!mIsViewSet) { + try { + wait(); + } catch (InterruptedException ignored) { + } + } + if (mUiThreadInitTask != null) { + mUiThreadInitTask.run(); + mUiThreadInitTask = null; + } + return mView; + } + } } int estimateTaskBackgroundColor(TaskInfo taskInfo) { - return mSplashscreenWindowCreator.estimateTaskBackgroundColor(taskInfo); + if (taskInfo.topActivityInfo == null) { + return Color.TRANSPARENT; + } + final ActivityInfo activityInfo = taskInfo.topActivityInfo; + final String packageName = activityInfo.packageName; + final int userId = taskInfo.userId; + final Context windowContext; + try { + windowContext = mContext.createPackageContextAsUser( + packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId)); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Failed creating package context with package name " + + packageName + " for user " + taskInfo.userId, e); + return Color.TRANSPARENT; + } + try { + final IPackageManager packageManager = ActivityThread.getPackageManager(); + final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName, + userId); + final int splashScreenThemeId = splashScreenThemeName != null + ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null) + : 0; + + final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo); + + if (theme != windowContext.getThemeResId()) { + windowContext.setTheme(theme); + } + return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext); + } catch (RuntimeException | RemoteException e) { + Slog.w(TAG, "failed get starting window background color at taskId: " + + taskInfo.taskId, e); + } + return Color.TRANSPARENT; } /** * Called when a task need a snapshot starting window. */ - void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) { - mSnapshotWindowCreator.makeTaskSnapshotWindow(startingWindowInfo, snapshot); + void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken, + TaskSnapshot snapshot) { + final int taskId = startingWindowInfo.taskInfo.taskId; + // Remove any existing starting window for this task before adding. + removeWindowNoAnimate(taskId); + final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken, + snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId)); + if (surface == null) { + return; + } + final StartingWindowRecord tView = new StartingWindowRecord(appToken, + null/* decorView */, surface, STARTING_WINDOW_TYPE_SNAPSHOT); + mStartingWindowRecords.put(taskId, tView); } /** * Called when the content of a task is ready to show, starting window can be removed. */ public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { - if (removalInfo.windowlessSurface) { - mWindowlessRecords.removeWindow(removalInfo, removalInfo.removeImmediately); - } else { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, - "Task start finish, remove starting surface for task: %d", - removalInfo.taskId); - mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately); - } - } - - /** - * Create a windowless starting surface and attach to the root surface. - */ - void addWindowlessStartingSurface(StartingWindowInfo windowInfo) { - if (windowInfo.taskSnapshot != null) { - mWindowlessSnapshotWindowCreator.makeTaskSnapshotWindow(windowInfo, - windowInfo.rootSurface, windowInfo.taskSnapshot, mSplashScreenExecutor); - } else { - mWindowlessSplashWindowCreator.addSplashScreenStartingWindow( - windowInfo, windowInfo.rootSurface); - } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "Task start finish, remove starting surface for task: %d", + removalInfo.taskId); + removeWindowSynced(removalInfo, false /* immediately */); } /** @@ -154,15 +419,37 @@ public class StartingSurfaceDrawer { public void clearAllWindows() { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "Clear all starting windows immediately"); - mWindowRecords.clearAllWindows(); - mWindowlessRecords.clearAllWindows(); + final int taskSize = mStartingWindowRecords.size(); + final int[] taskIds = new int[taskSize]; + for (int i = taskSize - 1; i >= 0; --i) { + taskIds[i] = mStartingWindowRecords.keyAt(i); + } + for (int i = taskSize - 1; i >= 0; --i) { + removeWindowNoAnimate(taskIds[i]); + } } /** * Called when the Task wants to copy the splash screen. */ public void copySplashScreenView(int taskId) { - mSplashscreenWindowCreator.copySplashScreenView(taskId); + final StartingWindowRecord preView = mStartingWindowRecords.get(taskId); + SplashScreenViewParcelable parcelable; + SplashScreenView splashScreenView = preView != null ? preView.mContentView : null; + if (splashScreenView != null && splashScreenView.isCopyable()) { + parcelable = new SplashScreenViewParcelable(splashScreenView); + parcelable.setClientCallback( + new RemoteCallback((bundle) -> mSplashScreenExecutor.execute( + () -> onAppSplashScreenViewRemoved(taskId, false)))); + splashScreenView.onCopied(); + mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost()); + } else { + parcelable = null; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "Copying splash screen window view for task: %d with parcelable %b", + taskId, parcelable != null); + ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable); } /** @@ -172,148 +459,195 @@ public class StartingSurfaceDrawer { * @param taskId The Task id on which the splash screen was attached */ public void onAppSplashScreenViewRemoved(int taskId) { - mSplashscreenWindowCreator.onAppSplashScreenViewRemoved(taskId); + onAppSplashScreenViewRemoved(taskId, true /* fromServer */); } - void onImeDrawnOnTask(int taskId) { - onImeDrawnOnTask(mWindowRecords, taskId); - onImeDrawnOnTask(mWindowlessRecords, taskId); - } - - private void onImeDrawnOnTask(StartingWindowRecordManager records, int taskId) { - final StartingSurfaceDrawer.StartingWindowRecord sRecord = - records.getRecord(taskId); - final SnapshotRecord record = sRecord instanceof SnapshotRecord - ? (SnapshotRecord) sRecord : null; - if (record != null && record.hasImeSurface()) { - records.removeWindow(taskId, true); + /** + * @param fromServer If true, this means the removal was notified by the server. This is only + * used for debugging purposes. + * @see #onAppSplashScreenViewRemoved(int) + */ + private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) { + SurfaceControlViewHost viewHost = + mAnimatedSplashScreenSurfaceHosts.get(taskId); + if (viewHost == null) { + return; } + mAnimatedSplashScreenSurfaceHosts.remove(taskId); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "%s the splash screen. Releasing SurfaceControlViewHost for task: %d", + fromServer ? "Server cleaned up" : "App removed", taskId); + SplashScreenView.releaseIconHost(viewHost); } - static class WindowlessStartingWindow extends WindowlessWindowManager { - SurfaceControl mChildSurface; - - WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface) { - super(c, rootSurface, null /* hostInputToken */); - } - - @Override - protected SurfaceControl getParentSurface(IWindow window, - WindowManager.LayoutParams attrs) { - final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) - .setContainerLayer() - .setName("Windowless window") - .setHidden(false) - .setParent(mRootSurface) - .setCallsite("WindowlessStartingWindow#attachToParentSurface"); - mChildSurface = builder.build(); - try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) { - t.setLayer(mChildSurface, Integer.MAX_VALUE); - t.apply(); + protected boolean addWindow(int taskId, IBinder appToken, View view, Display display, + WindowManager.LayoutParams params, @StartingWindowType int suggestType) { + boolean shouldSaveView = true; + final Context context = view.getContext(); + try { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView"); + mWindowManagerGlobal.addView(view, params, display, + null /* parentWindow */, context.getUserId()); + } catch (WindowManager.BadTokenException e) { + // ignore + Slog.w(TAG, appToken + " already running, starting window not displayed. " + + e.getMessage()); + shouldSaveView = false; + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + if (view.getParent() == null) { + Slog.w(TAG, "view not successfully added to wm, removing view"); + mWindowManagerGlobal.removeView(view, true /* immediate */); + shouldSaveView = false; } - return mChildSurface; } - } - abstract static class StartingWindowRecord { - protected int mBGColor; - abstract void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately); - int getBGColor() { - return mBGColor; + if (shouldSaveView) { + removeWindowNoAnimate(taskId); + saveSplashScreenRecord(appToken, taskId, view, suggestType); } + return shouldSaveView; } - abstract static class SnapshotRecord extends StartingWindowRecord { - private static final long DELAY_REMOVAL_TIME_GENERAL = 100; - /** - * The max delay time in milliseconds for removing the task snapshot window with IME - * visible. - * Ideally the delay time will be shorter when receiving - * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. - */ - private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600; - private final Runnable mScheduledRunnable = this::removeImmediately; - - @WindowConfiguration.ActivityType protected final int mActivityType; - protected final ShellExecutor mRemoveExecutor; - - SnapshotRecord(int activityType, ShellExecutor removeExecutor) { - mActivityType = activityType; - mRemoveExecutor = removeExecutor; - } - - @Override - public final void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { - if (immediately) { - removeImmediately(); - } else { - scheduleRemove(info.deferRemoveForIme); - } - } - - void scheduleRemove(boolean deferRemoveForIme) { - // Show the latest content as soon as possible for unlocking to home. - if (mActivityType == ACTIVITY_TYPE_HOME) { - removeImmediately(); - return; - } - mRemoveExecutor.removeCallbacks(mScheduledRunnable); - final long delayRemovalTime = hasImeSurface() && deferRemoveForIme - ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE - : DELAY_REMOVAL_TIME_GENERAL; - mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, - "Defer removing snapshot surface in %d", delayRemovalTime); - } + @VisibleForTesting + void saveSplashScreenRecord(IBinder appToken, int taskId, View view, + @StartingWindowType int suggestType) { + final StartingWindowRecord tView = new StartingWindowRecord(appToken, view, + null/* TaskSnapshotWindow */, suggestType); + mStartingWindowRecords.put(taskId, tView); + } - protected abstract boolean hasImeSurface(); + private void removeWindowNoAnimate(int taskId) { + mTmpRemovalInfo.taskId = taskId; + removeWindowSynced(mTmpRemovalInfo, true /* immediately */); + } - @CallSuper - protected void removeImmediately() { - mRemoveExecutor.removeCallbacks(mScheduledRunnable); + void onImeDrawnOnTask(int taskId) { + final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + if (record != null && record.mTaskSnapshotWindow != null + && record.mTaskSnapshotWindow.hasImeSurface()) { + removeWindowNoAnimate(taskId); } } - static class StartingWindowRecordManager { - private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo(); - private final SparseArray<StartingWindowRecord> mStartingWindowRecords = - new SparseArray<>(); + protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) { + final int taskId = removalInfo.taskId; + final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + if (record != null) { + if (record.mDecorView != null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "Removing splash screen window for task: %d", taskId); + if (record.mContentView != null) { + record.clearSystemBarColor(); + if (immediately + || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { + removeWindowInner(record.mDecorView, false); + } else { + if (removalInfo.playRevealAnimation) { + mSplashscreenContentDrawer.applyExitAnimation(record.mContentView, + removalInfo.windowAnimationLeash, removalInfo.mainFrame, + () -> removeWindowInner(record.mDecorView, true), + record.mCreateTime, removalInfo.roundedCornerRadius); + } else { + // the SplashScreenView has been copied to client, hide the view to skip + // default exit animation + removeWindowInner(record.mDecorView, true); + } + } + } else { + // shouldn't happen + Slog.e(TAG, "Found empty splash screen, remove!"); + removeWindowInner(record.mDecorView, false); + } - void clearAllWindows() { - final int taskSize = mStartingWindowRecords.size(); - final int[] taskIds = new int[taskSize]; - for (int i = taskSize - 1; i >= 0; --i) { - taskIds[i] = mStartingWindowRecords.keyAt(i); } - for (int i = taskSize - 1; i >= 0; --i) { - removeWindow(taskIds[i], true); + if (record.mTaskSnapshotWindow != null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "Removing task snapshot window for %d", taskId); + if (immediately) { + record.mTaskSnapshotWindow.removeImmediately(); + } else { + record.mTaskSnapshotWindow.scheduleRemove(removalInfo.deferRemoveForIme); + } } + mStartingWindowRecords.remove(taskId); } + } - void addRecord(int taskId, StartingWindowRecord record) { - mStartingWindowRecords.put(taskId, record); + private void removeWindowInner(View decorView, boolean hideView) { + if (mSysuiProxy != null) { + mSysuiProxy.requestTopUi(false, TAG); + } + if (hideView) { + decorView.setVisibility(View.GONE); } + mWindowManagerGlobal.removeView(decorView, false /* immediate */); + } - void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) { - final int taskId = removeInfo.taskId; - final StartingWindowRecord record = mStartingWindowRecords.get(taskId); - if (record != null) { - record.removeIfPossible(removeInfo, immediately); - mStartingWindowRecords.remove(taskId); + /** + * Record the view or surface for a starting window. + */ + private static class StartingWindowRecord { + private final IBinder mAppToken; + private final View mDecorView; + private final TaskSnapshotWindow mTaskSnapshotWindow; + private SplashScreenView mContentView; + private boolean mSetSplashScreen; + @StartingWindowType private int mSuggestType; + private int mBGColor; + private final long mCreateTime; + private int mSystemBarAppearance; + private boolean mDrawsSystemBarBackgrounds; + + StartingWindowRecord(IBinder appToken, View decorView, + TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) { + mAppToken = appToken; + mDecorView = decorView; + mTaskSnapshotWindow = taskSnapshotWindow; + if (mTaskSnapshotWindow != null) { + mBGColor = mTaskSnapshotWindow.getBackgroundColor(); } + mSuggestType = suggestType; + mCreateTime = SystemClock.uptimeMillis(); } - void removeWindow(int taskId, boolean immediately) { - mTmpRemovalInfo.taskId = taskId; - removeWindow(mTmpRemovalInfo, immediately); + private void setSplashScreenView(SplashScreenView splashScreenView) { + if (mSetSplashScreen) { + return; + } + mContentView = splashScreenView; + mSetSplashScreen = true; } - StartingWindowRecord getRecord(int taskId) { - return mStartingWindowRecords.get(taskId); + private void parseAppSystemBarColor(Context context) { + final TypedArray a = context.obtainStyledAttributes(R.styleable.Window); + mDrawsSystemBarBackgrounds = a.getBoolean( + R.styleable.Window_windowDrawsSystemBarBackgrounds, false); + if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) { + mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; + } + if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) { + mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; + } + a.recycle(); } - @VisibleForTesting - int recordSize() { - return mStartingWindowRecords.size(); + // Reset the system bar color which set by splash screen, make it align to the app. + private void clearSystemBarColor() { + if (mDecorView == null || !mDecorView.isAttachedToWindow()) { + return; + } + if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) { + final WindowManager.LayoutParams lp = + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); + if (mDrawsSystemBarBackgrounds) { + lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + } else { + lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + } + mDecorView.setLayoutParams(lp); + } + mDecorView.getWindowInsetsController().setSystemBarsAppearance( + mSystemBarAppearance, LIGHT_BARS_MASK); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index bec4ba3bf0d1..be2e79342d07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -21,7 +21,6 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; -import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW; @@ -30,6 +29,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; import android.content.Context; import android.graphics.Color; +import android.os.IBinder; import android.os.Trace; import android.util.SparseIntArray; import android.window.StartingWindowInfo; @@ -152,23 +152,22 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo /** * Called when a task need a starting window. */ - public void addStartingWindow(StartingWindowInfo windowInfo) { + public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { mSplashScreenExecutor.execute(() -> { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow"); final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType( windowInfo); final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo; - if (suggestionType == STARTING_WINDOW_TYPE_WINDOWLESS) { - mStartingSurfaceDrawer.addWindowlessStartingSurface(windowInfo); - } else if (isSplashScreenType(suggestionType)) { - mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType); + if (isSplashScreenType(suggestionType)) { + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken, + suggestionType); } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) { final TaskSnapshot snapshot = windowInfo.taskSnapshot; - mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot); + mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, + snapshot); } - if (suggestionType != STARTING_WINDOW_TYPE_NONE - && suggestionType != STARTING_WINDOW_TYPE_WINDOWLESS) { + if (suggestionType != STARTING_WINDOW_TYPE_NONE) { int taskId = runningTaskInfo.taskId; int color = mStartingSurfaceDrawer .getStartingWindowBackgroundColorForTask(taskId); @@ -219,13 +218,11 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow( removalInfo)); - if (!removalInfo.windowlessSurface) { - mSplashScreenExecutor.executeDelayed(() -> { - synchronized (mTaskBackgroundColors) { - mTaskBackgroundColors.delete(removalInfo.taskId); - } - }, TASK_BG_COLOR_RETAIN_TIME_MS); - } + mSplashScreenExecutor.executeDelayed(() -> { + synchronized (mTaskBackgroundColors) { + mTaskBackgroundColors.delete(removalInfo.taskId); + } + }, TASK_BG_COLOR_RETAIN_TIME_MS); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index c964df1452e0..a05ed4f24a08 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -16,6 +16,7 @@ package com.android.wm.shell.startingsurface; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.graphics.Color.WHITE; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -62,14 +63,24 @@ public class TaskSnapshotWindow { private static final String TAG = StartingWindowController.TAG; private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId="; + private static final long DELAY_REMOVAL_TIME_GENERAL = 100; + /** + * The max delay time in milliseconds for removing the task snapshot window with IME visible. + * Ideally the delay time will be shorter when receiving + * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. + */ + private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600; + private final Window mWindow; private final Runnable mClearWindowHandler; private final ShellExecutor mSplashScreenExecutor; private final IWindowSession mSession; private boolean mHasDrawn; private final Paint mBackgroundPaint = new Paint(); + private final int mActivityType; private final int mOrientationOnCreation; + private final Runnable mScheduledRunnable = this::removeImmediately; private final boolean mHasImeSurface; static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken, @@ -93,6 +104,7 @@ public class TaskSnapshotWindow { final Point taskSize = snapshot.getTaskSize(); final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y); final int orientation = snapshot.getOrientation(); + final int activityType = runningTaskInfo.topActivityType; final int displayId = runningTaskInfo.displayId; final IWindowSession session = WindowManagerGlobal.getWindowSession(); @@ -102,11 +114,16 @@ public class TaskSnapshotWindow { final InsetsSourceControl.Array tmpControls = new InsetsSourceControl.Array(); final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); - final TaskDescription taskDescription = - SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo); + final TaskDescription taskDescription; + if (runningTaskInfo.taskDescription != null) { + taskDescription = runningTaskInfo.taskDescription; + } else { + taskDescription = new TaskDescription(); + taskDescription.setBackgroundColor(WHITE); + } final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow( - snapshot, taskDescription, orientation, + snapshot, taskDescription, orientation, activityType, clearWindowHandler, splashScreenExecutor); final Window window = snapshotSurface.mWindow; @@ -136,8 +153,6 @@ public class TaskSnapshotWindow { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } catch (RemoteException e) { snapshotSurface.clearWindowSynced(); - Slog.w(TAG, "Failed to relayout snapshot starting window"); - return null; } SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot, @@ -149,7 +164,7 @@ public class TaskSnapshotWindow { } public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription, - int currentOrientation, Runnable clearWindowHandler, + int currentOrientation, int activityType, Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) { mSplashScreenExecutor = splashScreenExecutor; mSession = WindowManagerGlobal.getWindowSession(); @@ -158,6 +173,7 @@ public class TaskSnapshotWindow { int backgroundColor = taskDescription.getBackgroundColor(); mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE); mOrientationOnCreation = currentOrientation; + mActivityType = activityType; mClearWindowHandler = clearWindowHandler; mHasImeSurface = snapshot.hasImeSurface(); } @@ -170,7 +186,23 @@ public class TaskSnapshotWindow { return mHasImeSurface; } + void scheduleRemove(boolean deferRemoveForIme) { + // Show the latest content as soon as possible for unlocking to home. + if (mActivityType == ACTIVITY_TYPE_HOME) { + removeImmediately(); + return; + } + mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); + final long delayRemovalTime = mHasImeSurface && deferRemoveForIme + ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE + : DELAY_REMOVAL_TIME_GENERAL; + mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "Defer removing snapshot surface in %d", delayRemovalTime); + } + void removeImmediately() { + mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); try { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java deleted file mode 100644 index 144547885501..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.startingsurface; - -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.app.ActivityManager; -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.display.DisplayManager; -import android.view.Display; -import android.view.InsetsState; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.window.SnapshotDrawerUtils; -import android.window.StartingWindowInfo; -import android.window.TaskSnapshot; - -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.TransactionPool; - -class WindowlessSnapshotWindowCreator { - private static final int DEFAULT_FADEOUT_DURATION = 233; - private final StartingSurfaceDrawer.StartingWindowRecordManager - mStartingWindowRecordManager; - private final DisplayManager mDisplayManager; - private final Context mContext; - private final SplashscreenContentDrawer mSplashscreenContentDrawer; - private final TransactionPool mTransactionPool; - - WindowlessSnapshotWindowCreator( - StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager, - Context context, - DisplayManager displayManager, SplashscreenContentDrawer splashscreenContentDrawer, - TransactionPool transactionPool) { - mStartingWindowRecordManager = startingWindowRecordManager; - mContext = context; - mDisplayManager = displayManager; - mSplashscreenContentDrawer = splashscreenContentDrawer; - mTransactionPool = transactionPool; - } - - void makeTaskSnapshotWindow(StartingWindowInfo info, SurfaceControl rootSurface, - TaskSnapshot snapshot, ShellExecutor removeExecutor) { - final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; - final int taskId = runningTaskInfo.taskId; - final String title = "Windowless Snapshot " + taskId; - final WindowManager.LayoutParams lp = SnapshotDrawerUtils.createLayoutParameters( - info, title, TYPE_APPLICATION_OVERLAY, snapshot.getHardwareBuffer().getFormat(), - null /* token */); - if (lp == null) { - return; - } - final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId); - final StartingSurfaceDrawer.WindowlessStartingWindow wlw = - new StartingSurfaceDrawer.WindowlessStartingWindow( - runningTaskInfo.configuration, rootSurface); - final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost( - mContext, display, wlw, "WindowlessSnapshotWindowCreator"); - final Point taskSize = snapshot.getTaskSize(); - final Rect snapshotBounds = new Rect(0, 0, taskSize.x, taskSize.y); - final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds(); - final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; - final FrameLayout rootLayout = new FrameLayout( - mSplashscreenContentDrawer.createViewContextWrapper(mContext)); - mViewHost.setView(rootLayout, lp); - SnapshotDrawerUtils.drawSnapshotOnSurface(info, lp, wlw.mChildSurface, snapshot, - snapshotBounds, windowBounds, topWindowInsetsState, false /* releaseAfterDraw */); - - final ActivityManager.TaskDescription taskDescription = - SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo); - - final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface, - taskDescription.getBackgroundColor(), snapshot.hasImeSurface(), - runningTaskInfo.topActivityType, removeExecutor); - mStartingWindowRecordManager.addRecord(taskId, record); - info.notifyAddComplete(wlw.mChildSurface); - } - - private class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord { - private SurfaceControlViewHost mViewHost; - private SurfaceControl mChildSurface; - private final boolean mHasImeSurface; - - SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface, - int bgColor, boolean hasImeSurface, int activityType, - ShellExecutor removeExecutor) { - super(activityType, removeExecutor); - mViewHost = viewHost; - mChildSurface = childSurface; - mBGColor = bgColor; - mHasImeSurface = hasImeSurface; - } - - @Override - protected void removeImmediately() { - super.removeImmediately(); - fadeoutThenRelease(); - } - - void fadeoutThenRelease() { - final ValueAnimator fadeOutAnimator = ValueAnimator.ofFloat(1f, 0f); - fadeOutAnimator.setDuration(DEFAULT_FADEOUT_DURATION); - final SurfaceControl.Transaction t = mTransactionPool.acquire(); - fadeOutAnimator.addUpdateListener(animation -> { - if (mChildSurface == null || !mChildSurface.isValid()) { - fadeOutAnimator.cancel(); - return; - } - t.setAlpha(mChildSurface, (float) animation.getAnimatedValue()); - t.apply(); - }); - - fadeOutAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - if (mChildSurface == null || !mChildSurface.isValid()) { - fadeOutAnimator.cancel(); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - mTransactionPool.release(t); - if (mChildSurface != null) { - final SurfaceControl.Transaction t = mTransactionPool.acquire(); - t.remove(mChildSurface).apply(); - mTransactionPool.release(t); - mChildSurface = null; - } - if (mViewHost != null) { - mViewHost.release(); - mViewHost = null; - } - } - }); - fadeOutAnimator.start(); - } - - @Override - protected boolean hasImeSurface() { - return mHasImeSurface; - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java deleted file mode 100644 index 12a0d4054b4d..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.startingsurface; - -import static android.graphics.Color.WHITE; -import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; - -import android.app.ActivityManager; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.hardware.display.DisplayManager; -import android.os.Binder; -import android.os.SystemClock; -import android.view.Display; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.window.SplashScreenView; -import android.window.StartingWindowInfo; -import android.window.StartingWindowRemovalInfo; - -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.TransactionPool; - -class WindowlessSplashWindowCreator extends AbsSplashWindowCreator { - - private final TransactionPool mTransactionPool; - - WindowlessSplashWindowCreator(SplashscreenContentDrawer contentDrawer, - Context context, - ShellExecutor splashScreenExecutor, - DisplayManager displayManager, - StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager, - TransactionPool pool) { - super(contentDrawer, context, splashScreenExecutor, displayManager, - startingWindowRecordManager); - mTransactionPool = pool; - } - - void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, SurfaceControl rootSurface) { - final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo; - final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null - ? windowInfo.targetActivityInfo - : taskInfo.topActivityInfo; - if (activityInfo == null || activityInfo.packageName == null) { - return; - } - - final int displayId = taskInfo.displayId; - final Display display = mDisplayManager.getDisplay(displayId); - if (display == null) { - // Can't show splash screen on requested display, so skip showing at all. - return; - } - final Context myContext = SplashscreenContentDrawer.createContext(mContext, windowInfo, - 0 /* theme */, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager); - if (myContext == null) { - return; - } - final StartingSurfaceDrawer.WindowlessStartingWindow wlw = - new StartingSurfaceDrawer.WindowlessStartingWindow( - taskInfo.configuration, rootSurface); - final SurfaceControlViewHost viewHost = new SurfaceControlViewHost( - myContext, display, wlw, "WindowlessSplashWindowCreator"); - final String title = "Windowless Splash " + taskInfo.taskId; - final WindowManager.LayoutParams lp = SplashscreenContentDrawer.createLayoutParameters( - myContext, windowInfo, STARTING_WINDOW_TYPE_SPLASH_SCREEN, title, - PixelFormat.TRANSLUCENT, new Binder()); - final Rect windowBounds = taskInfo.configuration.windowConfiguration.getBounds(); - lp.width = windowBounds.width(); - lp.height = windowBounds.height(); - final ActivityManager.TaskDescription taskDescription; - if (taskInfo.taskDescription != null) { - taskDescription = taskInfo.taskDescription; - } else { - taskDescription = new ActivityManager.TaskDescription(); - taskDescription.setBackgroundColor(WHITE); - } - - final FrameLayout rootLayout = new FrameLayout( - mSplashscreenContentDrawer.createViewContextWrapper(mContext)); - viewHost.setView(rootLayout, lp); - - final int bgColor = taskDescription.getBackgroundColor(); - final SplashScreenView splashScreenView = mSplashscreenContentDrawer - .makeSimpleSplashScreenContentView(myContext, windowInfo, bgColor); - rootLayout.addView(splashScreenView); - final SplashWindowRecord record = new SplashWindowRecord(viewHost, splashScreenView, - wlw.mChildSurface, bgColor); - mStartingWindowRecordManager.addRecord(taskInfo.taskId, record); - windowInfo.notifyAddComplete(wlw.mChildSurface); - } - - private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord { - private SurfaceControlViewHost mViewHost; - private final long mCreateTime; - private SurfaceControl mChildSurface; - private final SplashScreenView mSplashView; - - SplashWindowRecord(SurfaceControlViewHost viewHost, SplashScreenView splashView, - SurfaceControl childSurface, int bgColor) { - mViewHost = viewHost; - mSplashView = splashView; - mChildSurface = childSurface; - mBGColor = bgColor; - mCreateTime = SystemClock.uptimeMillis(); - } - - @Override - public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) { - if (!immediately) { - mSplashscreenContentDrawer.applyExitAnimation(mSplashView, - info.windowAnimationLeash, info.mainFrame, - this::release, mCreateTime, 0 /* roundedCornerRadius */); - } else { - release(); - } - } - - void release() { - if (mChildSurface != null) { - final SurfaceControl.Transaction t = mTransactionPool.acquire(); - t.remove(mChildSurface).apply(); - mTransactionPool.release(t); - mChildSurface = null; - } - if (mViewHost != null) { - mViewHost.release(); - mViewHost = null; - } - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java index 72fc8686f648..bb43d7c1a090 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java @@ -22,7 +22,6 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; -import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS; import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED; import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN; import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT; @@ -31,7 +30,6 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK; import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN; -import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS; import android.window.StartingWindowInfo; @@ -57,7 +55,6 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor final boolean legacySplashScreen = ((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0); final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0; - final boolean windowlessSurface = (parameter & TYPE_PARAMETER_WINDOWLESS) != 0; final boolean topIsHome = windowInfo.taskInfo.topActivityType == ACTIVITY_TYPE_HOME; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, @@ -70,15 +67,10 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor + "isSolidColorSplashScreen=%b, " + "legacySplashScreen=%b, " + "activityDrawn=%b, " - + "windowless=%b, " + "topIsHome=%b", newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated, - isSolidColorSplashScreen, legacySplashScreen, activityDrawn, windowlessSurface, - topIsHome); + isSolidColorSplashScreen, legacySplashScreen, activityDrawn, topIsHome); - if (windowlessSurface) { - return STARTING_WINDOW_TYPE_WINDOWLESS; - } if (!topIsHome) { if (!processRunning || newTask || (taskSwitch && !activityCreated)) { return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java index 19133e29de4b..628ce27fe514 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java @@ -28,6 +28,7 @@ import android.window.WindowContainerToken; import androidx.annotation.NonNull; import com.android.wm.shell.util.CounterRotator; +import com.android.wm.shell.util.TransitionUtil; import java.util.List; @@ -57,7 +58,7 @@ public class CounterRotatorHelper { for (int i = numChanges - 1; i >= 0; --i) { final TransitionInfo.Change change = changes.get(i); final WindowContainerToken parent = change.getParent(); - if (!Transitions.isClosingType(change.getMode()) + if (!TransitionUtil.isClosingType(change.getMode()) || !TransitionInfo.isIndependent(change, info) || parent == null) { continue; } 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 8e916e63cac6..ef405c858e3d 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 @@ -44,6 +44,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; import java.util.Optional; @@ -149,7 +150,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mSplitHandler.addEnterOrExitIfNeeded(request, out); return out; } else if (request.getRemoteTransition() != null - && Transitions.isOpeningType(request.getType()) + && TransitionUtil.isOpeningType(request.getType()) && (request.getTriggerTask() == null || (request.getTriggerTask().topActivityType != ACTIVITY_TYPE_HOME && request.getTriggerTask().topActivityType != ACTIVITY_TYPE_RECENTS))) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index a3e05f2cf859..f66c26bb87e4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -112,6 +112,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; import java.util.List; @@ -445,7 +446,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { backgroundColorForTransition); if (!isTask && a.hasExtension()) { - if (!Transitions.isOpeningType(change.getMode())) { + if (!TransitionUtil.isOpeningType(change.getMode())) { // Can screenshot now (before startTransaction is applied) edgeExtendWindow(change, a, startTransaction, finishTransaction); } else { @@ -456,7 +457,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - final Rect clipRect = Transitions.isClosingType(change.getMode()) + final Rect clipRect = TransitionUtil.isClosingType(change.getMode()) ? new Rect(mRotator.getEndBoundsInStartRotation(change)) : new Rect(change.getEndAbsBounds()); clipRect.offsetTo(0, 0); @@ -562,12 +563,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final int flags = info.getFlags(); final int changeMode = change.getMode(); final int changeFlags = change.getFlags(); - final boolean isOpeningType = Transitions.isOpeningType(type); - final boolean enter = Transitions.isOpeningType(changeMode); + final boolean isOpeningType = TransitionUtil.isOpeningType(type); + final boolean enter = TransitionUtil.isOpeningType(changeMode); final boolean isTask = change.getTaskInfo() != null; final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); final int overrideType = options != null ? options.getType() : ANIM_NONE; - final Rect endBounds = Transitions.isClosingType(changeMode) + final Rect endBounds = TransitionUtil.isClosingType(changeMode) ? mRotator.getEndBoundsInStartRotation(change) : change.getEndAbsBounds(); @@ -689,8 +690,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private void attachThumbnail(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, TransitionInfo.AnimationOptions options, float cornerRadius) { - final boolean isOpen = Transitions.isOpeningType(change.getMode()); - final boolean isClose = Transitions.isClosingType(change.getMode()); + final boolean isOpen = TransitionUtil.isOpeningType(change.getMode()); + final boolean isClose = TransitionUtil.isClosingType(change.getMode()); if (isOpen) { if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) { attachCrossProfileThumbnailAnimation(animations, finishCallback, change, @@ -772,16 +773,16 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) { - if (Transitions.isOpeningType(change.getMode())) { + if (TransitionUtil.isOpeningType(change.getMode())) { hasOpenWallpaper = true; - } else if (Transitions.isClosingType(change.getMode())) { + } else if (TransitionUtil.isClosingType(change.getMode())) { hasCloseWallpaper = true; } } } if (hasOpenWallpaper && hasCloseWallpaper) { - return Transitions.isOpeningType(info.getType()) + return TransitionUtil.isOpeningType(info.getType()) ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE; } else if (hasOpenWallpaper) { return WALLPAPER_TRANSITION_OPEN; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index 02f19ebdb758..3c4e8898f215 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -39,6 +39,7 @@ import androidx.annotation.BinderThread; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; @@ -93,7 +94,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (!Transitions.SHELL_TRANSITIONS_ROTATION && Transitions.hasDisplayChange(info)) { + if (!Transitions.SHELL_TRANSITIONS_ROTATION && TransitionUtil.hasDisplayChange(info)) { // Note that if the remote doesn't have permission ACCESS_SURFACE_FLINGER, some // operations of the start transaction may be ignored. return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 5a5ceab383e7..8d29901c3a07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -54,6 +54,7 @@ import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.util.TransitionUtil; /** The helper class that provides methods for adding styles to transition animations. */ public class TransitionAnimationHelper { @@ -66,7 +67,7 @@ public class TransitionAnimationHelper { final int type = info.getType(); final int changeMode = change.getMode(); final int changeFlags = change.getFlags(); - final boolean enter = Transitions.isOpeningType(changeMode); + final boolean enter = TransitionUtil.isOpeningType(changeMode); final boolean isTask = change.getTaskInfo() != null; final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); final int overrideType = options != null ? options.getType() : ANIM_NONE; 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 0a67477212e9..3b154d1cda83 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 @@ -19,13 +19,11 @@ package com.android.wm.shell.transition; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; -import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; -import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_NO_ANIMATION; @@ -33,6 +31,8 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; +import static com.android.wm.shell.util.TransitionUtil.isClosingType; +import static com.android.wm.shell.util.TransitionUtil.isOpeningType; import android.annotation.NonNull; import android.annotation.Nullable; @@ -319,29 +319,6 @@ public class Transitions implements RemoteCallable<Transitions> { } } - /** @return true if the transition was triggered by opening something vs closing something */ - public static boolean isOpeningType(@WindowManager.TransitionType int type) { - return type == TRANSIT_OPEN - || type == TRANSIT_TO_FRONT - || type == TRANSIT_KEYGUARD_GOING_AWAY; - } - - /** @return true if the transition was triggered by closing something vs opening something */ - public static boolean isClosingType(@WindowManager.TransitionType int type) { - return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK; - } - - /** Returns {@code true} if the transition has a display change. */ - public static boolean hasDisplayChange(@NonNull TransitionInfo info) { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (change.getMode() == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) { - return true; - } - } - return false; - } - /** * Sets up visibility/alpha/transforms to resemble the starting state of an animation. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java new file mode 100644 index 000000000000..145f759d4de2 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.util; + +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_IS_DISPLAY; + +import android.annotation.NonNull; +import android.view.WindowManager; +import android.window.TransitionInfo; + +/** Various utility functions for transitions. */ +public class TransitionUtil { + + /** @return true if the transition was triggered by opening something vs closing something */ + public static boolean isOpeningType(@WindowManager.TransitionType int type) { + return type == TRANSIT_OPEN + || type == TRANSIT_TO_FRONT + || type == TRANSIT_KEYGUARD_GOING_AWAY; + } + + /** @return true if the transition was triggered by closing something vs opening something */ + public static boolean isClosingType(@WindowManager.TransitionType int type) { + return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK; + } + + /** Returns {@code true} if the transition has a display change. */ + public static boolean hasDisplayChange(@NonNull TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getMode() == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) { + return true; + } + } + return false; + } + +} diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index d6adaa7d533b..b6696c70dbb1 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -41,6 +41,8 @@ android_test { static_libs: [ "androidx.test.ext.junit", "flickerlib", + "flickerlib-apphelpers", + "flickerlib-helpers", "truth-prebuilt", "app-helpers-core", "launcher-helper-lib", diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml index 65923ff36fc8..67ca9a1a17f7 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml @@ -24,7 +24,11 @@ </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> + <option name="run-command" value="settings put system show_touches 1" /> + <option name="run-command" value="settings put system pointer_location 1" /> <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> + <option name="teardown-command" value="settings delete system show_touches" /> + <option name="teardown-command" value="settings delete system pointer_location" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt index aafd7edc7ef8..c5ee7b722617 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt @@ -18,12 +18,13 @@ package com.android.wm.shell.flicker import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerBuilderProvider +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.junit.FlickerBuilderProvider import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible @@ -32,7 +33,6 @@ import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.Assume import org.junit.Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index bd18108c841e..ed93045ec462 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -18,13 +18,13 @@ package com.android.wm.shell.flicker -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject -import com.android.server.wm.traces.common.component.matchers.IComponentMatcher -import com.android.server.wm.traces.common.region.Region -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.common.datatypes.Region +import android.tools.common.datatypes.component.IComponentMatcher +import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject +import android.tools.common.flicker.subject.layers.LayersTraceSubject +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.helpers.WindowUtils fun FlickerTest.appPairsDividerIsVisibleAtEnd() { assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } @@ -247,7 +247,7 @@ fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean, - rotation: PlatformConsts.Rotation + rotation: Rotation ): LayersTraceSubject { return invoke("splitAppLayerBoundsSnapToDivider") { it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation) @@ -258,11 +258,13 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean, - rotation: PlatformConsts.Rotation + rotation: Rotation ): LayerTraceEntrySubject { val displayBounds = WindowUtils.getDisplayBounds(rotation) return invoke { - val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region + val dividerRegion = + layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region + ?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found") visibleRegion(component) .coversAtMost( if (displayBounds.width > displayBounds.height) { @@ -367,46 +369,54 @@ fun FlickerTest.dockedStackDividerNotExistsAtEnd() { } fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd( - rotation: PlatformConsts.Rotation, + rotation: Rotation, primaryComponent: IComponentMatcher ) { assertLayersEnd { - val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region + val dividerRegion = + layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT)?.visibleRegion?.region + ?: error("$APP_PAIR_SPLIT_DIVIDER_COMPONENT component not found") visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation)) } } fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( - rotation: PlatformConsts.Rotation, + rotation: Rotation, primaryComponent: IComponentMatcher ) { assertLayersEnd { - val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region + val dividerRegion = + layer(DOCKED_STACK_DIVIDER_COMPONENT)?.visibleRegion?.region + ?: error("$DOCKED_STACK_DIVIDER_COMPONENT component not found") visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation)) } } fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( - rotation: PlatformConsts.Rotation, + rotation: Rotation, secondaryComponent: IComponentMatcher ) { assertLayersEnd { - val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region + val dividerRegion = + layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT)?.visibleRegion?.region + ?: error("$APP_PAIR_SPLIT_DIVIDER_COMPONENT component not found") visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation)) } } fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd( - rotation: PlatformConsts.Rotation, + rotation: Rotation, secondaryComponent: IComponentMatcher ) { assertLayersEnd { - val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region + val dividerRegion = + layer(DOCKED_STACK_DIVIDER_COMPONENT)?.visibleRegion?.region + ?: error("$DOCKED_STACK_DIVIDER_COMPONENT component not found") visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation)) } } -fun getPrimaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region { +fun getPrimaryRegion(dividerRegion: Region, rotation: Rotation): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) return if (rotation.isRotated()) { Region.from( @@ -425,7 +435,7 @@ fun getPrimaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): } } -fun getSecondaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region { +fun getSecondaryRegion(dividerRegion: Region, rotation: Rotation): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) return if (rotation.isRotated()) { Region.from( diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index e9c805ee5f4d..983640a70c4b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -18,7 +18,7 @@ package com.android.wm.shell.flicker -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher +import android.tools.common.datatypes.component.ComponentNameMatcher const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher" diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt index 996b677470fe..bab81d79c804 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -21,16 +21,16 @@ import android.app.NotificationManager import android.content.Context import android.content.pm.PackageManager import android.os.ServiceManager +import android.tools.common.Rotation +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.IFlickerTestData +import android.tools.device.helpers.SYSTEMUI_PACKAGE import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.IFlickerTestData import com.android.server.wm.flicker.helpers.LaunchBubbleHelper -import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.BaseTest import org.junit.runners.Parameterized @@ -89,7 +89,7 @@ abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) { @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt index 7358da3a58af..d0bca1332553 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt @@ -18,14 +18,14 @@ package com.android.wm.shell.flicker.bubble import android.os.SystemClock import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.Before import org.junit.Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt index 1a0fbe461444..bdfdad59c600 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.wm.shell.flicker.bubble -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt index cf696c8bbf59..5e85eb87e0e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.FlakyTest +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.Before import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt index 9367a8a0491a..8474ce0e64e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt @@ -19,14 +19,14 @@ package com.android.wm.shell.flicker.bubble import android.content.Context import android.graphics.Point import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import android.util.DisplayMetrics import android.view.WindowManager import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt index 85a534c30ed5..62fa7b4516c7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.wm.shell.flicker.bubble -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLockreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt index 0b1382be0c91..416315e4b06d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLockreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt @@ -18,14 +18,14 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import android.view.WindowInsets import android.view.WindowManager import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd import com.android.server.wm.flicker.navBarLayerPositionAtEnd import org.junit.Assume @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized /** * Test launching a new activity from bubble. * - * To run this test: `atest WMShellFlickerTests:LaunchBubbleFromLockScreen` + * To run this test: `atest WMShellFlickerTests:OpenActivityFromBubbleOnLocksreenTest` * * Actions: * ``` @@ -46,7 +46,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class OpenActivityFromBubbleOnLockreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt index 50507bf88d2f..07ba41333071 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt @@ -17,12 +17,12 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt index 94147e876372..6c61710d6284 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.wm.shell.flicker.bubble -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt index 4be4dcd7e1f0..29f76d01af83 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt @@ -17,12 +17,12 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt index 7efbcdbf0013..e323ebf3b5c8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.wm.shell.flicker.bubble -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index 88cf15e92c99..1045a5ac2ce8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -18,10 +18,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -44,7 +44,7 @@ import org.junit.runners.Parameterized * ``` * 1. All assertions are inherited from [EnterPipTest] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt index 88542d51563d..2d2588ef4348 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -43,7 +43,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt index fb1eb01918d7..02f60100d069 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,7 +40,7 @@ class ClosePipBySwipingDownTestCfArm(flicker: FlickerTest) : ClosePipBySwipingDo @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt index 080e033f3074..6c5a344c8f79 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt @@ -17,13 +17,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.LAUNCHER +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.LAUNCHER -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Test import org.junit.runners.Parameterized @@ -32,7 +32,7 @@ abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) override val transition: FlickerBuilder.() -> Unit get() = buildTransition { setup { this.setRotation(flicker.scenario.startRotation) } - teardown { this.setRotation(PlatformConsts.Rotation.ROTATION_0) } + teardown { this.setRotation(Rotation.ROTATION_0) } } /** @@ -91,7 +91,7 @@ abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt index f27fa4a81328..e540ad543228 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -43,7 +43,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt index fbada69f6f32..05262feceba5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -41,7 +41,7 @@ open class ClosePipWithDismissButtonTestCfArm(flicker: FlickerTest) : @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index 47537c6c5cdd..11bb0cc1306e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -39,14 +39,6 @@ import org.junit.runners.Parameterized * Select "Via code behind" radio button * Press Home button or swipe up to go Home and put [pipApp] in pip mode * ``` - * Notes: - * ``` - * 1. All assertions are inherited from [EnterPipTest] - * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], - * including configuring navigation mode, initial orientation and ensuring no - * apps are running before setup - * ``` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt index e47805001cd0..90f99c0c4cae 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index db5048968112..e079d5477e2f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -20,18 +20,18 @@ import android.app.Activity import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT import org.junit.Assume @@ -58,7 +58,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` @@ -69,8 +69,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flicker) { private val testApp = FixedOrientationAppHelper(instrumentation) - private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90) - private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0) + private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) + private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit @@ -213,7 +213,7 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt index ec5f13cbed49..58416660826f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -42,7 +42,7 @@ open class EnterPipToOtherOrientationCfArm(flicker: FlickerTest) : @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt index 3ef66d7e8ed2..327225421580 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.Test import org.junit.runners.Parameterized @@ -130,7 +130,7 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt index c3c705eb58e5..1f060e931be2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited from [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt index b487ff4a296b..4390f0bb70b2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,7 +40,7 @@ class EnterPipViaAppUiButtonTestCfArm(flicker: FlickerTest) : EnterPipViaAppUiBu @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt index f88f8d6e64ed..2001f484ed96 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Test import org.junit.runners.Parameterized @@ -137,7 +137,7 @@ abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flic @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt index d2fbb2a2c941..313631cbe8ee 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt @@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -47,7 +47,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt index 8b3755e38366..eccb85d98798 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -41,7 +41,7 @@ class ExitPipToAppViaExpandButtonTestCfArm(flicker: FlickerTest) : @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt index a9eb18d44856..93ffdd8d5294 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt @@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -46,7 +46,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited from [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt index 39b1c82f9676..6ab6a1f0bb73 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,7 +40,7 @@ class ExitPipToAppViaIntentTestCfArm(flicker: FlickerTest) : ExitPipToAppViaInte @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index d577b4f46319..7d5f740838bd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -17,13 +17,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -45,7 +45,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` @@ -147,7 +147,7 @@ open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flic @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt index 08db8aefb148..c09623490041 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -41,7 +41,7 @@ class ExpandPipOnDoubleClickTestTestCfArm(flicker: FlickerTest) : @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt index fcb8af4e8d40..0b73aac02797 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt @@ -17,12 +17,12 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -61,7 +61,7 @@ open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicke @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt index 30050bf4e9d9..e064bf2ee921 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,7 +40,7 @@ class ExpandPipOnPinchOpenTestCfArm(flicker: FlickerTest) : ExpandPipOnPinchOpen @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt index 39ac49f8c81c..9c007449fb8d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.wm.shell.flicker.Direction import org.junit.FixMethodOrder import org.junit.Test @@ -45,7 +45,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt index 7db80a8c8110..c23838a987bf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt @@ -17,17 +17,17 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.FixMethodOrder @@ -91,7 +91,7 @@ open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransitio @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt index be3bd60d28e8..d3d77d20662e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -37,7 +37,7 @@ class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) : @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt index ef9920c3c793..6f8111690f0f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt index 77a8c3c3e43f..109354ab5c79 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory +import android.tools.common.Rotation +import android.tools.common.flicker.subject.region.RegionSubject +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper -import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.Direction import org.junit.Test import org.junit.runners.Parameterized @@ -118,7 +118,7 @@ abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransitio @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt index 511a6511eb44..c8d5624b1d77 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt @@ -17,10 +17,10 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.wm.shell.flicker.Direction import org.junit.FixMethodOrder import org.junit.Test @@ -45,7 +45,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index e13344390584..85b2fbce2f21 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -17,12 +17,12 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Postsubmit +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -61,7 +61,7 @@ class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) { @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index 166416a49e4b..b30f30830156 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -19,15 +19,15 @@ package com.android.wm.shell.flicker.pip import android.app.Instrumentation import android.content.Intent import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome +import android.tools.device.helpers.WindowUtils import com.android.server.wm.flicker.helpers.PipAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.BaseTest import com.google.common.truth.Truth import org.junit.Test @@ -70,7 +70,7 @@ abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) { ): FlickerBuilder.() -> Unit { return { setup { - setRotation(PlatformConsts.Rotation.ROTATION_0) + setRotation(Rotation.ROTATION_0) removeAllTasksButHome() pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt index 3f5d06748d05..3850c1f6c89a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt @@ -20,15 +20,15 @@ import android.app.Activity import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import org.junit.Assume import org.junit.Before @@ -47,8 +47,8 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransition(flicker) { - private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0) - private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90) + private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) + private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -66,7 +66,7 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit wmHelper .StateSyncBuilder() .withPipShown() - .withRotation(PlatformConsts.Rotation.ROTATION_0) + .withRotation(Rotation.ROTATION_0) .withNavOrTaskBarVisible() .withStatusBarVisible() .waitForAndVerify() @@ -79,7 +79,7 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit wmHelper .StateSyncBuilder() .withFullScreenApp(pipApp) - .withRotation(PlatformConsts.Rotation.ROTATION_90) + .withRotation(Rotation.ROTATION_90) .withNavOrTaskBarVisible() .withStatusBarVisible() .waitForAndVerify() @@ -98,7 +98,7 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit @Presubmit @Test fun displayEndsAt90Degrees() { - flicker.assertWmEnd { hasRotation(PlatformConsts.Rotation.ROTATION_90) } + flicker.assertWmEnd { hasRotation(Rotation.ROTATION_90) } } @Presubmit @@ -151,7 +151,7 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt index 720fe7244047..2cf8f61f13fe 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt @@ -17,14 +17,14 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -48,7 +48,7 @@ import org.junit.runners.Parameterized * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited from [PipTransition] * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * [android.tools.device.flicker.legacy.runner.TransitionRunner], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt index daf3e1b18b4b..b7a2c47e3b32 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.flicker.pip -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt index 36909dd74245..000ae8f9458e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt @@ -17,12 +17,12 @@ package com.android.wm.shell.flicker.pip.tv import android.app.Instrumentation +import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.PipAppHelper -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper /** Helper class for PIP app on AndroidTV */ open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt index dc1fe4761757..6104b7bdacba 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt @@ -20,10 +20,10 @@ import android.app.ActivityManager import android.app.IActivityManager import android.app.IProcessObserver import android.os.SystemClock +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import android.tools.device.traces.parsers.WindowManagerStateHelper import android.view.Surface.ROTATION_0 import android.view.Surface.rotationToString -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME import org.junit.After import org.junit.Assert.assertFalse diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index 247403a2cbc6..0c9c16153ea3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.datatypes.component.EdgeExtensionComponentMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.component.matchers.EdgeExtensionComponentMatcher import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd import com.android.wm.shell.flicker.appWindowIsVisibleAtStart diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt index d3c68207bf97..1b55f3975e1c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt @@ -20,12 +20,12 @@ import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.wm.shell.flicker.appWindowBecomesInvisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd import com.android.wm.shell.flicker.layerBecomesInvisible @@ -104,14 +104,12 @@ class DismissSplitScreenByDivider(flicker: FlickerTest) : SplitScreenBase(flicke @Test fun secondaryAppBoundsIsFullscreenAtEnd() { flicker.assertLayers { - this.isVisible(secondaryApp) - .then() - .isInvisible(secondaryApp) - .then() - .invoke("secondaryAppBoundsIsFullscreenAtEnd") { - val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation) - it.visibleRegion(secondaryApp).coversExactly(displayBounds) - } + this.isVisible(secondaryApp).then().isInvisible(secondaryApp).then().invoke( + "secondaryAppBoundsIsFullscreenAtEnd" + ) { + val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation) + it.visibleRegion(secondaryApp).coversExactly(displayBounds) + } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt index b44b681704ba..bd2ffc1a018d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt @@ -19,11 +19,11 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.wm.shell.flicker.appWindowBecomesInvisible import com.android.wm.shell.flicker.layerBecomesInvisible import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index 514365fbd71a..7db5ecc484ad 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd import com.android.wm.shell.flicker.appWindowIsVisibleAtStart diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt index 4e36c367f226..ffdb87f190d5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt @@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.IwTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd @@ -186,7 +186,7 @@ class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase( fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt index 5d37e858c15f..792e2b03522f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt @@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.IwTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd import com.android.wm.shell.flicker.layerBecomesVisible @@ -208,7 +208,7 @@ class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreen fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt index d086f7e04486..c1977e9e82f7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt @@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd @@ -136,7 +136,7 @@ class EnterSplitScreenByDragFromShortcut(flicker: FlickerTest) : SplitScreenBase fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt index 795a2c4f43ba..da80c6f46976 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt @@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.IwTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd @@ -205,7 +205,7 @@ class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase( @JvmStatic fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt index a9cbb7419417..c45387722a49 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt @@ -19,11 +19,11 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt index 8c0a303189e1..7abdc06820d6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt @@ -17,8 +17,8 @@ package com.android.wm.shell.flicker.splitscreen import android.content.Context -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseTest diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt index 4f8cfca5c872..7901f7502e2c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt @@ -19,6 +19,12 @@ package com.android.wm.shell.flicker.splitscreen import android.app.Instrumentation import android.graphics.Point import android.os.SystemClock +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.datatypes.component.IComponentMatcher +import android.tools.common.datatypes.component.IComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import android.view.InputDevice import android.view.MotionEvent import android.view.ViewConfiguration @@ -32,16 +38,9 @@ import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.NotificationAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.component.matchers.IComponentMatcher -import com.android.server.wm.traces.common.component.matchers.IComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME -import java.util.Collections import org.junit.Assert.assertNotNull internal object SplitScreenUtils { @@ -129,18 +128,12 @@ internal object SplitScreenUtils { // Find the second task in the upper right corner in split select mode by sorting // 'left' in descending order and 'top' in ascending order. - Collections.sort( - snapshots, - { t1: UiObject2, t2: UiObject2 -> - t2.getVisibleBounds().left - t1.getVisibleBounds().left - } - ) - Collections.sort( - snapshots, - { t1: UiObject2, t2: UiObject2 -> - t1.getVisibleBounds().top - t2.getVisibleBounds().top - } - ) + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t2.getVisibleBounds().left - t1.getVisibleBounds().left + } + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t1.getVisibleBounds().top - t2.getVisibleBounds().top + } snapshots[0].click() } else { tapl.workspace diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index c7b81d924a9b..fbb7c7159234 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -19,14 +19,15 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.IwTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils +import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd import com.android.wm.shell.flicker.appWindowIsVisibleAtStart @@ -133,7 +134,7 @@ class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicke .waitForAndVerify() } - private fun isLandscape(rotation: PlatformConsts.Rotation): Boolean { + private fun isLandscape(rotation: Rotation): Boolean { val displayBounds = WindowUtils.getDisplayBounds(rotation) return displayBounds.width > displayBounds.height } @@ -205,7 +206,7 @@ class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicke fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt index 940e0e93d524..d675bfb0119d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt @@ -19,12 +19,12 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd @@ -166,7 +166,7 @@ class SwitchBackToSplitFromAnotherApp(flicker: FlickerTest) : SplitScreenBase(fl fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt index 8c3bea8e6297..2855c71518eb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt @@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd @@ -179,7 +179,7 @@ class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt index 06a1449f75ee..c29a917c4e7c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt @@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import com.android.wm.shell.flicker.appWindowBecomesVisible import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd @@ -179,7 +179,7 @@ class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicke fun getParams(): List<FlickerTest> { return FlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt index 193ab98cf191..4c96b3a319d5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt @@ -19,11 +19,11 @@ package com.android.wm.shell.flicker.splitscreen import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesInvisible import com.android.wm.shell.flicker.appWindowBecomesVisible diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index bf62acfc47a1..11fda8bf7bbc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -24,8 +24,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MAX_ANIMATION_DURATION; -import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MINIMAL_ANIMATION_DURATION; +import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION; +import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -56,9 +56,11 @@ import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; import android.testing.TestableContext; +import android.view.Display; import android.view.IWindowSession; import android.view.InsetsState; import android.view.Surface; +import android.view.View; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.WindowMetrics; @@ -104,7 +106,36 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { private ShellExecutor mTestExecutor; private final TestableContext mTestContext = new TestContext( InstrumentationRegistry.getInstrumentation().getTargetContext()); - StartingSurfaceDrawer mStartingSurfaceDrawer; + TestStartingSurfaceDrawer mStartingSurfaceDrawer; + + static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{ + int mAddWindowForTask = 0; + + TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, + IconProvider iconProvider, TransactionPool pool) { + super(context, splashScreenExecutor, iconProvider, pool); + } + + @Override + protected boolean addWindow(int taskId, IBinder appToken, View view, Display display, + WindowManager.LayoutParams params, int suggestType) { + // listen for addView + mAddWindowForTask = taskId; + saveSplashScreenRecord(appToken, taskId, view, suggestType); + // Do not wait for background color + return false; + } + + @Override + protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, + boolean immediately) { + // listen for removeView + if (mAddWindowForTask == removalInfo.taskId) { + mAddWindowForTask = 0; + } + mStartingWindowRecords.remove(removalInfo.taskId); + } + } private static class TestContext extends TestableContext { TestContext(Context context) { @@ -134,51 +165,44 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics(); doNothing().when(mMockWindowManager).addView(any(), any()); mTestExecutor = new HandlerExecutor(mTestHandler); - mStartingSurfaceDrawer = new StartingSurfaceDrawer(mTestContext, mTestExecutor, - mIconProvider, mTransactionPool); mStartingSurfaceDrawer = spy( - new StartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider, + new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider, mTransactionPool)); - spyOn(mStartingSurfaceDrawer.mSplashscreenWindowCreator); - spyOn(mStartingSurfaceDrawer.mWindowRecords); - spyOn(mStartingSurfaceDrawer.mWindowlessRecords); } @Test public void testAddSplashScreenSurface() { final int taskId = 1; final StartingWindowInfo windowInfo = - createWindowInfo(taskId, android.R.style.Theme, mBinder); - mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, + createWindowInfo(taskId, android.R.style.Theme); + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, STARTING_WINDOW_TYPE_SPLASH_SCREEN); waitHandlerIdle(mTestHandler); - verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator).addWindow( - eq(taskId), eq(mBinder), any(), any(), any(), + verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(), eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN)); + assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId); StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo(); removalInfo.taskId = windowInfo.taskInfo.taskId; mStartingSurfaceDrawer.removeStartingWindow(removalInfo); waitHandlerIdle(mTestHandler); - verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(false)); - assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0); + verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(false)); + assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0); } @Test public void testFallbackDefaultTheme() { final int taskId = 1; final StartingWindowInfo windowInfo = - createWindowInfo(taskId, 0, mBinder); + createWindowInfo(taskId, 0); final int[] theme = new int[1]; doAnswer(invocation -> theme[0] = (Integer) invocation.callRealMethod()) - .when(mStartingSurfaceDrawer.mSplashscreenWindowCreator) - .getSplashScreenTheme(eq(0), any()); + .when(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any()); - mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, STARTING_WINDOW_TYPE_SPLASH_SCREEN); waitHandlerIdle(mTestHandler); - verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator) - .getSplashScreenTheme(eq(0), any()); + verify(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any()); assertNotEquals(theme[0], 0); } @@ -217,7 +241,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception { final int taskId = 1; final StartingWindowInfo windowInfo = - createWindowInfo(taskId, android.R.style.Theme, mBinder); + createWindowInfo(taskId, android.R.style.Theme); TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100), new Rect(0, 0, 0, 50), true /* hasImeSurface */); final IWindowSession session = WindowManagerGlobal.getWindowSession(); @@ -246,7 +270,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(), any())).thenReturn(mockSnapshotWindow); // Simulate a task snapshot window created with IME snapshot shown. - mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot); + mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot); waitHandlerIdle(mTestHandler); // Verify the task snapshot with IME snapshot will be removed when received the real IME @@ -254,36 +278,27 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { // makeTaskSnapshotWindow shall call removeWindowSynced before there add a new // StartingWindowRecord for the task. mStartingSurfaceDrawer.onImeDrawnOnTask(1); - verify(mStartingSurfaceDrawer.mWindowRecords, times(2)) - .removeWindow(any(), eq(true)); + verify(mStartingSurfaceDrawer, times(2)) + .removeWindowSynced(any(), eq(true)); } } @Test public void testClearAllWindows() { final int taskId = 1; - mStartingSurfaceDrawer.mWindowRecords.addRecord(taskId, - new StartingSurfaceDrawer.StartingWindowRecord() { - @Override - public void removeIfPossible(StartingWindowRemovalInfo info, - boolean immediately) { - - } - }); - mStartingSurfaceDrawer.mWindowlessRecords.addRecord(taskId, - new StartingSurfaceDrawer.StartingWindowRecord() { - @Override - public void removeIfPossible(StartingWindowRemovalInfo info, - boolean immediately) { + final StartingWindowInfo windowInfo = + createWindowInfo(taskId, android.R.style.Theme); + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, + STARTING_WINDOW_TYPE_SPLASH_SCREEN); + waitHandlerIdle(mTestHandler); + verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(), + eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN)); + assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId); - } - }); mStartingSurfaceDrawer.clearAllWindows(); waitHandlerIdle(mTestHandler); - verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(true)); - assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0); - verify(mStartingSurfaceDrawer.mWindowlessRecords).removeWindow(any(), eq(true)); - assertEquals(mStartingSurfaceDrawer.mWindowlessRecords.recordSize(), 0); + verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(true)); + assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0); } @Test @@ -336,7 +351,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { longAppDuration, longAppDuration)); } - private StartingWindowInfo createWindowInfo(int taskId, int themeResId, IBinder appToken) { + private StartingWindowInfo createWindowInfo(int taskId, int themeResId) { StartingWindowInfo windowInfo = new StartingWindowInfo(); final ActivityInfo info = new ActivityInfo(); info.applicationInfo = new ApplicationInfo(); @@ -345,7 +360,6 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.topActivityInfo = info; taskInfo.taskId = taskId; - windowInfo.appToken = appToken; windowInfo.targetActivityInfo = info; windowInfo.taskInfo = taskInfo; windowInfo.topOpaqueWindowInsetsState = new InsetsState(); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index bcbe706d71a3..536bb49675f1 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -332,6 +332,7 @@ cc_defaults { "jni/android_graphics_Matrix.cpp", "jni/android_graphics_Picture.cpp", "jni/android_graphics_DisplayListCanvas.cpp", + "jni/android_graphics_Mesh.cpp", "jni/android_graphics_RenderNode.cpp", "jni/android_nio_utils.cpp", "jni/android_util_PathParser.cpp", @@ -351,7 +352,6 @@ cc_defaults { "jni/ImageDecoder.cpp", "jni/Interpolator.cpp", "jni/MeshSpecification.cpp", - "jni/Mesh.cpp", "jni/MaskFilter.cpp", "jni/NinePatch.cpp", "jni/NinePatchPeeker.cpp", @@ -538,6 +538,7 @@ cc_defaults { "Interpolator.cpp", "LightingInfo.cpp", "Matrix.cpp", + "Mesh.cpp", "MemoryPolicy.cpp", "PathParser.cpp", "Properties.cpp", diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in index e2127efca716..a18ba1c633b9 100644 --- a/libs/hwui/DisplayListOps.in +++ b/libs/hwui/DisplayListOps.in @@ -52,4 +52,5 @@ X(DrawShadowRec) X(DrawVectorDrawable) X(DrawRippleDrawable) X(DrawWebView) -X(DrawMesh) +X(DrawSkMesh) +X(DrawMesh)
\ No newline at end of file diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h index 2f0f7f506447..41ced8cebf83 100644 --- a/libs/hwui/MemoryPolicy.h +++ b/libs/hwui/MemoryPolicy.h @@ -53,8 +53,8 @@ struct MemoryPolicy { // Whether or not to only purge scratch resources when triggering UI Hidden or background // collection bool purgeScratchOnly = true; - // Whether or not to trigger releasing GPU context when all contexts are stopped - bool releaseContextOnStoppedOnly = true; + // EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped + bool releaseContextOnStoppedOnly = false; }; const MemoryPolicy& loadMemoryPolicy(); diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp new file mode 100644 index 000000000000..e59bc9565a59 --- /dev/null +++ b/libs/hwui/Mesh.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Mesh.h" + +#include <GLES/gl.h> +#include <SkMesh.h> + +#include "SafeMath.h" + +static size_t min_vcount_for_mode(SkMesh::Mode mode) { + switch (mode) { + case SkMesh::Mode::kTriangles: + return 3; + case SkMesh::Mode::kTriangleStrip: + return 3; + } +} + +// Re-implementation of SkMesh::validate to validate user side that their mesh is valid. +std::tuple<bool, SkString> Mesh::validate() { +#define FAIL_MESH_VALIDATE(...) return std::make_tuple(false, SkStringPrintf(__VA_ARGS__)) + if (!mMeshSpec) { + FAIL_MESH_VALIDATE("MeshSpecification is required."); + } + if (mVertexBufferData.empty()) { + FAIL_MESH_VALIDATE("VertexBuffer is required."); + } + + auto meshStride = mMeshSpec->stride(); + auto meshMode = SkMesh::Mode(mMode); + SafeMath sm; + size_t vsize = sm.mul(meshStride, mVertexCount); + if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) { + FAIL_MESH_VALIDATE( + "The vertex buffer offset and vertex count reads beyond the end of the" + " vertex buffer."); + } + + if (mVertexOffset % meshStride != 0) { + FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).", + mVertexOffset, meshStride); + } + + if (size_t uniformSize = mMeshSpec->uniformSize()) { + if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) { + FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.", + mBuilder->fUniforms->size(), uniformSize); + } + } + + auto modeToStr = [](SkMesh::Mode m) { + switch (m) { + case SkMesh::Mode::kTriangles: + return "triangles"; + case SkMesh::Mode::kTriangleStrip: + return "triangle-strip"; + } + }; + if (!mIndexBufferData.empty()) { + if (mIndexCount < min_vcount_for_mode(meshMode)) { + FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.", + modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount); + } + size_t isize = sm.mul(sizeof(uint16_t), mIndexCount); + if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) { + FAIL_MESH_VALIDATE( + "The index buffer offset and index count reads beyond the end of the" + " index buffer."); + } + // If we allow 32 bit indices then this should enforce 4 byte alignment in that case. + if (!SkIsAlign2(mIndexOffset)) { + FAIL_MESH_VALIDATE("The index offset must be a multiple of 2."); + } + } else { + if (mVertexCount < min_vcount_for_mode(meshMode)) { + FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.", + modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount); + } + SkASSERT(!fICount); + SkASSERT(!fIOffset); + } + + if (!sm.ok()) { + FAIL_MESH_VALIDATE("Overflow"); + } +#undef FAIL_MESH_VALIDATE + return {true, {}}; +} diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h new file mode 100644 index 000000000000..983681707415 --- /dev/null +++ b/libs/hwui/Mesh.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MESH_H_ +#define MESH_H_ + +#include <GrDirectContext.h> +#include <SkMesh.h> +#include <jni.h> +#include <log/log.h> + +#include <utility> + +class MeshUniformBuilder { +public: + struct MeshUniform { + template <typename T> + std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=( + const T& val) { + if (!fVar) { + LOG_FATAL("Assigning to missing variable"); + } else if (sizeof(val) != fVar->sizeInBytes()) { + LOG_FATAL("Incorrect value size"); + } else { + void* dst = reinterpret_cast<void*>( + reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset); + memcpy(dst, &val, sizeof(val)); + } + } + + MeshUniform& operator=(const SkMatrix& val) { + if (!fVar) { + LOG_FATAL("Assigning to missing variable"); + } else if (fVar->sizeInBytes() != 9 * sizeof(float)) { + LOG_FATAL("Incorrect value size"); + } else { + float* data = reinterpret_cast<float*>( + reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset); + data[0] = val.get(0); + data[1] = val.get(3); + data[2] = val.get(6); + data[3] = val.get(1); + data[4] = val.get(4); + data[5] = val.get(7); + data[6] = val.get(2); + data[7] = val.get(5); + data[8] = val.get(8); + } + return *this; + } + + template <typename T> + bool set(const T val[], const int count) { + static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable"); + if (!fVar) { + LOG_FATAL("Assigning to missing variable"); + return false; + } else if (sizeof(T) * count != fVar->sizeInBytes()) { + LOG_FATAL("Incorrect value size"); + return false; + } else { + void* dst = reinterpret_cast<void*>( + reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset); + memcpy(dst, val, sizeof(T) * count); + } + return true; + } + + MeshUniformBuilder* fOwner; + const SkRuntimeEffect::Uniform* fVar; + }; + MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; } + + explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) { + fMeshSpec = sk_sp(meshSpec); + fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize())); + } + + sk_sp<SkData> fUniforms; + +private: + void* writableUniformData() { + if (!fUniforms->unique()) { + fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size()); + } + return fUniforms->writable_data(); + } + + sk_sp<SkMeshSpecification> fMeshSpec; +}; + +class Mesh { +public: + Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer, + size_t vertexBufferSize, jint vertexCount, jint vertexOffset, + std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds) + : mMeshSpec(meshSpec) + , mMode(mode) + , mVertexCount(vertexCount) + , mVertexOffset(vertexOffset) + , mBuilder(std::move(builder)) + , mBounds(bounds) { + copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize); + } + + Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer, + size_t vertexBufferSize, jint vertexCount, jint vertexOffset, const void* indexBuffer, + size_t indexBufferSize, jint indexCount, jint indexOffset, + std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds) + : mMeshSpec(meshSpec) + , mMode(mode) + , mVertexCount(vertexCount) + , mVertexOffset(vertexOffset) + , mIndexCount(indexCount) + , mIndexOffset(indexOffset) + , mBuilder(std::move(builder)) + , mBounds(bounds) { + copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize); + copyToVector(mIndexBufferData, indexBuffer, indexBufferSize); + } + + Mesh(Mesh&&) = default; + + Mesh& operator=(Mesh&&) = default; + + [[nodiscard]] std::tuple<bool, SkString> validate(); + + void updateSkMesh(GrDirectContext* context) const { + GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID(); + if (context) { + genId = context->directContextID(); + } + + if (mIsDirty || genId != mGenerationId) { + auto vb = SkMesh::MakeVertexBuffer( + context, reinterpret_cast<const void*>(mVertexBufferData.data()), + mVertexBufferData.size()); + auto meshMode = SkMesh::Mode(mMode); + if (!mIndexBufferData.empty()) { + auto ib = SkMesh::MakeIndexBuffer( + context, reinterpret_cast<const void*>(mIndexBufferData.data()), + mIndexBufferData.size()); + mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset, + ib, mIndexCount, mIndexOffset, mBuilder->fUniforms, + mBounds) + .mesh; + } else { + mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset, + mBuilder->fUniforms, mBounds) + .mesh; + } + mIsDirty = false; + mGenerationId = genId; + } + } + + SkMesh& getSkMesh() const { + LOG_FATAL_IF(mIsDirty, + "Attempt to obtain SkMesh when Mesh is dirty, did you " + "forget to call updateSkMesh with a GrDirectContext? " + "Defensively creating a CPU mesh"); + return mMesh; + } + + void markDirty() { mIsDirty = true; } + + MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); } + +private: + void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) { + if (src) { + dst.resize(srcSize); + memcpy(dst.data(), src, srcSize); + } + } + + sk_sp<SkMeshSpecification> mMeshSpec; + int mMode = 0; + + std::vector<uint8_t> mVertexBufferData; + size_t mVertexCount = 0; + size_t mVertexOffset = 0; + + std::vector<uint8_t> mIndexBufferData; + size_t mIndexCount = 0; + size_t mIndexOffset = 0; + + std::unique_ptr<MeshUniformBuilder> mBuilder; + SkRect mBounds{}; + + mutable SkMesh mMesh{}; + mutable bool mIsDirty = true; + mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID(); +}; +#endif // MESH_H_ diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 659aec0fdf58..0b58406516e3 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -24,6 +24,7 @@ #include <experimental/type_traits> #include <utility> +#include "Mesh.h" #include "SkAndroidFrameworkUtils.h" #include "SkBlendMode.h" #include "SkCanvas.h" @@ -502,14 +503,14 @@ struct DrawVertices final : Op { c->drawVertices(vertices, mode, paint); } }; -struct DrawMesh final : Op { - static const auto kType = Type::DrawMesh; - DrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) +struct DrawSkMesh final : Op { + static const auto kType = Type::DrawSkMesh; + DrawSkMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) : cpuMesh(mesh), blender(std::move(blender)), paint(paint) { isGpuBased = false; } - SkMesh cpuMesh; + const SkMesh& cpuMesh; mutable SkMesh gpuMesh; sk_sp<SkBlender> blender; SkPaint paint; @@ -517,6 +518,7 @@ struct DrawMesh final : Op { mutable GrDirectContext::DirectContextID contextId; void draw(SkCanvas* c, const SkMatrix&) const { GrDirectContext* directContext = c->recordingContext()->asDirectContext(); + GrDirectContext::DirectContextID id = directContext->directContextID(); if (!isGpuBased || contextId != id) { sk_sp<SkMesh::VertexBuffer> vb = @@ -543,6 +545,18 @@ struct DrawMesh final : Op { c->drawMesh(gpuMesh, blender, paint); } }; + +struct DrawMesh final : Op { + static const auto kType = Type::DrawMesh; + DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) + : mesh(mesh), blender(std::move(blender)), paint(paint) {} + + const Mesh& mesh; + sk_sp<SkBlender> blender; + SkPaint paint; + + void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh.getSkMesh(), blender, paint); } +}; struct DrawAtlas final : Op { static const auto kType = Type::DrawAtlas; DrawAtlas(const SkImage* atlas, int count, SkBlendMode mode, const SkSamplingOptions& sampling, @@ -859,6 +873,10 @@ void DisplayListData::drawVertices(const SkVertices* vert, SkBlendMode mode, con } void DisplayListData::drawMesh(const SkMesh& mesh, const sk_sp<SkBlender>& blender, const SkPaint& paint) { + this->push<DrawSkMesh>(0, mesh, blender, paint); +} +void DisplayListData::drawMesh(const Mesh& mesh, const sk_sp<SkBlender>& blender, + const SkPaint& paint) { this->push<DrawMesh>(0, mesh, blender, paint); } void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[], @@ -1205,6 +1223,9 @@ void RecordingCanvas::onDrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) { fDL->drawMesh(mesh, blender, paint); } +void RecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) { + fDL->drawMesh(mesh, blender, paint); +} void RecordingCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[], const SkColor colors[], int count, SkBlendMode bmode, const SkSamplingOptions& sampling, @@ -1223,5 +1244,14 @@ void RecordingCanvas::drawWebView(skiapipeline::FunctorDrawable* drawable) { fDL->drawWebView(drawable); } +[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const { + LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null"); + if (meshWrapper) { + return meshWrapper->getSkMesh(); + } else { + return *mesh; + } +} + } // namespace uirenderer } // namespace android diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 8409e136b57b..1f4ba5d6d557 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -28,6 +28,7 @@ #include <log/log.h> #include <cstdlib> +#include <utility> #include <vector> #include "CanvasTransform.h" @@ -40,6 +41,7 @@ enum class SkBlendMode; class SkRRect; +class Mesh; namespace android { namespace uirenderer { @@ -66,6 +68,18 @@ struct DisplayListOp { static_assert(sizeof(DisplayListOp) == 4); +class DrawMeshPayload { +public: + explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {} + explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {} + + [[nodiscard]] const SkMesh& getSkMesh() const; + +private: + const SkMesh* mesh = nullptr; + const Mesh* meshWrapper = nullptr; +}; + struct DrawImagePayload { explicit DrawImagePayload(Bitmap& bitmap) : image(bitmap.makeImage()), palette(bitmap.palette()) { @@ -143,6 +157,7 @@ private: void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&); void drawMesh(const SkMesh&, const sk_sp<SkBlender>&, const SkPaint&); + void drawMesh(const Mesh&, const sk_sp<SkBlender>&, const SkPaint&); void drawAnnotation(const SkRect&, const char*, SkData*); void drawDrawable(SkDrawable*, const SkMatrix*); @@ -247,6 +262,7 @@ public: SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override; void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override; + void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint); void drawVectorDrawable(VectorDrawableRoot* tree); void drawWebView(skiapipeline::FunctorDrawable*); diff --git a/libs/hwui/SafeMath.h b/libs/hwui/SafeMath.h new file mode 100644 index 000000000000..4d6adf55c0cb --- /dev/null +++ b/libs/hwui/SafeMath.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SkSafeMath_DEFINED +#define SkSafeMath_DEFINED + +#include <cstddef> +#include <cstdint> +#include <limits> + +// Copy of Skia's SafeMath API used to validate Mesh parameters to support +// deferred creation of SkMesh instances on RenderThread. +// SafeMath always check that a series of operations do not overflow. +// This must be correct for all platforms, because this is a check for safety at runtime. + +class SafeMath { +public: + SafeMath() = default; + + bool ok() const { return fOK; } + explicit operator bool() const { return fOK; } + + size_t mul(size_t x, size_t y) { + return sizeof(size_t) == sizeof(uint64_t) ? mul64(x, y) : mul32(x, y); + } + + size_t add(size_t x, size_t y) { + size_t result = x + y; + fOK &= result >= x; + return result; + } + + /** + * Return a + b, unless this result is an overflow/underflow. In those cases, fOK will + * be set to false, and it is undefined what this returns. + */ + int addInt(int a, int b) { + if (b < 0 && a < std::numeric_limits<int>::min() - b) { + fOK = false; + return a; + } else if (b > 0 && a > std::numeric_limits<int>::max() - b) { + fOK = false; + return a; + } + return a + b; + } + + // These saturate to their results + static size_t Add(size_t x, size_t y) { + SafeMath tmp; + size_t sum = tmp.add(x, y); + return tmp.ok() ? sum : SIZE_MAX; + } + + static size_t Mul(size_t x, size_t y) { + SafeMath tmp; + size_t prod = tmp.mul(x, y); + return tmp.ok() ? prod : SIZE_MAX; + } + +private: + uint32_t mul32(uint32_t x, uint32_t y) { + uint64_t bx = x; + uint64_t by = y; + uint64_t result = bx * by; + fOK &= result >> 32 == 0; + // Overflow information is capture in fOK. Return the result modulo 2^32. + return (uint32_t)result; + } + + uint64_t mul64(uint64_t x, uint64_t y) { + if (x <= std::numeric_limits<uint64_t>::max() >> 32 && + y <= std::numeric_limits<uint64_t>::max() >> 32) { + return x * y; + } else { + auto hi = [](uint64_t x) { return x >> 32; }; + auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; }; + + uint64_t lx_ly = lo(x) * lo(y); + uint64_t hx_ly = hi(x) * lo(y); + uint64_t lx_hy = lo(x) * hi(y); + uint64_t hx_hy = hi(x) * hi(y); + uint64_t result = 0; + result = this->add(lx_ly, (hx_ly << 32)); + result = this->add(result, (lx_hy << 32)); + fOK &= (hx_hy + (hx_ly >> 32) + (lx_hy >> 32)) == 0; + + return result; + } + } + bool fOK = true; +}; + +#endif // SkSafeMath_DEFINED diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index d0124f5d4bad..7a1276982d0a 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -39,21 +39,22 @@ #include <SkShader.h> #include <SkTextBlob.h> #include <SkVertices.h> +#include <log/log.h> +#include <ui/FatVector.h> #include <memory> #include <optional> #include <utility> #include "CanvasProperty.h" +#include "Mesh.h" #include "NinePatchUtils.h" #include "VectorDrawable.h" #include "hwui/Bitmap.h" #include "hwui/MinikinUtils.h" #include "hwui/PaintFilter.h" -#include <log/log.h> #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/HolePunch.h" -#include <ui/FatVector.h> namespace android { @@ -572,8 +573,14 @@ void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, cons applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); }); } -void SkiaCanvas::drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) { - mCanvas->drawMesh(mesh, blender, paint); +void SkiaCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) { + GrDirectContext* context = nullptr; + auto recordingContext = mCanvas->recordingContext(); + if (recordingContext) { + context = recordingContext->asDirectContext(); + } + mesh.updateSkMesh(context); + mCanvas->drawMesh(mesh.getSkMesh(), blender, paint); } // ---------------------------------------------------------------------------- diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index f2c286a4fb46..b785989f35cb 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -129,8 +129,7 @@ public: float sweepAngle, bool useCenter, const Paint& paint) override; virtual void drawPath(const SkPath& path, const Paint& paint) override; virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) override; - virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, - const SkPaint& paint) override; + virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override; virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override; virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override; diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 2a2019199bda..44ee31d34d23 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -16,25 +16,25 @@ #pragma once +#include <SaveFlags.h> +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkMatrix.h> +#include <androidfw/ResourceTypes.h> #include <cutils/compiler.h> #include <utils/Functor.h> -#include <SaveFlags.h> -#include <androidfw/ResourceTypes.h> #include "Properties.h" #include "pipeline/skia/AnimatedDrawables.h" #include "utils/Macros.h" -#include <SkBitmap.h> -#include <SkCanvas.h> -#include <SkMatrix.h> - class SkAnimatedImage; enum class SkBlendMode; class SkCanvasState; class SkRRect; class SkRuntimeShaderBuilder; class SkVertices; +class Mesh; namespace minikin { class Font; @@ -227,7 +227,7 @@ public: float sweepAngle, bool useCenter, const Paint& paint) = 0; virtual void drawPath(const SkPath& path, const Paint& paint) = 0; virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) = 0; - virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender>, const SkPaint& paint) = 0; + virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender>, const Paint& paint) = 0; // Bitmap-based virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) = 0; diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h index 6b983c10bdca..24f9e82b5340 100644 --- a/libs/hwui/jni/GraphicsJNI.h +++ b/libs/hwui/jni/GraphicsJNI.h @@ -46,10 +46,16 @@ public: static void setJavaVM(JavaVM* javaVM); - /** returns a pointer to the JavaVM provided when we initialized the module */ + /** + * returns a pointer to the JavaVM provided when we initialized the module + * DEPRECATED: Objects should know the JavaVM that created them + */ static JavaVM* getJavaVM() { return mJavaVM; } - /** return a pointer to the JNIEnv for this thread */ + /** + * return a pointer to the JNIEnv for this thread + * DEPRECATED: Objects should know the JavaVM that created them + */ static JNIEnv* getJNIEnv(); /** create a JNIEnv* for this thread or assert if one already exists */ @@ -337,13 +343,21 @@ public: JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} virtual ~JGlobalRefHolder() { - GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); + env()->DeleteGlobalRef(mObject); mObject = nullptr; } jobject object() { return mObject; } JavaVM* vm() { return mVm; } + JNIEnv* env() { + JNIEnv* env; + if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm); + } + return env; + } + private: JGlobalRefHolder(const JGlobalRefHolder&) = delete; void operator=(const JGlobalRefHolder&) = delete; diff --git a/libs/hwui/jni/Interpolator.cpp b/libs/hwui/jni/Interpolator.cpp index fc3d70b87f5a..c71e3085caf5 100644 --- a/libs/hwui/jni/Interpolator.cpp +++ b/libs/hwui/jni/Interpolator.cpp @@ -24,12 +24,8 @@ static void Interpolator_setKeyFrame(JNIEnv* env, jobject clazz, jlong interpHan AutoJavaFloatArray autoValues(env, valueArray); AutoJavaFloatArray autoBlend(env, blendArray, 4); -#ifdef SK_SCALAR_IS_FLOAT SkScalar* scalars = autoValues.ptr(); SkScalar* blend = autoBlend.ptr(); -#else - #error Need to convert float array to SkScalar array before calling the following function. -#endif interp->setKeyFrame(index, msec, scalars, blend); } diff --git a/libs/hwui/jni/JvmErrorReporter.h b/libs/hwui/jni/JvmErrorReporter.h index 5e10b9d93275..3a3587572a1a 100644 --- a/libs/hwui/jni/JvmErrorReporter.h +++ b/libs/hwui/jni/JvmErrorReporter.h @@ -30,7 +30,10 @@ public: JvmErrorReporter(JNIEnv* env) { env->GetJavaVM(&mVm); } virtual void onError(const std::string& message) override { - JNIEnv* env = GraphicsJNI::getJNIEnv(); + JNIEnv* env; + if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm); + } jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); } diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp deleted file mode 100644 index b13d9bafb417..000000000000 --- a/libs/hwui/jni/Mesh.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <GLES/gl.h> -#include <Mesh.h> -#include <SkMesh.h> - -#include "GraphicsJNI.h" -#include "graphics_jni_helpers.h" - -namespace android { - -sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size, - jboolean isDirect) { - auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect); - auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size); - return vertexBuffer; -} - -sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size, - jboolean isDirect) { - auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect); - auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size); - return indexBuffer; -} - -static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, - jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top, - jfloat right, jfloat bottom) { - auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec)); - sk_sp<SkMesh::VertexBuffer> skVertexBuffer = - genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect); - auto skRect = SkRect::MakeLTRB(left, top, right, bottom); - auto meshResult = SkMesh::Make( - skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset, - SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect); - - if (!meshResult.error.isEmpty()) { - jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str()); - } - - auto meshPtr = std::make_unique<MeshWrapper>( - MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)}); - return reinterpret_cast<jlong>(meshPtr.release()); -} - -static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, - jboolean isVertexDirect, jint vertexCount, jint vertexOffset, - jobject indexBuffer, jboolean isIndexDirect, jint indexCount, - jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) { - auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec)); - sk_sp<SkMesh::VertexBuffer> skVertexBuffer = - genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isVertexDirect); - sk_sp<SkMesh::IndexBuffer> skIndexBuffer = - genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect); - auto skRect = SkRect::MakeLTRB(left, top, right, bottom); - - auto meshResult = SkMesh::MakeIndexed( - skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset, - skIndexBuffer, indexCount, indexOffset, - SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect); - - if (!meshResult.error.isEmpty()) { - jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str()); - } - auto meshPtr = std::make_unique<MeshWrapper>( - MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)}); - return reinterpret_cast<jlong>(meshPtr.release()); -} - -static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) { - auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); - auto mesh = wrapper->mesh; - if (indexed) { - wrapper->mesh = SkMesh::MakeIndexed(sk_ref_sp(mesh.spec()), mesh.mode(), - sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(), - mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()), - mesh.indexCount(), mesh.indexOffset(), - wrapper->builder.fUniforms, mesh.bounds()) - .mesh; - } else { - wrapper->mesh = SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(), - sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(), - mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds()) - .mesh; - } -} - -static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) { - va_list args; - va_start(args, fmt); - int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args); - va_end(args); - return ret; -} - -static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) { - switch (type) { - case SkRuntimeEffect::Uniform::Type::kFloat: - case SkRuntimeEffect::Uniform::Type::kFloat2: - case SkRuntimeEffect::Uniform::Type::kFloat3: - case SkRuntimeEffect::Uniform::Type::kFloat4: - case SkRuntimeEffect::Uniform::Type::kFloat2x2: - case SkRuntimeEffect::Uniform::Type::kFloat3x3: - case SkRuntimeEffect::Uniform::Type::kFloat4x4: - return false; - case SkRuntimeEffect::Uniform::Type::kInt: - case SkRuntimeEffect::Uniform::Type::kInt2: - case SkRuntimeEffect::Uniform::Type::kInt3: - case SkRuntimeEffect::Uniform::Type::kInt4: - return true; - } -} - -static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder, - const char* uniformName, const float values[], int count, - bool isColor) { - MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName); - if (uniform.fVar == nullptr) { - ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); - } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) { - if (isColor) { - jniThrowExceptionFmt( - env, "java/lang/IllegalArgumentException", - "attempting to set a color uniform using the non-color specific APIs: %s %x", - uniformName, uniform.fVar->flags); - } else { - ThrowIAEFmt(env, - "attempting to set a non-color uniform using the setColorUniform APIs: %s", - uniformName); - } - } else if (isIntUniformType(uniform.fVar->type)) { - ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s", - uniformName); - } else if (!uniform.set<float>(values, count)) { - ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", - uniform.fVar->sizeInBytes(), sizeof(float) * count); - } -} - -static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, - jfloat value1, jfloat value2, jfloat value3, jfloat value4, - jint count) { - auto* wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); - ScopedUtfChars name(env, uniformName); - const float values[4] = {value1, value2, value3, value4}; - nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false); -} - -static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName, - jfloatArray jvalues, jboolean isColor) { - auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); - ScopedUtfChars name(env, jUniformName); - AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess); - nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(), - autoValues.length(), isColor); -} - -static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder, - const char* uniformName, const int values[], int count) { - MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName); - if (uniform.fVar == nullptr) { - ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); - } else if (!isIntUniformType(uniform.fVar->type)) { - ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s", - uniformName); - } else if (!uniform.set<int>(values, count)) { - ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", - uniform.fVar->sizeInBytes(), sizeof(float) * count); - } -} - -static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, - jint value1, jint value2, jint value3, jint value4, jint count) { - auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); - ScopedUtfChars name(env, uniformName); - const int values[4] = {value1, value2, value3, value4}; - nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count); -} - -static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, - jintArray values) { - auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); - ScopedUtfChars name(env, uniformName); - AutoJavaIntArray autoValues(env, values, 0); - nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(), - autoValues.length()); -} - -static void MeshWrapper_destroy(MeshWrapper* wrapper) { - delete wrapper; -} - -static jlong getMeshFinalizer(JNIEnv*, jobject) { - return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy)); -} - -static const JNINativeMethod gMeshMethods[] = { - {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer}, - {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make}, - {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J", - (void*)makeIndexed}, - {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh}, - {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms}, - {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms}, - {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms}, - {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}}; - -int register_android_graphics_Mesh(JNIEnv* env) { - android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods)); - return 0; -} - -} // namespace android diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h deleted file mode 100644 index 61c2260e3ad1..000000000000 --- a/libs/hwui/jni/Mesh.h +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_ -#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_ - -#include <SkMesh.h> -#include <jni.h> - -#include <log/log.h> -#include <utility> - -#include "graphics_jni_helpers.h" - -#define gIndexByteSize 2 - -// A smart pointer that provides read only access to Java.nio.Buffer. This handles both -// direct and indrect buffers, allowing access to the underlying data in both -// situations. If passed a null buffer, we will throw NullPointerException, -// and c_data will return nullptr. -// -// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void * -// conversion. -class ScopedJavaNioBuffer { -public: - ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect) - : mEnv(env), mBuffer(buffer) { - if (buffer == nullptr) { - mDataBase = nullptr; - mData = nullptr; - jniThrowNullPointerException(env); - } else { - mArray = (jarray) nullptr; - if (isDirect) { - mData = getDirectBufferPointer(mEnv, mBuffer); - } else { - mData = setIndirectData(size); - } - } - } - - ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); } - - ~ScopedJavaNioBuffer() { reset(); } - - void reset() { - if (mDataBase) { - releasePointer(mEnv, mArray, mDataBase, JNI_FALSE); - mDataBase = nullptr; - } - } - - ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept { - if (this != &rhs) { - reset(); - - mEnv = rhs.mEnv; - mBuffer = rhs.mBuffer; - mDataBase = rhs.mDataBase; - mData = rhs.mData; - mArray = rhs.mArray; - rhs.mEnv = nullptr; - rhs.mData = nullptr; - rhs.mBuffer = nullptr; - rhs.mArray = nullptr; - rhs.mDataBase = nullptr; - } - return *this; - } - - const void* data() const { return mData; } - -private: - /** - * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data - * from a java.nio.Buffer. - */ - void* getDirectBufferPointer(JNIEnv* env, jobject buffer) { - if (buffer == nullptr) { - return nullptr; - } - - jint position; - jint limit; - jint elementSizeShift; - jlong pointer; - pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift); - if (pointer == 0) { - jniThrowException(mEnv, "java/lang/IllegalArgumentException", - "Must use a native order direct Buffer"); - return nullptr; - } - pointer += position << elementSizeShift; - return reinterpret_cast<void*>(pointer); - } - - static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) { - env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT); - } - - static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining, - jint* offset) { - jint position; - jint limit; - jint elementSizeShift; - - jlong pointer; - pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift); - *remaining = (limit - position) << elementSizeShift; - if (pointer != 0L) { - *array = nullptr; - pointer += position << elementSizeShift; - return reinterpret_cast<void*>(pointer); - } - - *array = jniGetNioBufferBaseArray(env, buffer); - *offset = jniGetNioBufferBaseArrayOffset(env, buffer); - return nullptr; - } - - /** - * This is a copy of - * static void android_glBufferData__IILjava_nio_Buffer_2I - * from com_google_android_gles_jni_GLImpl.cpp - */ - void* setIndirectData(jint size) { - jint exception; - const char* exceptionType; - const char* exceptionMessage; - jint bufferOffset = (jint)0; - jint remaining; - void* tempData; - - if (mBuffer) { - tempData = - (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset); - if (remaining < size) { - exception = 1; - exceptionType = "java/lang/IllegalArgumentException"; - exceptionMessage = "remaining() < size < needed"; - goto exit; - } - } - if (mBuffer && tempData == nullptr) { - mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0); - tempData = (void*)(mDataBase + bufferOffset); - } - return tempData; - exit: - if (mArray) { - releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE); - } - if (exception) { - jniThrowException(mEnv, exceptionType, exceptionMessage); - } - return nullptr; - } - - JNIEnv* mEnv; - - // Java Buffer data - void* mData; - jobject mBuffer; - - // Indirect Buffer Data - jarray mArray; - char* mDataBase; -}; - -class MeshUniformBuilder { -public: - struct MeshUniform { - template <typename T> - std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=( - const T& val) { - if (!fVar) { - LOG_FATAL("Assigning to missing variable"); - } else if (sizeof(val) != fVar->sizeInBytes()) { - LOG_FATAL("Incorrect value size"); - } else { - void* dst = reinterpret_cast<void*>( - reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset); - memcpy(dst, &val, sizeof(val)); - } - } - - MeshUniform& operator=(const SkMatrix& val) { - if (!fVar) { - LOG_FATAL("Assigning to missing variable"); - } else if (fVar->sizeInBytes() != 9 * sizeof(float)) { - LOG_FATAL("Incorrect value size"); - } else { - float* data = reinterpret_cast<float*>( - reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset); - data[0] = val.get(0); - data[1] = val.get(3); - data[2] = val.get(6); - data[3] = val.get(1); - data[4] = val.get(4); - data[5] = val.get(7); - data[6] = val.get(2); - data[7] = val.get(5); - data[8] = val.get(8); - } - return *this; - } - - template <typename T> - bool set(const T val[], const int count) { - static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable"); - if (!fVar) { - LOG_FATAL("Assigning to missing variable"); - return false; - } else if (sizeof(T) * count != fVar->sizeInBytes()) { - LOG_FATAL("Incorrect value size"); - return false; - } else { - void* dst = reinterpret_cast<void*>( - reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset); - memcpy(dst, val, sizeof(T) * count); - } - return true; - } - - MeshUniformBuilder* fOwner; - const SkRuntimeEffect::Uniform* fVar; - }; - MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; } - - explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) { - fMeshSpec = sk_sp(meshSpec); - fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize())); - } - - sk_sp<SkData> fUniforms; - -private: - void* writableUniformData() { - if (!fUniforms->unique()) { - fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size()); - } - return fUniforms->writable_data(); - } - - sk_sp<SkMeshSpecification> fMeshSpec; -}; - -struct MeshWrapper { - SkMesh mesh; - MeshUniformBuilder builder; -}; -#endif // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_ diff --git a/libs/hwui/jni/Path.cpp b/libs/hwui/jni/Path.cpp index 3694ce07b972..a5e04763d885 100644 --- a/libs/hwui/jni/Path.cpp +++ b/libs/hwui/jni/Path.cpp @@ -182,11 +182,7 @@ public: SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPathDirection dir = static_cast<SkPathDirection>(dirHandle); AutoJavaFloatArray afa(env, array, 8); -#ifdef SK_SCALAR_IS_FLOAT const float* src = afa.ptr(); -#else - #error Need to convert float array to SkScalar array before calling the following function. -#endif obj->addRoundRect(rect, src, dir); } diff --git a/libs/hwui/jni/PathEffect.cpp b/libs/hwui/jni/PathEffect.cpp index f99bef7b7d58..3dbe1a67f52e 100644 --- a/libs/hwui/jni/PathEffect.cpp +++ b/libs/hwui/jni/PathEffect.cpp @@ -35,11 +35,7 @@ public: jfloatArray intervalArray, jfloat phase) { AutoJavaFloatArray autoInterval(env, intervalArray); int count = autoInterval.length() & ~1; // even number -#ifdef SK_SCALAR_IS_FLOAT - SkScalar* intervals = autoInterval.ptr(); -#else - #error Need to convert float array to SkScalar array before calling the following function. -#endif + SkScalar* intervals = autoInterval.ptr(); SkPathEffect* effect = SkDashPathEffect::Make(intervals, count, phase).release(); return reinterpret_cast<jlong>(effect); } diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index 8a0db1c91d46..75d45e5bd8aa 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -52,12 +52,7 @@ static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray) { AutoJavaFloatArray autoHSV(env, hsvArray, 3); -#ifdef SK_SCALAR_IS_FLOAT - SkScalar* hsv = autoHSV.ptr(); -#else - #error Need to convert float array to SkScalar array before calling the following function. -#endif - + SkScalar* hsv = autoHSV.ptr(); return static_cast<jint>(SkHSVToColor(alpha, hsv)); } @@ -149,11 +144,7 @@ static jlong LinearGradient_create(JNIEnv* env, jobject, jlong matrixPtr, std::vector<SkColor4f> colors = convertColorLongs(env, colorArray); AutoJavaFloatArray autoPos(env, posArray, colors.size()); -#ifdef SK_SCALAR_IS_FLOAT SkScalar* pos = autoPos.ptr(); -#else - #error Need to convert float array to SkScalar array before calling the following function. -#endif sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0], GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(), @@ -193,11 +184,7 @@ static jlong RadialGradient_create(JNIEnv* env, std::vector<SkColor4f> colors = convertColorLongs(env, colorArray); AutoJavaFloatArray autoPos(env, posArray, colors.size()); -#ifdef SK_SCALAR_IS_FLOAT SkScalar* pos = autoPos.ptr(); -#else - #error Need to convert float array to SkScalar array before calling the following function. -#endif auto colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); auto skTileMode = static_cast<SkTileMode>(tileMode); @@ -225,11 +212,7 @@ static jlong SweepGradient_create(JNIEnv* env, jobject, jlong matrixPtr, jfloat std::vector<SkColor4f> colors = convertColorLongs(env, colorArray); AutoJavaFloatArray autoPos(env, jpositions, colors.size()); -#ifdef SK_SCALAR_IS_FLOAT SkScalar* pos = autoPos.ptr(); -#else - #error Need to convert float array to SkScalar array before calling the following function. -#endif sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0], GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(), diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp index 8a4d4e17edb1..8ba750372d18 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -21,7 +21,6 @@ #else #define __ANDROID_API_P__ 28 #endif -#include <Mesh.h> #include <androidfw/ResourceTypes.h> #include <hwui/Canvas.h> #include <hwui/Paint.h> @@ -446,10 +445,10 @@ static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle, static void drawMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong meshHandle, jint modeHandle, jlong paintHandle) { - const SkMesh mesh = reinterpret_cast<MeshWrapper*>(meshHandle)->mesh; + const Mesh* mesh = reinterpret_cast<Mesh*>(meshHandle); SkBlendMode blendMode = static_cast<SkBlendMode>(modeHandle); - SkPaint* paint = reinterpret_cast<Paint*>(paintHandle); - get_canvas(canvasHandle)->drawMesh(mesh, SkBlender::Mode(blendMode), *paint); + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + get_canvas(canvasHandle)->drawMesh(*mesh, SkBlender::Mode(blendMode), *paint); } static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp index 3e453e65ae92..ae22213f4bf4 100644 --- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp @@ -49,7 +49,7 @@ static RenderCallback createRenderCallback(JNIEnv* env, jobject releaseCallback) auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(releaseCallback)); return [globalCallbackRef](android::base::unique_fd&& fd, int status) { - GraphicsJNI::getJNIEnv()->CallStaticVoidMethod( + globalCallbackRef->env()->CallStaticVoidMethod( gHardwareBufferRendererClassInfo.clazz, gHardwareBufferRendererClassInfo.invokeRenderCallback, globalCallbackRef->object(), reinterpret_cast<jint>(fd.release()), reinterpret_cast<jint>(status)); @@ -172,7 +172,8 @@ static const JNINativeMethod gMethods[] = { int register_android_graphics_HardwareBufferRenderer(JNIEnv* env) { jclass hardwareBufferRendererClazz = FindClassOrDie(env, "android/graphics/HardwareBufferRenderer"); - gHardwareBufferRendererClassInfo.clazz = hardwareBufferRendererClazz; + gHardwareBufferRendererClassInfo.clazz = + reinterpret_cast<jclass>(env->NewGlobalRef(hardwareBufferRendererClazz)); gHardwareBufferRendererClassInfo.invokeRenderCallback = GetStaticMethodIDOrDie(env, hardwareBufferRendererClazz, "invokeRenderCallback", "(Ljava/util/function/Consumer;II)V"); diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index d6aad7d3eede..6a7411f5d859 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -94,12 +94,21 @@ struct { jmethodID getDestinationBitmap; } gCopyRequest; +static JNIEnv* getenv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); + } + return env; +} + typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface); ANW_fromSurface fromSurface; class FrameCommitWrapper : public LightRefBase<FrameCommitWrapper> { public: explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) { + env->GetJavaVM(&mVm); mObject = env->NewGlobalRef(jobject); LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref"); } @@ -109,18 +118,19 @@ public: void onFrameCommit(bool didProduceBuffer) { if (mObject) { ATRACE_FORMAT("frameCommit success=%d", didProduceBuffer); - GraphicsJNI::getJNIEnv()->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, - didProduceBuffer); + getenv(mVm)->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, + didProduceBuffer); releaseObject(); } } private: + JavaVM* mVm; jobject mObject; void releaseObject() { if (mObject) { - GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); + getenv(mVm)->DeleteGlobalRef(mObject); mObject = nullptr; } } @@ -541,10 +551,9 @@ static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* auto pictureState = std::make_shared<PictureCaptureState>(); proxy->setPictureCapturedCallback([globalCallbackRef, pictureState](sk_sp<SkPicture>&& picture) { - JNIEnv* env = GraphicsJNI::getJNIEnv(); Picture* wrapper = new PictureWrapper{std::move(picture), pictureState}; - env->CallStaticVoidMethod(gHardwareRenderer.clazz, - gHardwareRenderer.invokePictureCapturedCallback, + globalCallbackRef->env()->CallStaticVoidMethod( + gHardwareRenderer.clazz, gHardwareRenderer.invokePictureCapturedCallback, static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)), globalCallbackRef->object()); }); @@ -561,16 +570,14 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback( LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); auto globalCallbackRef = std::make_shared<JGlobalRefHolder>( vm, env->NewGlobalRef(aSurfaceTransactionCallback)); - proxy->setASurfaceTransactionCallback( - [globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) -> bool { - JNIEnv* env = GraphicsJNI::getJNIEnv(); - jboolean ret = env->CallBooleanMethod( - globalCallbackRef->object(), - gASurfaceTransactionCallback.onMergeTransaction, - static_cast<jlong>(transObj), static_cast<jlong>(scObj), - static_cast<jlong>(frameNr)); - return ret; - }); + proxy->setASurfaceTransactionCallback([globalCallbackRef](int64_t transObj, int64_t scObj, + int64_t frameNr) -> bool { + jboolean ret = globalCallbackRef->env()->CallBooleanMethod( + globalCallbackRef->object(), gASurfaceTransactionCallback.onMergeTransaction, + static_cast<jlong>(transObj), static_cast<jlong>(scObj), + static_cast<jlong>(frameNr)); + return ret; + }); } } @@ -585,9 +592,8 @@ static void android_view_ThreadedRenderer_setPrepareSurfaceControlForWebviewCall auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback)); proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() { - JNIEnv* env = GraphicsJNI::getJNIEnv(); - env->CallVoidMethod(globalCallbackRef->object(), - gPrepareSurfaceControlForWebviewCallback.prepare); + globalCallbackRef->env()->CallVoidMethod( + globalCallbackRef->object(), gPrepareSurfaceControlForWebviewCallback.prepare); }); } } @@ -604,7 +610,7 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env, env->NewGlobalRef(frameCallback)); proxy->setFrameCallback([globalCallbackRef](int32_t syncResult, int64_t frameNr) -> std::function<void(bool)> { - JNIEnv* env = GraphicsJNI::getJNIEnv(); + JNIEnv* env = globalCallbackRef->env(); ScopedLocalRef<jobject> frameCommitCallback( env, env->CallObjectMethod( globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw, @@ -643,9 +649,8 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env, auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback)); proxy->setFrameCompleteCallback([globalCallbackRef]() { - JNIEnv* env = GraphicsJNI::getJNIEnv(); - env->CallVoidMethod(globalCallbackRef->object(), - gFrameCompleteCallback.onFrameComplete); + globalCallbackRef->env()->CallVoidMethod(globalCallbackRef->object(), + gFrameCompleteCallback.onFrameComplete); }); } } @@ -656,8 +661,7 @@ public: : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {} virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override { - JNIEnv* env = GraphicsJNI::getJNIEnv(); - jlong bitmapPtr = env->CallLongMethod( + jlong bitmapPtr = mRefHolder.env()->CallLongMethod( mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight); SkBitmap bitmap; bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); @@ -665,9 +669,8 @@ public: } virtual void onCopyFinished(CopyResult result) override { - JNIEnv* env = GraphicsJNI::getJNIEnv(); - env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished, - static_cast<jint>(result)); + mRefHolder.env()->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished, + static_cast<jint>(result)); } private: diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp index cf6702e45fff..ca667b0d09bc 100644 --- a/libs/hwui/jni/android_graphics_Matrix.cpp +++ b/libs/hwui/jni/android_graphics_Matrix.cpp @@ -23,8 +23,6 @@ namespace android { static_assert(sizeof(SkMatrix) == 40, "Unexpected sizeof(SkMatrix), " "update size in Matrix.java#NATIVE_ALLOCATION_SIZE and here"); -static_assert(SK_SCALAR_IS_FLOAT, "SK_SCALAR_IS_FLOAT is false, " - "only float scalar is supported"); class SkMatrixGlue { public: diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp new file mode 100644 index 000000000000..04339dc8b9a5 --- /dev/null +++ b/libs/hwui/jni/android_graphics_Mesh.cpp @@ -0,0 +1,350 @@ +/* + * 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. + */ + +#include <GrDirectContext.h> +#include <Mesh.h> +#include <SkMesh.h> +#include <jni.h> +#include <log/log.h> + +#include <utility> + +#include "GraphicsJNI.h" +#include "graphics_jni_helpers.h" + +#define gIndexByteSize 2 + +// A smart pointer that provides read only access to Java.nio.Buffer. This handles both +// direct and indrect buffers, allowing access to the underlying data in both +// situations. If passed a null buffer, we will throw NullPointerException, +// and c_data will return nullptr. +// +// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void * +// conversion. +class ScopedJavaNioBuffer { +public: + ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, size_t size, jboolean isDirect) + : mEnv(env), mBuffer(buffer) { + if (buffer == nullptr) { + mDataBase = nullptr; + mData = nullptr; + jniThrowNullPointerException(env); + } else { + mArray = (jarray) nullptr; + if (isDirect) { + mData = getDirectBufferPointer(mEnv, mBuffer); + } else { + mData = setIndirectData(size); + } + } + } + + ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); } + + ~ScopedJavaNioBuffer() { reset(); } + + void reset() { + if (mDataBase) { + releasePointer(mEnv, mArray, mDataBase, JNI_FALSE); + mDataBase = nullptr; + } + } + + ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept { + if (this != &rhs) { + reset(); + + mEnv = rhs.mEnv; + mBuffer = rhs.mBuffer; + mDataBase = rhs.mDataBase; + mData = rhs.mData; + mArray = rhs.mArray; + rhs.mEnv = nullptr; + rhs.mData = nullptr; + rhs.mBuffer = nullptr; + rhs.mArray = nullptr; + rhs.mDataBase = nullptr; + } + return *this; + } + + const void* data() const { return mData; } + +private: + /** + * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data + * from a java.nio.Buffer. + */ + void* getDirectBufferPointer(JNIEnv* env, jobject buffer) { + if (buffer == nullptr) { + return nullptr; + } + + jint position; + jint limit; + jint elementSizeShift; + jlong pointer; + pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift); + if (pointer == 0) { + jniThrowException(mEnv, "java/lang/IllegalArgumentException", + "Must use a native order direct Buffer"); + return nullptr; + } + pointer += position << elementSizeShift; + return reinterpret_cast<void*>(pointer); + } + + static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) { + env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT); + } + + static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining, + jint* offset) { + jint position; + jint limit; + jint elementSizeShift; + + jlong pointer; + pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift); + *remaining = (limit - position) << elementSizeShift; + if (pointer != 0L) { + *array = nullptr; + pointer += position << elementSizeShift; + return reinterpret_cast<void*>(pointer); + } + + *array = jniGetNioBufferBaseArray(env, buffer); + *offset = jniGetNioBufferBaseArrayOffset(env, buffer); + return nullptr; + } + + /** + * This is a copy of + * static void android_glBufferData__IILjava_nio_Buffer_2I + * from com_google_android_gles_jni_GLImpl.cpp + */ + void* setIndirectData(size_t size) { + jint exception; + const char* exceptionType; + const char* exceptionMessage; + jint bufferOffset = (jint)0; + jint remaining; + void* tempData; + + if (mBuffer) { + tempData = + (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset); + if (remaining < size) { + exception = 1; + exceptionType = "java/lang/IllegalArgumentException"; + exceptionMessage = "remaining() < size < needed"; + goto exit; + } + } + if (mBuffer && tempData == nullptr) { + mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0); + tempData = (void*)(mDataBase + bufferOffset); + } + return tempData; + exit: + if (mArray) { + releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE); + } + if (exception) { + jniThrowException(mEnv, exceptionType, exceptionMessage); + } + return nullptr; + } + + JNIEnv* mEnv; + + // Java Buffer data + void* mData; + jobject mBuffer; + + // Indirect Buffer Data + jarray mArray; + char* mDataBase; +}; + +namespace android { + +static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, + jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top, + jfloat right, jfloat bottom) { + auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec)); + size_t bufferSize = vertexCount * skMeshSpec->stride(); + auto buff = ScopedJavaNioBuffer(env, vertexBuffer, bufferSize, isDirect); + auto skRect = SkRect::MakeLTRB(left, top, right, bottom); + auto meshPtr = new Mesh(skMeshSpec, mode, buff.data(), bufferSize, vertexCount, vertexOffset, + std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect); + auto [valid, msg] = meshPtr->validate(); + if (!valid) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str()); + } + return reinterpret_cast<jlong>(meshPtr); +} + +static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, + jboolean isVertexDirect, jint vertexCount, jint vertexOffset, + jobject indexBuffer, jboolean isIndexDirect, jint indexCount, + jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) { + auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec)); + auto vertexBufferSize = vertexCount * skMeshSpec->stride(); + auto indexBufferSize = indexCount * gIndexByteSize; + auto vBuf = ScopedJavaNioBuffer(env, vertexBuffer, vertexBufferSize, isVertexDirect); + auto iBuf = ScopedJavaNioBuffer(env, indexBuffer, indexBufferSize, isIndexDirect); + auto skRect = SkRect::MakeLTRB(left, top, right, bottom); + auto meshPtr = new Mesh(skMeshSpec, mode, vBuf.data(), vertexBufferSize, vertexCount, + vertexOffset, iBuf.data(), indexBufferSize, indexCount, indexOffset, + std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect); + auto [valid, msg] = meshPtr->validate(); + if (!valid) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str()); + } + + return reinterpret_cast<jlong>(meshPtr); +} + +static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args); + va_end(args); + return ret; +} + +static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) { + switch (type) { + case SkRuntimeEffect::Uniform::Type::kFloat: + case SkRuntimeEffect::Uniform::Type::kFloat2: + case SkRuntimeEffect::Uniform::Type::kFloat3: + case SkRuntimeEffect::Uniform::Type::kFloat4: + case SkRuntimeEffect::Uniform::Type::kFloat2x2: + case SkRuntimeEffect::Uniform::Type::kFloat3x3: + case SkRuntimeEffect::Uniform::Type::kFloat4x4: + return false; + case SkRuntimeEffect::Uniform::Type::kInt: + case SkRuntimeEffect::Uniform::Type::kInt2: + case SkRuntimeEffect::Uniform::Type::kInt3: + case SkRuntimeEffect::Uniform::Type::kInt4: + return true; + } +} + +static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder, + const char* uniformName, const float values[], int count, + bool isColor) { + MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName); + if (uniform.fVar == nullptr) { + ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); + } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) { + if (isColor) { + jniThrowExceptionFmt( + env, "java/lang/IllegalArgumentException", + "attempting to set a color uniform using the non-color specific APIs: %s %x", + uniformName, uniform.fVar->flags); + } else { + ThrowIAEFmt(env, + "attempting to set a non-color uniform using the setColorUniform APIs: %s", + uniformName); + } + } else if (isIntUniformType(uniform.fVar->type)) { + ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s", + uniformName); + } else if (!uniform.set<float>(values, count)) { + ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", + uniform.fVar->sizeInBytes(), sizeof(float) * count); + } +} + +static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, + jfloat value1, jfloat value2, jfloat value3, jfloat value4, + jint count) { + auto* wrapper = reinterpret_cast<Mesh*>(meshWrapper); + ScopedUtfChars name(env, uniformName); + const float values[4] = {value1, value2, value3, value4}; + nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false); + wrapper->markDirty(); +} + +static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName, + jfloatArray jvalues, jboolean isColor) { + auto wrapper = reinterpret_cast<Mesh*>(meshWrapper); + ScopedUtfChars name(env, jUniformName); + AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess); + nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(), + autoValues.length(), isColor); + wrapper->markDirty(); +} + +static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder, + const char* uniformName, const int values[], int count) { + MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName); + if (uniform.fVar == nullptr) { + ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); + } else if (!isIntUniformType(uniform.fVar->type)) { + ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s", + uniformName); + } else if (!uniform.set<int>(values, count)) { + ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", + uniform.fVar->sizeInBytes(), sizeof(float) * count); + } +} + +static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, + jint value1, jint value2, jint value3, jint value4, jint count) { + auto wrapper = reinterpret_cast<Mesh*>(meshWrapper); + ScopedUtfChars name(env, uniformName); + const int values[4] = {value1, value2, value3, value4}; + nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count); + wrapper->markDirty(); +} + +static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, + jintArray values) { + auto wrapper = reinterpret_cast<Mesh*>(meshWrapper); + ScopedUtfChars name(env, uniformName); + AutoJavaIntArray autoValues(env, values, 0); + nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(), + autoValues.length()); + wrapper->markDirty(); +} + +static void MeshWrapper_destroy(Mesh* wrapper) { + delete wrapper; +} + +static jlong getMeshFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy)); +} + +static const JNINativeMethod gMeshMethods[] = { + {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer}, + {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make}, + {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J", + (void*)makeIndexed}, + {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms}, + {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms}, + {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms}, + {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}}; + +int register_android_graphics_Mesh(JNIEnv* env) { + android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods)); + return 0; +} + +} // namespace android
\ No newline at end of file diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index fcfc4f82abed..af2d3b34bac7 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -23,6 +23,7 @@ #else #include "DamageAccumulator.h" #endif +#include "TreeInfo.h" #include "VectorDrawable.h" #ifdef __ANDROID__ #include "renderthread/CanvasContext.h" @@ -102,6 +103,12 @@ bool SkiaDisplayList::prepareListAndChildren( info.prepareTextures = false; info.canvasContext.unpinImages(); } + + auto grContext = info.canvasContext.getGrContext(); + for (auto mesh : mMeshes) { + mesh->updateSkMesh(grContext); + } + #endif bool hasBackwardProjectedNodesHere = false; @@ -168,6 +175,7 @@ void SkiaDisplayList::reset() { mDisplayList.reset(); + mMeshes.clear(); mMutableImages.clear(); mVectorDrawables.clear(); mAnimatedImages.clear(); diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index 2a677344b7b2..7af31a4dc4c6 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -18,6 +18,7 @@ #include <deque> +#include "Mesh.h" #include "RecordingCanvas.h" #include "RenderNodeDrawable.h" #include "TreeInfo.h" @@ -167,6 +168,7 @@ public: std::deque<RenderNodeDrawable> mChildNodes; std::deque<FunctorDrawable*> mChildFunctors; std::vector<SkImage*> mMutableImages; + std::vector<const Mesh*> mMeshes; private: std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index e1c8877a8b7a..3ca7eeb37a89 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -321,6 +321,11 @@ double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedIma return 0; } +void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) { + mDisplayList->mMeshes.push_back(&mesh); + mRecorder.drawMesh(mesh, blender, paint); +} + } // namespace skiapipeline } // namespace uirenderer } // namespace android diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 3fd8fa3aa9a9..a8e4580dc200 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -81,6 +81,7 @@ public: virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; virtual void enableZ(bool enableZ) override; + virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override; virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override; virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 5136675a95eb..4da4c7fc56de 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -16,9 +16,9 @@ package android.media; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; +import static android.content.Context.DEVICE_ID_DEFAULT; import android.Manifest; import android.annotation.CallbackExecutor; diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index b6ab262b60c5..7faa13c80c62 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -16,9 +16,9 @@ package android.media; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; +import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE; import android.annotation.CallbackExecutor; diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 0b086816ca4b..faa7f7fa3aa1 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -3049,7 +3049,13 @@ public final class MediaDrm implements AutoCloseable { /** - * Gets the {@link LogSessionId}. + * Sets the {@link LogSessionId}. + * + * <p>The implementation of this method varies by DRM provider; Please refer + * to your DRM provider documentation for more details on this method. + * + * @throws UnsupportedOperationException when the vendor plugin does not + * implement this method */ public void setLogSessionId(@NonNull LogSessionId logSessionId) { Objects.requireNonNull(logSessionId); diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java index 38115e19d608..3f44b09124e4 100644 --- a/media/java/android/media/PlayerBase.java +++ b/media/java/android/media/PlayerBase.java @@ -16,9 +16,9 @@ package android.media; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; +import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE; import android.annotation.NonNull; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java index 76543f4ce989..6089f4291f3e 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java @@ -17,10 +17,10 @@ package com.android.mediaframeworktest.unit; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; +import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.AudioManager.FX_KEY_CLICK; import static org.mockito.ArgumentMatchers.anyInt; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java index 9c813c28d7d9..71228e2120c0 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java @@ -16,10 +16,10 @@ package com.android.mediaframeworktest.unit; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; +import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE; import static org.junit.Assert.assertEquals; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java index ffed39ac82bf..ac24a1007170 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java @@ -16,10 +16,10 @@ package com.android.mediaframeworktest.unit; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; +import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE; import static org.junit.Assert.assertEquals; diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml index b36cb5c9709f..dfc8aa06c404 100644 --- a/packages/CredentialManager/AndroidManifest.xml +++ b/packages/CredentialManager/AndroidManifest.xml @@ -41,6 +41,15 @@ android:excludeFromRecents="true" android:theme="@style/Theme.CredentialSelector"> </activity> + + <receiver + android:name=".CredentialProviderReceiver" + android:exported="true" + android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"> + <intent-filter> + <action android:name="android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED"/> + </intent-filter> + </receiver> </application> </manifest> diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 3eb58f1532d8..ee512427fa9c 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -94,6 +94,8 @@ <string name="accessibility_back_arrow_button">"Go back to the previous page"</string> <!-- Spoken content description of the close "X" icon button. --> <string name="accessibility_close_button">Close</string> + <!-- Spoken content description of the close "X" icon button. [CHAR LIMIT=NONE] --> + <string name="accessibility_snackbar_dismiss">Dismiss</string> <!-- Strings for the get flow. --> <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] --> diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml index a58a0383b9b2..c7e47962472f 100644 --- a/packages/CredentialManager/res/values/themes.xml +++ b/packages/CredentialManager/res/values/themes.xml @@ -1,13 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material"> - <item name="android:statusBarColor">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowIsTranslucent">true</item> - <item name="android:colorBackgroundCacheHint">@null</item> - <item name="fontFamily">google-sans</item> + <item name="android:statusBarColor">@android:color/transparent</item> + <item name="android:navigationBarColor">@android:color/transparent</item> </style> </resources>
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index e3236b38b0f7..0e604ce2cf3a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -16,8 +16,6 @@ package com.android.credentialmanager -import android.app.slice.Slice -import android.app.slice.SliceSpec import android.content.Context import android.content.Intent import android.credentials.CreateCredentialRequest @@ -25,6 +23,7 @@ import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL import android.credentials.CredentialOption import android.credentials.GetCredentialRequest import android.credentials.ui.AuthenticationEntry +import android.credentials.ui.CancelUiRequest import android.credentials.ui.Constants import android.credentials.ui.Entry import android.credentials.ui.CreateCredentialProviderData @@ -35,7 +34,6 @@ import android.credentials.ui.RequestInfo import android.credentials.ui.BaseDialogResult import android.credentials.ui.ProviderPendingIntentResponse import android.credentials.ui.UserSelectionDialogResult -import android.net.Uri import android.os.IBinder import android.os.Binder import android.os.Bundle @@ -47,6 +45,8 @@ import com.android.credentialmanager.getflow.GetCredentialUiState import androidx.credentials.CreateCredentialRequest.DisplayInfo import androidx.credentials.CreatePublicKeyCredentialRequest import androidx.credentials.CreatePasswordRequest +import androidx.credentials.GetPasswordOption +import androidx.credentials.GetPublicKeyCredentialOption import java.time.Instant @@ -75,6 +75,12 @@ class CredentialManagerRepo( RequestInfo::class.java ) ?: testCreatePasskeyRequestInfo() + val originName: String? = when (requestInfo.type) { + RequestInfo.TYPE_CREATE -> requestInfo.createCredentialRequest?.origin + RequestInfo.TYPE_GET -> requestInfo.getCredentialRequest?.origin + else -> null + } + providerEnabledList = when (requestInfo.type) { RequestInfo.TYPE_CREATE -> intent.extras?.getParcelableArrayList( @@ -108,15 +114,15 @@ class CredentialManagerRepo( val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse() val providerEnableListUiState = getCreateProviderEnableListInitialUiState() val providerDisableListUiState = getCreateProviderDisableListInitialUiState() - val requestDisplayInfoUiState = getCreateRequestDisplayInfoInitialUiState()!! + val requestDisplayInfoUiState = + getCreateRequestDisplayInfoInitialUiState(originName)!! UiState( createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState( providerEnableListUiState, providerDisableListUiState, defaultProviderId, requestDisplayInfoUiState, - /** isOnPasskeyIntroStateAlready = */ - false, + isOnPasskeyIntroStateAlready = false, isPasskeyFirstUse )!!, getCredentialUiState = null, @@ -124,7 +130,7 @@ class CredentialManagerRepo( } RequestInfo.TYPE_GET -> UiState( createCredentialUiState = null, - getCredentialUiState = getCredentialInitialUiState()!!, + getCredentialUiState = getCredentialInitialUiState(originName)!!, ) else -> throw IllegalStateException("Unrecognized request type: ${requestInfo.type}") } @@ -175,11 +181,11 @@ class CredentialManagerRepo( } // IMPORTANT: new invocation should be mindful that this method can throw. - private fun getCredentialInitialUiState(): GetCredentialUiState? { + private fun getCredentialInitialUiState(originName: String?): GetCredentialUiState? { val providerEnabledList = GetFlowUtils.toProviderList( providerEnabledList as List<GetCredentialProviderData>, context ) - val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo, context) + val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo, context, originName) return GetCredentialUiState( providerEnabledList, requestDisplayInfo ?: return null, @@ -201,8 +207,10 @@ class CredentialManagerRepo( ) } - private fun getCreateRequestDisplayInfoInitialUiState(): RequestDisplayInfo? { - return CreateFlowUtils.toRequestDisplayInfo(requestInfo, context) + private fun getCreateRequestDisplayInfoInitialUiState( + originName: String? + ): RequestDisplayInfo? { + return CreateFlowUtils.toRequestDisplayInfo(requestInfo, context, originName) } companion object { @@ -217,6 +225,14 @@ class CredentialManagerRepo( resultReceiver.send(cancelCode, resultData) } } + + /** Return the request token whose UI should be cancelled, or null otherwise. */ + fun getCancelUiRequestToken(intent: Intent): IBinder? { + return intent.extras?.getParcelable( + CancelUiRequest.EXTRA_CANCEL_UI_REQUEST, + CancelUiRequest::class.java + )?.token + } } // TODO: below are prototype functionalities. To be removed for productionization. @@ -230,7 +246,8 @@ class CredentialManagerRepo( context, "key1", "subkey-1", "elisa.beckett@gmail.com", 20, 7, 27, Instant.ofEpochSecond(10L), - "Legal note" + "You can use your passkey on this or other devices. It is saved to " + + "the Password Manager for elisa.beckett@gmail.com." ), CreateTestUtils.newCreateEntry( context, @@ -239,11 +256,9 @@ class CredentialManagerRepo( null ), ) - ) - .setRemoteEntry( - newRemoteEntry("key2", "subkey-1") - ) - .build(), + ).setRemoteEntry( + CreateTestUtils.newRemoteCreateEntry(context, "key2", "subkey-1") + ).build(), CreateCredentialProviderData .Builder("com.dashlane") .setSaveEntries( @@ -258,11 +273,11 @@ class CredentialManagerRepo( context, "key1", "subkey-4", "elisa.work@dashlane.com", 20, 7, 27, Instant.ofEpochSecond(14L), - null + "You can use your passkey on this or other devices. It is saved to " + + "the Password Manager for elisa.work@dashlane.com" ), ) - ) - .build(), + ).build(), ) } @@ -318,7 +333,7 @@ class CredentialManagerRepo( ), ) ).setRemoteEntry( - newRemoteEntry("key4", "subkey-1") + GetTestUtils.newRemoteCredentialEntry(context, "key4", "subkey-1") ).build(), GetCredentialProviderData.Builder("com.dashlane") .setCredentialEntries( @@ -333,10 +348,12 @@ class CredentialManagerRepo( ), ) ).setAuthenticationEntries( - listOf(GetTestUtils.newAuthenticationEntry( - context, "key2", "subkey-1", "foo@email.com", - AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT - )) + listOf( + GetTestUtils.newAuthenticationEntry( + context, "key2", "subkey-1", "foo@email.com", + AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT, + ) + ) ).setActionChips( listOf( GetTestUtils.newActionEntry( @@ -348,20 +365,6 @@ class CredentialManagerRepo( ) } - - private fun newRemoteEntry( - key: String, - subkey: String, - ): Entry { - return Entry( - key, - subkey, - Slice.Builder( - Uri.EMPTY, SliceSpec("type", 1) - ).build() - ) - } - private fun testCreatePasskeyRequestInfo(): RequestInfo { val request = CreatePublicKeyCredentialRequest( "{\"extensions\": {\n" + @@ -398,7 +401,8 @@ class CredentialManagerRepo( " \"authenticatorSelection\": {\n" + " \"residentKey\": \"required\",\n" + " \"requireResidentKey\": true\n" + - " }}" + " }}", + preferImmediatelyAvailableCredentials = true, ) val credentialData = request.credentialData return RequestInfo.newCreateRequestInfo( @@ -444,19 +448,28 @@ class CredentialManagerRepo( } private fun testGetRequestInfo(): RequestInfo { + val passwordOption = GetPasswordOption() + val passkeyOption = GetPublicKeyCredentialOption( + "json", preferImmediatelyAvailableCredentials = false) return RequestInfo.newGetRequestInfo( Binder(), GetCredentialRequest.Builder( Bundle() ).addCredentialOption( CredentialOption( - "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL", - Bundle(), - Bundle(), /*isSystemProviderRequired=*/ - false + passwordOption.type, + passwordOption.requestData, + passwordOption.candidateQueryData, + passwordOption.isSystemProviderRequired ) - ) - .build(), + ).addCredentialOption( + CredentialOption( + passkeyOption.type, + passkeyOption.requestData, + passkeyOption.candidateQueryData, + passkeyOption.isSystemProviderRequired + ) + ).build(), "com.google.android.youtube" ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt new file mode 100644 index 000000000000..ee8cffed7f7d --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt @@ -0,0 +1,35 @@ +/* + * 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.credentialmanager + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import com.android.credentialmanager.common.Constants + + +class CredentialProviderReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context?, intent: Intent?) { + Log.d(Constants.LOG_TAG, "Received intent in CredentialProviderReceiver") + + val sharedPreferences = context?.getSharedPreferences(context?.packageName, + Context.MODE_PRIVATE) + sharedPreferences?.edit()?.remove(UserConfigRepo.DEFAULT_PROVIDER)?.commit() + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index d618e746f4b8..e8e39741c3bd 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -39,7 +39,7 @@ import com.android.credentialmanager.createflow.CreateCredentialScreen import com.android.credentialmanager.createflow.hasContentToDisplay import com.android.credentialmanager.getflow.GetCredentialScreen import com.android.credentialmanager.getflow.hasContentToDisplay -import com.android.credentialmanager.ui.theme.CredentialSelectorTheme +import com.android.credentialmanager.ui.theme.PlatformTheme @ExperimentalMaterialApi class CredentialSelectorActivity : ComponentActivity() { @@ -47,10 +47,16 @@ class CredentialSelectorActivity : ComponentActivity() { super.onCreate(savedInstanceState) Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity") try { + if (CredentialManagerRepo.getCancelUiRequestToken(intent) != null) { + Log.d( + Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.") + this.finish() + return + } val userConfigRepo = UserConfigRepo(this) val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo) setContent { - CredentialSelectorTheme { + PlatformTheme { CredentialManagerBottomSheet( credManRepo, userConfigRepo @@ -67,10 +73,19 @@ class CredentialSelectorActivity : ComponentActivity() { setIntent(intent) Log.d(Constants.LOG_TAG, "Existing activity received new intent") try { - val userConfigRepo = UserConfigRepo(this) - val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo) + val cancelUiRequestToken = CredentialManagerRepo.getCancelUiRequestToken(intent) val viewModel: CredentialSelectorViewModel by viewModels() - viewModel.onNewCredentialManagerRepo(credManRepo) + if (cancelUiRequestToken != null && + viewModel.shouldCancelCurrentUi(cancelUiRequestToken)) { + Log.d( + Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.") + this.finish() + return + } else { + val userConfigRepo = UserConfigRepo(this) + val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo) + viewModel.onNewCredentialManagerRepo(credManRepo) + } } catch (e: Exception) { onInitializationError(e, intent) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index a1e0823e4f96..9b7139ccc26e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -17,6 +17,7 @@ package com.android.credentialmanager import android.app.Activity +import android.os.IBinder import android.util.Log import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult @@ -135,6 +136,11 @@ class CredentialSelectorViewModel( uiState = uiState.copy(dialogState = DialogState.COMPLETE) } + /** Return true if the current UI's request token matches the UI cancellation request token. */ + fun shouldCancelCurrentUi(cancelRequestToken: IBinder): Boolean { + return credManRepo.requestInfo.token.equals(cancelRequestToken) + } + /**************************************************************************/ /***** Get Flow Callbacks *****/ /**************************************************************************/ diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index aa0959c82b4b..a834994a5b0f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -50,7 +50,11 @@ import com.android.credentialmanager.getflow.RemoteEntryInfo import androidx.credentials.CreateCredentialRequest import androidx.credentials.CreateCustomCredentialRequest import androidx.credentials.CreatePasswordRequest +import androidx.credentials.CredentialOption import androidx.credentials.CreatePublicKeyCredentialRequest +import androidx.credentials.CreatePublicKeyCredentialRequestPrivileged +import androidx.credentials.GetPublicKeyCredentialOption +import androidx.credentials.GetPublicKeyCredentialOptionPrivileged import androidx.credentials.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL import androidx.credentials.provider.Action import androidx.credentials.provider.AuthenticationAction @@ -170,10 +174,29 @@ class GetFlowUtils { fun toRequestDisplayInfo( requestInfo: RequestInfo, context: Context, + originName: String?, ): com.android.credentialmanager.getflow.RequestDisplayInfo? { + val getCredentialRequest = requestInfo.getCredentialRequest ?: return null + val preferImmediatelyAvailableCredentials = getCredentialRequest.credentialOptions.any { + val credentialOptionJetpack = CredentialOption.createFrom( + it.type, + it.credentialRetrievalData, + it.credentialRetrievalData, + it.isSystemProviderRequired + ) + if (credentialOptionJetpack is GetPublicKeyCredentialOption) { + credentialOptionJetpack.preferImmediatelyAvailableCredentials + } else if (credentialOptionJetpack is GetPublicKeyCredentialOptionPrivileged) { + credentialOptionJetpack.preferImmediatelyAvailableCredentials + } else { + false + } + } return com.android.credentialmanager.getflow.RequestDisplayInfo( - appName = getAppLabel(context.packageManager, requestInfo.appPackageName) - ?: return null + appName = originName + ?: getAppLabel(context.packageManager, requestInfo.appPackageName) + ?: return null, + preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials ) } @@ -287,6 +310,7 @@ class GetFlowUtils { pendingIntent = structuredAuthEntry.pendingIntent, fillInIntent = entry.frameworkExtrasIntent, title = title, + providerDisplayName = providerDisplayName, icon = providerIcon, isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED, isLastUnlocked = @@ -394,8 +418,10 @@ class CreateFlowUtils { fun toRequestDisplayInfo( requestInfo: RequestInfo, context: Context, + originName: String?, ): RequestDisplayInfo? { - val appLabel = getAppLabel(context.packageManager, requestInfo.appPackageName) + val appLabel = originName + ?: getAppLabel(context.packageManager, requestInfo.appPackageName) ?: return null val createCredentialRequest = requestInfo.createCredentialRequest ?: return null val createCredentialRequestJetpack = CreateCredentialRequest.createFrom( @@ -410,24 +436,25 @@ class CreateFlowUtils { createCredentialRequestJetpack.password, CredentialType.PASSWORD, appLabel, - context.getDrawable(R.drawable.ic_password)!! + context.getDrawable(R.drawable.ic_password) ?: return null, + preferImmediatelyAvailableCredentials = false, ) is CreatePublicKeyCredentialRequest -> { - val requestJson = createCredentialRequestJetpack.requestJson - val json = JSONObject(requestJson) - var name = "" - var displayName = "" - if (json.has("user")) { - val user: JSONObject = json.getJSONObject("user") - name = user.getString("name") - displayName = user.getString("displayName") - } - RequestDisplayInfo( - name, - displayName, - CredentialType.PASSKEY, - appLabel, - context.getDrawable(R.drawable.ic_passkey)!! + newRequestDisplayInfoFromPasskeyJson( + requestJson = createCredentialRequestJetpack.requestJson, + appLabel = appLabel, + context = context, + preferImmediatelyAvailableCredentials = + createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, + ) + } + is CreatePublicKeyCredentialRequestPrivileged -> { + newRequestDisplayInfoFromPasskeyJson( + requestJson = createCredentialRequestJetpack.requestJson, + appLabel = appLabel, + context = context, + preferImmediatelyAvailableCredentials = + createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, ) } is CreateCustomCredentialRequest -> { @@ -441,7 +468,8 @@ class CreateFlowUtils { type = CredentialType.UNKNOWN, appName = appLabel, typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context) - ?: context.getDrawable(R.drawable.ic_other_sign_in)!! + ?: context.getDrawable(R.drawable.ic_other_sign_in) ?: return null, + preferImmediatelyAvailableCredentials = false, ) } else -> null @@ -608,5 +636,29 @@ class CreateFlowUtils { ) } else null } + + private fun newRequestDisplayInfoFromPasskeyJson( + requestJson: String, + appLabel: String, + context: Context, + preferImmediatelyAvailableCredentials: Boolean, + ): RequestDisplayInfo? { + val json = JSONObject(requestJson) + var name = "" + var displayName = "" + if (json.has("user")) { + val user: JSONObject = json.getJSONObject("user") + name = user.getString("name") + displayName = user.getString("displayName") + } + return RequestDisplayInfo( + name, + displayName, + CredentialType.PASSKEY, + appLabel, + context.getDrawable(R.drawable.ic_passkey) ?: return null, + preferImmediatelyAvailableCredentials, + ) + } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt index eb3d188eab07..9216429eac52 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt @@ -29,6 +29,8 @@ import android.provider.Settings import androidx.credentials.provider.CreateEntry import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry +import androidx.credentials.provider.RemoteCreateEntry +import androidx.credentials.provider.RemoteCredentialEntry import java.time.Instant @@ -69,6 +71,21 @@ class GetTestUtils { ) } + internal fun newRemoteCredentialEntry( + context: Context, + key: String, + subkey: String, + ): Entry { + val intent = Intent(Settings.ACTION_SYNC_SETTINGS) + val pendingIntent = + PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + return Entry( + key, + subkey, + RemoteCredentialEntry(pendingIntent).slice + ) + } + internal fun newActionEntry( context: Context, key: String, @@ -203,5 +220,20 @@ class CreateTestUtils { Intent() ) } + + internal fun newRemoteCreateEntry( + context: Context, + key: String, + subkey: String, + ): Entry { + val intent = Intent(Settings.ACTION_SYNC_SETTINGS) + val pendingIntent = + PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + return Entry( + key, + subkey, + RemoteCreateEntry(pendingIntent).slice + ) + } } }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt index 6d07df70e7bf..297143309d14 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogResult.kt @@ -24,8 +24,6 @@ enum class DialogState { enum class ResultState { COMPLETE, - NORMAL_CANCELED, - LAUNCH_SETTING_CANCELED } data class DialogResult( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt index 58edb25336f8..335d58ac85f6 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt @@ -451,7 +451,7 @@ private fun Modifier.bottomSheetSwipeable( } @Composable -private fun Scrim( +internal fun Scrim( color: Color, onDismiss: () -> Unit, visible: Boolean diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt index 984057a1c960..04a2c07da388 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt @@ -16,6 +16,8 @@ package com.android.credentialmanager.common.ui +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding import com.android.credentialmanager.R import androidx.compose.material.Icon import androidx.compose.material.IconButton @@ -24,7 +26,6 @@ import androidx.compose.material.icons.outlined.Visibility import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -32,17 +33,19 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme +import androidx.compose.ui.unit.dp @Composable fun ActionButton(text: String, onClick: () -> Unit) { TextButton( + modifier = Modifier.padding(vertical = 4.dp), onClick = onClick, colors = ButtonDefaults.textButtonColors( contentColor = MaterialTheme.colorScheme.primary, - ) + ), + contentPadding = PaddingValues(start = 12.dp, top = 10.dp, end = 12.dp, bottom = 10.dp), ) { - Text(text = text) + LargeLabelText(text = text) } } @@ -64,7 +67,7 @@ fun ToggleVisibilityButton(modifier: Modifier = Modifier, onToggle: (Boolean) -> contentDescription = if (toggleState.value) stringResource(R.string.content_description_show_password) else stringResource(R.string.content_description_hide_password), - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant + tint = MaterialTheme.colorScheme.onSurfaceVariant, ) } }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt index 85e5c1ee69d5..cc73089a96e7 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt @@ -16,33 +16,77 @@ package com.android.credentialmanager.common.ui -import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp /** - * By default the card is filled with surfaceVariant color. This container card instead fills the - * background color with surface corlor. + * Container card for the whole sheet. + * + * Surface 1 color. No vertical padding. 24dp horizontal padding. 24dp bottom padding. 24dp top + * padding if [topAppBar] is not present, and none otherwise. + */ +@Composable +fun SheetContainerCard( + topAppBar: (@Composable () -> Unit)? = null, + modifier: Modifier = Modifier, + contentVerticalArrangement: Arrangement.Vertical = Arrangement.Top, + content: LazyListScope.() -> Unit, +) { + Card( + modifier = modifier.fillMaxWidth().wrapContentHeight(), + border = null, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + ElevationTokens.Level1 + ), + ), + ) { + if (topAppBar != null) { + topAppBar() + } + LazyColumn( + modifier = Modifier.padding( + start = 24.dp, + end = 24.dp, + bottom = 18.dp, + top = if (topAppBar == null) 24.dp else 0.dp + ).fillMaxWidth().wrapContentHeight(), + horizontalAlignment = Alignment.CenterHorizontally, + content = content, + verticalArrangement = contentVerticalArrangement, + ) + } +} + +/** + * Container card for the entries. + * + * Surface 3 color. No padding. Four rounded corner shape. */ @Composable -fun ContainerCard( +fun CredentialContainerCard( modifier: Modifier = Modifier, - shape: Shape = CardDefaults.shape, - border: BorderStroke? = null, content: @Composable ColumnScope.() -> Unit, ) { Card( - modifier = modifier.fillMaxWidth(), - shape = shape, - border = border, + modifier = modifier.fillMaxWidth().wrapContentHeight(), + shape = MaterialTheme.shapes.medium, + border = null, colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surface, + containerColor = Color.Transparent, ), content = content, ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt new file mode 100644 index 000000000000..b2489fd57b5d --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt @@ -0,0 +1,30 @@ +/* + * 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.credentialmanager.common.ui + +import androidx.compose.material3.ColorScheme +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.math.ln + +fun ColorScheme.surfaceColorAtElevation(elevation: Dp): Color { + if (elevation == 0.dp) return surface + val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f + return surfaceTint.copy(alpha = alpha).compositeOver(surface) +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt index d8ee750a88d6..c09a692ce9fc 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt @@ -16,21 +16,27 @@ package com.android.credentialmanager.common.ui +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +/** Primary container color; label-large button text; on-primary button text color. */ @Composable fun ConfirmButton(text: String, onClick: () -> Unit) { FilledTonalButton( + modifier = Modifier.padding(vertical = 4.dp), onClick = onClick, colors = ButtonDefaults.filledTonalButtonColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - ) + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), + contentPadding = PaddingValues(start = 24.dp, top = 10.dp, end = 24.dp, bottom = 10.dp), ) { - Text(text = text) + LargeLabelText(text = text) } }
\ No newline at end of file diff --git a/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt index a0813565b256..e1e666ef908b 100644 --- a/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt @@ -11,19 +11,19 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package android.window; +package com.android.credentialmanager.common.ui -import android.view.SurfaceControl; +import androidx.compose.ui.unit.dp -/** - * Interface to be invoked when a windowless starting surface added. - * - * @param addedSurface The starting surface. - * {@hide} - */ -interface IWindowlessStartingSurfaceCallback { - void onSurfaceAdded(in SurfaceControl addedSurface); -} +/** Copied from androidx.compose.material3.tokens. */ +internal object ElevationTokens { + val Level0 = 0.0.dp + val Level1 = 1.0.dp + val Level2 = 3.0.dp + val Level3 = 6.0.dp + val Level4 = 8.0.dp + val Level5 = 12.0.dp +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt index aefd534da63c..0eaaf970f37f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt @@ -16,55 +16,314 @@ package com.android.credentialmanager.common.ui +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.outlined.Lock import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SuggestionChip import androidx.compose.material3.SuggestionChipDefaults +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import com.android.credentialmanager.R import com.android.credentialmanager.ui.theme.EntryShape +import com.android.credentialmanager.ui.theme.Shapes -@OptIn(ExperimentalMaterial3Api::class) @Composable fun Entry( - onClick: () -> Unit, - label: @Composable () -> Unit, modifier: Modifier = Modifier, - icon: @Composable (() -> Unit)? = null, + onClick: () -> Unit, + entryHeadlineText: String, + entrySecondLineText: String? = null, + entryThirdLineText: String? = null, + /** Supply one and only one of the [iconImageBitmap], [iconImageVector], or [iconPainter] for + * drawing the leading icon. */ + iconImageBitmap: ImageBitmap? = null, + shouldApplyIconImageBitmapTint: Boolean = false, + iconImageVector: ImageVector? = null, + iconPainter: Painter? = null, + /** This will replace the [entrySecondLineText] value and render the text along with a + * mask on / off toggle for hiding / displaying the password value. */ + passwordValue: String? = null, + /** If true, draws a trailing lock icon. */ + isLockedAuthEntry: Boolean = false, ) { SuggestionChip( - modifier = modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth().wrapContentHeight(), onClick = onClick, shape = EntryShape.FullSmallRoundedCorner, - label = label, - icon = icon, + label = { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(all = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.wrapContentSize()) { + SmallTitleText(entryHeadlineText) + if (passwordValue != null) { + Row(modifier = Modifier.fillMaxWidth()) { + val visualTransformation = remember { PasswordVisualTransformation() } + val originalPassword by remember { + mutableStateOf(passwordValue) + } + val displayedPassword = remember { + mutableStateOf( + visualTransformation.filter( + AnnotatedString(originalPassword) + ).text.text + ) + } + BodySmallText(displayedPassword.value) + ToggleVisibilityButton( + modifier = Modifier.padding(start = 5.dp).size(24.dp), + onToggle = { + if (it) { + displayedPassword.value = originalPassword + } else { + displayedPassword.value = visualTransformation.filter( + AnnotatedString(originalPassword) + ).text.text + } + }, + ) + } + } else if (entrySecondLineText != null) { + BodySmallText(entrySecondLineText) + } + if (entryThirdLineText != null) { + BodySmallText(entryThirdLineText) + } + } + if (isLockedAuthEntry) { + Box(modifier = Modifier.wrapContentSize().padding(end = 16.dp)) { + Icon( + imageVector = Icons.Outlined.Lock, + // Decorative purpose only. + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + }, + icon = + if (iconImageBitmap != null) { + if (shouldApplyIconImageBitmapTint) { + { + Box(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + Icon( + modifier = Modifier.size(24.dp), + bitmap = iconImageBitmap, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + // Decorative purpose only. + contentDescription = null, + ) + } + } + } else { + { + Box(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + Image( + modifier = Modifier.size(24.dp), + bitmap = iconImageBitmap, + // Decorative purpose only. + contentDescription = null, + ) + } + } + } + } else if (iconImageVector != null) { + { + Box(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = iconImageVector, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + // Decorative purpose only. + contentDescription = null, + ) + } + } + } else if (iconPainter != null) { + { + Box(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + Icon( + modifier = Modifier.size(24.dp), + painter = iconPainter, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + // Decorative purpose only. + contentDescription = null, + ) + } + } + } else { + null + }, border = null, colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + ElevationTokens.Level3 + ), + // TODO: remove? labelColor = MaterialTheme.colorScheme.onSurfaceVariant, iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, ), ) } -@OptIn(ExperimentalMaterial3Api::class) +/** + * A variation of the normal entry in that its background is transparent and the paddings are + * different (no horizontal padding). + */ @Composable -fun TransparentBackgroundEntry( +fun ActionEntry( onClick: () -> Unit, - label: @Composable () -> Unit, - modifier: Modifier = Modifier, - icon: @Composable (() -> Unit)? = null, + entryHeadlineText: String, + entrySecondLineText: String? = null, + iconImageBitmap: ImageBitmap, ) { SuggestionChip( - modifier = modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().wrapContentHeight(), onClick = onClick, - label = label, - icon = icon, + shape = Shapes.large, + label = { + Column(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + SmallTitleText(entryHeadlineText) + if (entrySecondLineText != null) { + BodySmallText(entrySecondLineText) + } + } + }, + icon = { + Box(modifier = Modifier.wrapContentSize().padding(vertical = 16.dp)) { + Image( + modifier = Modifier.size(24.dp), + bitmap = iconImageBitmap, + // Decorative purpose only. + contentDescription = null, + ) + } + }, border = null, colors = SuggestionChipDefaults.suggestionChipColors( containerColor = Color.Transparent, ), ) +} + +/** + * A single row of leading icon and text describing a benefit of passkeys, used by the + * [com.android.credentialmanager.createflow.PasskeyIntroCard]. + */ +@Composable +fun PasskeyBenefitRow( + leadingIconPainter: Painter, + text: String, +) { + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = leadingIconPainter, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + // Decorative purpose only. + contentDescription = null, + ) + BodyMediumText(text = text) + } +} + +/** + * A single row of one or two CTA buttons for continuing or cancelling the current step. + */ +@Composable +fun CtaButtonRow( + leftButton: (@Composable () -> Unit)? = null, + rightButton: (@Composable () -> Unit)? = null, +) { + Row( + horizontalArrangement = + if (leftButton == null) Arrangement.End + else if (rightButton == null) Arrangement.Start + else Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + if (leftButton != null) { + leftButton() + } + if (rightButton != null) { + rightButton() + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MoreOptionTopAppBar( + text: String, + onNavigationIconClicked: () -> Unit, +) { + TopAppBar( + title = { + LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp)) + }, + navigationIcon = { + IconButton( + modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp), + onClick = onNavigationIconClicked + ) { + Box( + modifier = Modifier.size(48.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = stringResource( + R.string.accessibility_back_arrow_button + ), + modifier = Modifier.size(16.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + modifier = Modifier.padding(top = 12.dp, bottom = 8.dp) + ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt new file mode 100644 index 000000000000..ac79844457a8 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt @@ -0,0 +1,81 @@ +/* + * 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.credentialmanager.common.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp + +/** Tinted primary; centered; 32X32. */ +@Composable +fun HeadlineIcon(bitmap: ImageBitmap, tint: Color? = null) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + ) { + Icon( + modifier = Modifier.size(32.dp), + bitmap = bitmap, + tint = tint ?: MaterialTheme.colorScheme.primary, + // Decorative purpose only. + contentDescription = null, + ) + } +} + +@Composable +fun HeadlineIcon(imageVector: ImageVector) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + ) { + Icon( + modifier = Modifier.size(32.dp), + imageVector = imageVector, + tint = MaterialTheme.colorScheme.primary, + // Decorative purpose only. + contentDescription = null, + ) + } +} + +@Composable +fun HeadlineIcon(painter: Painter) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + ) { + Icon( + modifier = Modifier.size(32.dp), + painter = painter, + tint = MaterialTheme.colorScheme.primary, + // Decorative purpose only. + contentDescription = null, + ) + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt new file mode 100644 index 000000000000..c63771e49516 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt @@ -0,0 +1,48 @@ +/* + * 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.credentialmanager.common.ui + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun CredentialListSectionHeader(text: String) { + InternalSectionHeader(text, MaterialTheme.colorScheme.onSurfaceVariant) +} + +@Composable +fun MoreAboutPasskeySectionHeader(text: String) { + InternalSectionHeader(text, MaterialTheme.colorScheme.onSurface) +} + +@Composable +private fun InternalSectionHeader(text: String, color: Color) { + Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) { + SectionHeaderText( + text, + modifier = Modifier.padding(top = 20.dp, bottom = 8.dp), + color = color + ) + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt new file mode 100644 index 000000000000..514ff90be8d7 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt @@ -0,0 +1,92 @@ +/* + * 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.credentialmanager.common.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.android.credentialmanager.R +import com.android.credentialmanager.common.material.Scrim +import com.android.credentialmanager.ui.theme.Shapes + +@Composable +fun Snackbar( + contentText: String, + action: (@Composable () -> Unit)? = null, + onDismiss: () -> Unit, +) { + BoxWithConstraints { + Box(Modifier.fillMaxSize()) { + Scrim( + color = Color.Transparent, + onDismiss = onDismiss, + visible = true + ) + } + Box( + modifier = Modifier + .align(Alignment.BottomCenter).wrapContentSize().padding(bottom = 18.dp) + ) { + Card( + shape = Shapes.medium, + modifier = Modifier.wrapContentSize(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.inverseSurface, + ) + ) { + Row( + modifier = Modifier.wrapContentSize(), + verticalAlignment = Alignment.CenterVertically, + ) { + SnackbarContentText(contentText, modifier = Modifier.padding( + top = 18.dp, bottom = 18.dp, start = 24.dp, + )) + if (action != null) { + action() + } + IconButton(onClick = onDismiss, modifier = Modifier.padding( + top = 4.dp, bottom = 4.dp, start = 2.dp, end = 10.dp, + )) { + Icon( + Icons.Filled.Close, + contentDescription = stringResource( + R.string.accessibility_snackbar_dismiss + ), + tint = MaterialTheme.colorScheme.inverseOnSurface, + ) + } + } + } + } + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt index 3a66dda73832..8af729ecdc25 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt @@ -16,69 +16,144 @@ package com.android.credentialmanager.common.ui +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign +/** + * The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X". + * + * Centered horizontally; headline-small typography; on-surface color. + */ @Composable -fun TextOnSurface( - text: String, - modifier: Modifier = Modifier, - textAlign: TextAlign? = null, - style: TextStyle, -) { - TextInternal( +fun HeadlineText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentSize(), text = text, color = MaterialTheme.colorScheme.onSurface, - modifier = modifier, - textAlign = textAlign, - style = style, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.headlineSmall, ) } +/** + * Body-medium typography; on-surface-variant color. + */ @Composable -fun TextSecondary( - text: String, - modifier: Modifier = Modifier, - textAlign: TextAlign? = null, - style: TextStyle, -) { - TextInternal( +fun BodyMediumText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentSize(), text = text, - color = MaterialTheme.colorScheme.secondary, - modifier = modifier, - textAlign = textAlign, - style = style, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyMedium, ) } +/** + * Body-small typography; on-surface-variant color. + */ @Composable -fun TextOnSurfaceVariant( - text: String, - modifier: Modifier = Modifier, - textAlign: TextAlign? = null, - style: TextStyle, -) { - TextInternal( +fun BodySmallText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentSize(), text = text, color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = modifier, - textAlign = textAlign, - style = style, + style = MaterialTheme.typography.bodySmall, + ) +} + +/** + * Title-large typography; on-surface color. + */ +@Composable +fun LargeTitleText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentSize(), + text = text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.titleLarge, ) } +/** + * Title-small typography; on-surface color. + */ +@Composable +fun SmallTitleText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentSize(), + text = text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.titleSmall, + ) +} + +/** + * Title-small typography. + */ @Composable -private fun TextInternal( - text: String, - color: Color, - modifier: Modifier, - textAlign: TextAlign?, - style: TextStyle, -) { - Text(text = text, color = color, modifier = modifier, textAlign = textAlign, style = style) +fun SectionHeaderText(text: String, modifier: Modifier = Modifier, color: Color) { + Text( + modifier = modifier.wrapContentSize(), + text = text, + color = color, + style = MaterialTheme.typography.titleSmall, + ) +} + +/** + * Body-medium typography; inverse-on-surface color. + */ +@Composable +fun SnackbarContentText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentSize(), + text = text, + color = MaterialTheme.colorScheme.inverseOnSurface, + style = MaterialTheme.typography.bodyMedium, + ) +} + +/** + * Label-large typography; inverse-primary color. + */ +@Composable +fun SnackbarActionText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentSize(), + text = text, + color = MaterialTheme.colorScheme.inversePrimary, + style = MaterialTheme.typography.labelLarge, + ) +} + +/** + * Label-large typography; on-surface-variant color; centered. + */ +@Composable +fun LargeLabelTextOnSurfaceVariant(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentSize(), + text = text, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.labelLarge, + ) +} + +/** + * Label-large typography; color following parent spec; centered. + */ +@Composable +fun LargeLabelText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentSize(), + text = text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.labelLarge, + ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 942eb490aa20..00b7d0057746 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -11,36 +11,24 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.filled.Add 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.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel @@ -49,15 +37,20 @@ import com.android.credentialmanager.common.BaseEntry import com.android.credentialmanager.common.CredentialType import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.ui.ActionButton +import com.android.credentialmanager.common.ui.BodyMediumText +import com.android.credentialmanager.common.ui.BodySmallText import com.android.credentialmanager.common.ui.ConfirmButton +import com.android.credentialmanager.common.ui.CredentialContainerCard +import com.android.credentialmanager.common.ui.CtaButtonRow import com.android.credentialmanager.common.ui.Entry +import com.android.credentialmanager.common.ui.HeadlineIcon +import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.ModalBottomSheet -import com.android.credentialmanager.common.ui.TextOnSurface -import com.android.credentialmanager.common.ui.TextSecondary -import com.android.credentialmanager.common.ui.TextOnSurfaceVariant -import com.android.credentialmanager.common.ui.ContainerCard -import com.android.credentialmanager.common.ui.ToggleVisibilityButton -import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme +import com.android.credentialmanager.common.ui.MoreAboutPasskeySectionHeader +import com.android.credentialmanager.common.ui.MoreOptionTopAppBar +import com.android.credentialmanager.common.ui.SheetContainerCard +import com.android.credentialmanager.common.ui.PasskeyBenefitRow +import com.android.credentialmanager.common.ui.HeadlineText @Composable fun CreateCredentialScreen( @@ -73,7 +66,7 @@ fun CreateCredentialScreen( when (viewModel.uiState.providerActivityState) { ProviderActivityState.NOT_APPLICABLE -> { when (createCredentialUiState.currentScreenState) { - CreateScreenState.PASSKEY_INTRO -> ConfirmationCard( + CreateScreenState.PASSKEY_INTRO -> PasskeyIntroCard( onConfirm = viewModel::createFlowOnConfirmIntro, onLearnMore = viewModel::createFlowOnLearnMore, ) @@ -157,14 +150,13 @@ fun CreateCredentialScreen( ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable -fun ConfirmationCard( +fun PasskeyIntroCard( onConfirm: () -> Unit, onLearnMore: () -> Unit, ) { - ContainerCard() { - Column() { + SheetContainerCard { + item { val onboardingImageResource = remember { mutableStateOf(R.drawable.ic_passkeys_onboarding) } @@ -173,101 +165,56 @@ fun ConfirmationCard( } else { onboardingImageResource.value = R.drawable.ic_passkeys_onboarding } - Image( - painter = painterResource(onboardingImageResource.value), - contentDescription = null, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - .padding(top = 24.dp, bottom = 12.dp).size(316.dp, 168.dp) - ) - TextOnSurface( - text = stringResource(R.string.passkey_creation_intro_title), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier - .padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - ) - Divider( - thickness = 16.dp, - color = Color.Transparent - ) Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) + modifier = Modifier.wrapContentHeight().fillMaxWidth(), + horizontalArrangement = Arrangement.Center, ) { Image( - modifier = Modifier.size(24.dp), - painter = painterResource(R.drawable.ic_passkeys_onboarding_password), - contentDescription = null - ) - TextSecondary( - text = stringResource(R.string.passkey_creation_intro_body_password), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 16.dp, end = 4.dp), + painter = painterResource(onboardingImageResource.value), + contentDescription = null, + modifier = Modifier.size(316.dp, 168.dp) ) } - Divider( - thickness = 16.dp, - color = Color.Transparent + } + item { Divider(thickness = 16.dp, color = Color.Transparent) } + item { HeadlineText(text = stringResource(R.string.passkey_creation_intro_title)) } + item { Divider(thickness = 16.dp, color = Color.Transparent) } + item { + PasskeyBenefitRow( + leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_password), + text = stringResource(R.string.passkey_creation_intro_body_password), ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint), - contentDescription = null - ) - TextSecondary( - text = stringResource(R.string.passkey_creation_intro_body_fingerprint), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 16.dp, end = 4.dp), - ) - } - Divider( - thickness = 16.dp, - color = Color.Transparent + } + item { Divider(thickness = 16.dp, color = Color.Transparent) } + item { + PasskeyBenefitRow( + leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint), + text = stringResource(R.string.passkey_creation_intro_body_fingerprint), ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(R.drawable.ic_passkeys_onboarding_device), - contentDescription = null - ) - TextSecondary( - text = stringResource(R.string.passkey_creation_intro_body_device), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 16.dp, end = 4.dp), - ) - } - Divider( - thickness = 32.dp, - color = Color.Transparent + } + item { Divider(thickness = 16.dp, color = Color.Transparent) } + item { + PasskeyBenefitRow( + leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_device), + text = stringResource(R.string.passkey_creation_intro_body_device), ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - ActionButton( - stringResource(R.string.string_learn_more), - onClick = onLearnMore - ) - ConfirmButton( - stringResource(R.string.string_continue), - onClick = onConfirm - ) - } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 18.dp) + } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + + item { + CtaButtonRow( + leftButton = { + ActionButton( + stringResource(R.string.string_learn_more), + onClick = onLearnMore + ) + }, + rightButton = { + ConfirmButton( + stringResource(R.string.string_continue), + onClick = onConfirm + ) + }, ) } } @@ -283,16 +230,11 @@ fun ProviderSelectionCard( onDisabledProvidersSelected: () -> Unit, onMoreOptionsSelected: () -> Unit, ) { - ContainerCard() { - Column() { - Icon( - bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - .padding(top = 24.dp, bottom = 16.dp).size(32.dp) - ) - TextOnSurface( + SheetContainerCard { + item { HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) } + item { Divider(thickness = 16.dp, color = Color.Transparent) } + item { + HeadlineText( text = stringResource( R.string.choose_provider_title, when (requestDisplayInfo.type) { @@ -302,83 +244,56 @@ fun ProviderSelectionCard( stringResource(R.string.passwords) CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) } - ), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - ) - Divider( - thickness = 16.dp, - color = Color.Transparent - ) - TextSecondary( - text = stringResource(R.string.choose_provider_body), - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(horizontal = 28.dp), + ) ) - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier.padding( - start = 24.dp, - end = 24.dp, - top = 24.dp, - bottom = if (hasRemoteEntry) 24.dp else 16.dp - ).align(alignment = Alignment.CenterHorizontally), - ) { - LazyColumn( + } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + + item { BodyMediumText(text = stringResource(R.string.choose_provider_body)) } + item { Divider(thickness = 16.dp, color = Color.Transparent) } + item { + CredentialContainerCard { + Column( verticalArrangement = Arrangement.spacedBy(2.dp) ) { sortedCreateOptionsPairs.forEach { entry -> - item { - MoreOptionsInfoRow( - requestDisplayInfo = requestDisplayInfo, - providerInfo = entry.second, - createOptionInfo = entry.first, - onOptionSelected = { - onOptionSelected( - ActiveEntry( - entry.second, - entry.first - ) + MoreOptionsInfoRow( + requestDisplayInfo = requestDisplayInfo, + providerInfo = entry.second, + createOptionInfo = entry.first, + onOptionSelected = { + onOptionSelected( + ActiveEntry( + entry.second, + entry.first ) - } - ) - } - } - item { - MoreOptionsDisabledProvidersRow( - disabledProviders = disabledProviderList, - onDisabledProvidersSelected = - onDisabledProvidersSelected, + ) + } ) } + MoreOptionsDisabledProvidersRow( + disabledProviders = disabledProviderList, + onDisabledProvidersSelected = onDisabledProvidersSelected, + ) } } - if (hasRemoteEntry) { - Divider( - thickness = 24.dp, - color = Color.Transparent + } + if (hasRemoteEntry) { + item { Divider(thickness = 24.dp, color = Color.Transparent) } + item { + CtaButtonRow( + leftButton = { + ActionButton( + stringResource(R.string.string_more_options), + onMoreOptionsSelected + ) + } ) - Row( - horizontalArrangement = Arrangement.Start, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - ActionButton( - stringResource(R.string.string_more_options), - onMoreOptionsSelected - ) - } } - Divider( - thickness = 24.dp, - color = Color.Transparent, - ) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsSelectionCard( requestDisplayInfo: RequestDisplayInfo, @@ -393,158 +308,104 @@ fun MoreOptionsSelectionCard( onDisabledProvidersSelected: () -> Unit, onRemoteEntrySelected: (BaseEntry) -> Unit, ) { - ContainerCard() { - Column() { - TopAppBar( - title = { - TextOnSurface( - text = - stringResource( - R.string.save_credential_to_title, - when (requestDisplayInfo.type) { - CredentialType.PASSKEY -> - stringResource(R.string.passkey) - CredentialType.PASSWORD -> - stringResource(R.string.password) - CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) - } - ), - style = MaterialTheme.typography.titleMedium, - ) - }, - navigationIcon = { - IconButton( - onClick = - if (isFromProviderSelection) - onBackProviderSelectionButtonSelected - else onBackCreationSelectionButtonSelected - ) { - Icon( - Icons.Filled.ArrowBack, - stringResource(R.string.accessibility_back_arrow_button) - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), - modifier = Modifier.padding(top = 12.dp) - ) - Divider( - thickness = 8.dp, - color = Color.Transparent - ) - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally) - ) { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(2.dp) - ) { + SheetContainerCard(topAppBar = { + MoreOptionTopAppBar( + text = stringResource( + R.string.save_credential_to_title, + when (requestDisplayInfo.type) { + CredentialType.PASSKEY -> + stringResource(R.string.passkey) + CredentialType.PASSWORD -> + stringResource(R.string.password) + CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) + } + ), + onNavigationIconClicked = + if (isFromProviderSelection) onBackProviderSelectionButtonSelected + else onBackCreationSelectionButtonSelected, + ) + }) { + item { Divider(thickness = 16.dp, color = Color.Transparent) } + item { + CredentialContainerCard { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { // Only in the flows with default provider(not first time use) we can show the // createOptions here, or they will be shown on ProviderSelectionCard if (hasDefaultProvider) { sortedCreateOptionsPairs.forEach { entry -> - item { - MoreOptionsInfoRow( - requestDisplayInfo = requestDisplayInfo, - providerInfo = entry.second, - createOptionInfo = entry.first, - onOptionSelected = { - onOptionSelected( - ActiveEntry( - entry.second, - entry.first - ) + MoreOptionsInfoRow( + requestDisplayInfo = requestDisplayInfo, + providerInfo = entry.second, + createOptionInfo = entry.first, + onOptionSelected = { + onOptionSelected( + ActiveEntry( + entry.second, + entry.first ) - }) - } - } - item { - MoreOptionsDisabledProvidersRow( - disabledProviders = disabledProviderList, - onDisabledProvidersSelected = - onDisabledProvidersSelected, + ) + } ) } + MoreOptionsDisabledProvidersRow( + disabledProviders = disabledProviderList, + onDisabledProvidersSelected = + onDisabledProvidersSelected, + ) } enabledProviderList.forEach { if (it.remoteEntry != null) { - item { - RemoteEntryRow( - remoteInfo = it.remoteEntry!!, - onRemoteEntrySelected = onRemoteEntrySelected, - ) - } + RemoteEntryRow( + remoteInfo = it.remoteEntry!!, + onRemoteEntrySelected = onRemoteEntrySelected, + ) return@forEach } } } } - Divider( - thickness = 8.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 40.dp) - ) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsRowIntroCard( providerInfo: EnabledProviderInfo, onChangeDefaultSelected: () -> Unit, onUseOnceSelected: () -> Unit, ) { - ContainerCard() { - Column() { - Icon( - Icons.Outlined.NewReleases, - contentDescription = null, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - .padding(all = 24.dp), - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - ) - TextOnSurface( + SheetContainerCard { + item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + item { + HeadlineText( text = stringResource( R.string.use_provider_for_all_title, providerInfo.displayName - ), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - ) - TextSecondary( - text = stringResource(R.string.use_provider_for_all_description), - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(all = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - ActionButton( - stringResource(R.string.use_once), - onClick = onUseOnceSelected - ) - ConfirmButton( - stringResource(R.string.set_as_default), - onClick = onChangeDefaultSelected ) - } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 40.dp) + ) + } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + item { BodyMediumText(text = stringResource(R.string.use_provider_for_all_description)) } + item { + CtaButtonRow( + leftButton = { + ActionButton( + stringResource(R.string.use_once), + onClick = onUseOnceSelected + ) + }, + rightButton = { + ConfirmButton( + stringResource(R.string.set_as_default), + onClick = onChangeDefaultSelected + ) + }, ) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun CreationSelectionCard( requestDisplayInfo: RequestDisplayInfo, @@ -556,26 +417,18 @@ fun CreationSelectionCard( onMoreOptionsSelected: () -> Unit, hasDefaultProvider: Boolean, ) { - ContainerCard() { - Column() { - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - Icon( + SheetContainerCard { + item { + HeadlineIcon( bitmap = providerInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null, tint = Color.Unspecified, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally).size(32.dp) - ) - TextSecondary( - text = providerInfo.displayName, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(vertical = 10.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, ) - TextOnSurface( + } + item { Divider(thickness = 4.dp, color = Color.Transparent) } + item { LargeLabelTextOnSurfaceVariant(text = providerInfo.displayName) } + item { Divider(thickness = 16.dp, color = Color.Transparent) } + item { + HeadlineText( text = when (requestDisplayInfo.type) { CredentialType.PASSKEY -> stringResource( R.string.choose_create_option_passkey_title, @@ -589,80 +442,67 @@ fun CreationSelectionCard( R.string.choose_create_option_sign_in_title, requestDisplayInfo.appName ) - }, - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, + } ) - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .padding(all = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - ) { + } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + item { + CredentialContainerCard { PrimaryCreateOptionRow( requestDisplayInfo = requestDisplayInfo, entryInfo = createOptionInfo, onOptionSelected = onOptionSelected ) } - var createOptionsSize = 0 - var remoteEntry: RemoteInfo? = null - enabledProviderList.forEach { enabledProvider -> - if (enabledProvider.remoteEntry != null) { - remoteEntry = enabledProvider.remoteEntry - } - createOptionsSize += enabledProvider.createOptions.size - } - val shouldShowMoreOptionsButton = if (!hasDefaultProvider) { - // User has already been presented with all options on the default provider - // selection screen. Don't show them again. Therefore, only show the more option - // button if remote option is present. - remoteEntry != null - } else { - createOptionsSize > 1 || remoteEntry != null + } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + var createOptionsSize = 0 + var remoteEntry: RemoteInfo? = null + enabledProviderList.forEach { enabledProvider -> + if (enabledProvider.remoteEntry != null) { + remoteEntry = enabledProvider.remoteEntry } - Row( - horizontalArrangement = - if (shouldShowMoreOptionsButton) Arrangement.SpaceBetween else Arrangement.End, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - if (shouldShowMoreOptionsButton) { - ActionButton( - stringResource(R.string.string_more_options), - onClick = onMoreOptionsSelected + createOptionsSize += enabledProvider.createOptions.size + } + val shouldShowMoreOptionsButton = if (!hasDefaultProvider) { + // User has already been presented with all options on the default provider + // selection screen. Don't show them again. Therefore, only show the more option + // button if remote option is present. + remoteEntry != null + } else { + createOptionsSize > 1 || remoteEntry != null + } + item { + CtaButtonRow( + leftButton = if (shouldShowMoreOptionsButton) { + { + ActionButton( + stringResource(R.string.string_more_options), + onMoreOptionsSelected + ) + } + } else null, + rightButton = { + ConfirmButton( + stringResource(R.string.string_continue), + onClick = onConfirm ) - } - ConfirmButton( - stringResource(R.string.string_continue), - onClick = onConfirm - ) - } - if (createOptionInfo.footerDescription != null) { + }, + ) + } + if (createOptionInfo.footerDescription != null) { + item { Divider( thickness = 1.dp, - color = Color.LightGray, - modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 18.dp) - ) - TextSecondary( - text = createOptionInfo.footerDescription, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding( - start = 29.dp, top = 8.dp, bottom = 18.dp, end = 28.dp - ) + color = MaterialTheme.colorScheme.outlineVariant, + modifier = Modifier.padding(vertical = 16.dp) ) } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 16.dp) - ) + item { BodySmallText(text = createOptionInfo.footerDescription) } } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun ExternalOnlySelectionCard( requestDisplayInfo: RequestDisplayInfo, @@ -670,148 +510,74 @@ fun ExternalOnlySelectionCard( onOptionSelected: (BaseEntry) -> Unit, onConfirm: () -> Unit, ) { - ContainerCard() { - Column() { - Icon( - painter = painterResource(R.drawable.ic_other_devices), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - .padding(all = 24.dp).size(32.dp) - ) - TextOnSurface( - text = stringResource(R.string.create_passkey_in_other_device_title), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - ) - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - ) { + SheetContainerCard { + item { HeadlineIcon(painter = painterResource(R.drawable.ic_other_devices)) } + item { Divider(thickness = 16.dp, color = Color.Transparent) } + item { HeadlineText(text = stringResource(R.string.create_passkey_in_other_device_title)) } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + item { + CredentialContainerCard { PrimaryCreateOptionRow( requestDisplayInfo = requestDisplayInfo, entryInfo = activeRemoteEntry, onOptionSelected = onOptionSelected ) } - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - Row( - horizontalArrangement = Arrangement.End, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - ConfirmButton( - stringResource(R.string.string_continue), - onClick = onConfirm - ) - } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 16.dp) + } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + item { + CtaButtonRow( + rightButton = { + ConfirmButton( + stringResource(R.string.string_continue), + onClick = onConfirm + ) + }, ) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreAboutPasskeysIntroCard( onBackPasskeyIntroButtonSelected: () -> Unit, ) { - ContainerCard() { - Column() { - TopAppBar( - title = { - TextOnSurface( - text = - stringResource( - R.string.more_about_passkeys_title - ), - style = MaterialTheme.typography.titleMedium, - ) - }, - navigationIcon = { - IconButton( - onClick = onBackPasskeyIntroButtonSelected - ) { - Icon( - Icons.Filled.ArrowBack, - stringResource(R.string.accessibility_back_arrow_button) - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), - modifier = Modifier.padding(top = 12.dp) + SheetContainerCard( + topAppBar = { + MoreOptionTopAppBar( + text = stringResource(R.string.more_about_passkeys_title), + onNavigationIconClicked = onBackPasskeyIntroButtonSelected, ) - Column( - modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 68.dp) - ) { - TextOnSurfaceVariant( - text = stringResource(R.string.passwordless_technology_title), - style = MaterialTheme.typography.titleLarge, - ) - TextSecondary( - text = stringResource(R.string.passwordless_technology_detail), - style = MaterialTheme.typography.bodyMedium, - ) - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - TextOnSurfaceVariant( - text = stringResource(R.string.public_key_cryptography_title), - style = MaterialTheme.typography.titleLarge, - ) - TextSecondary( - text = stringResource(R.string.public_key_cryptography_detail), - style = MaterialTheme.typography.bodyMedium, - ) - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - TextOnSurfaceVariant( - text = stringResource(R.string.improved_account_security_title), - style = MaterialTheme.typography.titleLarge, - ) - TextSecondary( - text = stringResource(R.string.improved_account_security_detail), - style = MaterialTheme.typography.bodyMedium, - ) - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - TextOnSurfaceVariant( - text = stringResource(R.string.seamless_transition_title), - style = MaterialTheme.typography.titleLarge, - ) - TextSecondary( - text = stringResource(R.string.seamless_transition_detail), - style = MaterialTheme.typography.bodyMedium, - ) - } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 24.dp) + }, + contentVerticalArrangement = Arrangement.spacedBy(8.dp) + ) { + item { + MoreAboutPasskeySectionHeader( + text = stringResource(R.string.passwordless_technology_title) + ) + BodyMediumText(text = stringResource(R.string.passwordless_technology_detail)) + } + item { + MoreAboutPasskeySectionHeader( + text = stringResource(R.string.public_key_cryptography_title) ) + BodyMediumText(text = stringResource(R.string.public_key_cryptography_detail)) + } + item { + MoreAboutPasskeySectionHeader( + text = stringResource(R.string.improved_account_security_title) + ) + BodyMediumText(text = stringResource(R.string.improved_account_security_detail)) + } + item { + MoreAboutPasskeySectionHeader( + text = stringResource(R.string.seamless_transition_title) + ) + BodyMediumText(text = stringResource(R.string.seamless_transition_detail)) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun PrimaryCreateOptionRow( requestDisplayInfo: RequestDisplayInfo, @@ -820,115 +586,37 @@ fun PrimaryCreateOptionRow( ) { Entry( onClick = { onOptionSelected(entryInfo) }, - icon = { - if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) { - Image( - bitmap = entryInfo.profileIcon.toBitmap().asImageBitmap(), - contentDescription = null, - modifier = Modifier.padding(start = 10.dp).size(32.dp), - ) - } else { - Icon( - bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.padding(start = 10.dp).size(32.dp), - ) - } + iconImageBitmap = + if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) { + entryInfo.profileIcon.toBitmap().asImageBitmap() + } else { + requestDisplayInfo.typeIcon.toBitmap().asImageBitmap() }, - label = { - Column() { - when (requestDisplayInfo.type) { - CredentialType.PASSKEY -> { - TextOnSurfaceVariant( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), - ) - TextSecondary( - text = if (requestDisplayInfo.subtitle != null) { - requestDisplayInfo.subtitle + " • " + stringResource( - R.string.passkey_before_subtitle - ) - } else { - stringResource(R.string.passkey_before_subtitle) - }, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } - CredentialType.PASSWORD -> { - TextOnSurfaceVariant( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), - ) - Row( - modifier = Modifier.fillMaxWidth().padding( - top = 4.dp, bottom = 16.dp, - start = 5.dp - ), - verticalAlignment = Alignment.CenterVertically - ) { - val visualTransformation = remember { PasswordVisualTransformation() } - // This subtitle would never be null for create password - val originalPassword by remember { - mutableStateOf(requestDisplayInfo.subtitle ?: "") - } - val displayedPassword = remember { - mutableStateOf( - visualTransformation.filter( - AnnotatedString(originalPassword) - ).text.text - ) - } - TextSecondary( - text = displayedPassword.value, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(top = 4.dp, bottom = 4.dp), - ) - - ToggleVisibilityButton(modifier = Modifier.padding(start = 4.dp) - .height(24.dp).width(24.dp), onToggle = { - if (it) { - displayedPassword.value = originalPassword - } else { - displayedPassword.value = visualTransformation.filter( - AnnotatedString(originalPassword) - ).text.text - } - }) - } - } - CredentialType.UNKNOWN -> { - if (requestDisplayInfo.subtitle != null) { - TextOnSurfaceVariant( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), - ) - TextOnSurfaceVariant( - text = requestDisplayInfo.subtitle, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else { - TextOnSurfaceVariant( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding( - top = 16.dp, bottom = 16.dp, start = 5.dp - ), - ) - } - } + shouldApplyIconImageBitmapTint = !(entryInfo is CreateOptionInfo && + entryInfo.profileIcon != null), + entryHeadlineText = requestDisplayInfo.title, + entrySecondLineText = when (requestDisplayInfo.type) { + CredentialType.PASSKEY -> { + if (requestDisplayInfo.subtitle != null) { + requestDisplayInfo.subtitle + " • " + stringResource( + R.string.passkey_before_subtitle + ) + } else { + stringResource(R.string.passkey_before_subtitle) } } - } + // Set passwordValue instead + CredentialType.PASSWORD -> null + CredentialType.UNKNOWN -> requestDisplayInfo.subtitle + }, + passwordValue = + if (requestDisplayInfo.type == CredentialType.PASSWORD) + // This subtitle would never be null for create password + requestDisplayInfo.subtitle ?: "" + else null, ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsInfoRow( requestDisplayInfo: RequestDisplayInfo, @@ -938,93 +626,46 @@ fun MoreOptionsInfoRow( ) { Entry( onClick = onOptionSelected, - icon = { - Image( - modifier = Modifier.padding(start = 10.dp).size(32.dp), - bitmap = providerInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null - ) - }, - label = { - Column() { - TextOnSurfaceVariant( - text = providerInfo.displayName, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), + iconImageBitmap = providerInfo.icon.toBitmap().asImageBitmap(), + entryHeadlineText = providerInfo.displayName, + entrySecondLineText = createOptionInfo.userProviderDisplayName, + entryThirdLineText = + if (requestDisplayInfo.type == CredentialType.PASSKEY || + requestDisplayInfo.type == CredentialType.PASSWORD) { + if (createOptionInfo.passwordCount != null && + createOptionInfo.passkeyCount != null + ) { + stringResource( + R.string.more_options_usage_passwords_passkeys, + createOptionInfo.passwordCount, + createOptionInfo.passkeyCount ) - if (createOptionInfo.userProviderDisplayName != null) { - TextSecondary( - text = createOptionInfo.userProviderDisplayName, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 5.dp), - ) - } - if (requestDisplayInfo.type == CredentialType.PASSKEY || - requestDisplayInfo.type == CredentialType.PASSWORD - ) { - if (createOptionInfo.passwordCount != null && - createOptionInfo.passkeyCount != null - ) { - TextSecondary( - text = - stringResource( - R.string.more_options_usage_passwords_passkeys, - createOptionInfo.passwordCount, - createOptionInfo.passkeyCount - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else if (createOptionInfo.passwordCount != null) { - TextSecondary( - text = - stringResource( - R.string.more_options_usage_passwords, - createOptionInfo.passwordCount - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else if (createOptionInfo.passkeyCount != null) { - TextSecondary( - text = - stringResource( - R.string.more_options_usage_passkeys, - createOptionInfo.passkeyCount - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else { - Divider( - thickness = 16.dp, - color = Color.Transparent, - ) - } - } else { - if (createOptionInfo.totalCredentialCount != null) { - TextSecondary( - text = - stringResource( - R.string.more_options_usage_credentials, - createOptionInfo.totalCredentialCount - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else { - Divider( - thickness = 16.dp, - color = Color.Transparent, - ) - } - } + } else if (createOptionInfo.passwordCount != null) { + stringResource( + R.string.more_options_usage_passwords, + createOptionInfo.passwordCount + ) + } else if (createOptionInfo.passkeyCount != null) { + stringResource( + R.string.more_options_usage_passkeys, + createOptionInfo.passkeyCount + ) + } else { + null } - } + } else { + if (createOptionInfo.totalCredentialCount != null) { + stringResource( + R.string.more_options_usage_credentials, + createOptionInfo.totalCredentialCount + ) + } else { + null + } + }, ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsDisabledProvidersRow( disabledProviders: List<ProviderInfo>?, @@ -1033,36 +674,15 @@ fun MoreOptionsDisabledProvidersRow( if (disabledProviders != null && disabledProviders.isNotEmpty()) { Entry( onClick = onDisabledProvidersSelected, - icon = { - Icon( - Icons.Filled.Add, - contentDescription = null, - modifier = Modifier.padding(start = 16.dp), - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - ) + iconImageVector = Icons.Filled.Add, + entryHeadlineText = stringResource(R.string.other_password_manager), + entrySecondLineText = disabledProviders.joinToString(separator = " • ") { + it.displayName }, - label = { - Column() { - TextOnSurfaceVariant( - text = stringResource(R.string.other_password_manager), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), - ) - // TODO: Update the subtitle once design is confirmed - TextSecondary( - text = disabledProviders.joinToString(separator = " • ") { - it.displayName - }, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } - } ) } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun RemoteEntryRow( remoteInfo: RemoteInfo, @@ -1070,23 +690,7 @@ fun RemoteEntryRow( ) { Entry( onClick = { onRemoteEntrySelected(remoteInfo) }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_other_devices), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.padding(start = 10.dp) - ) - }, - label = { - Column() { - TextOnSurfaceVariant( - text = stringResource(R.string.another_device), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = 10.dp, top = 18.dp, bottom = 18.dp) - .align(alignment = Alignment.CenterHorizontally), - ) - } - } + iconPainter = painterResource(R.drawable.ic_other_devices), + entryHeadlineText = stringResource(R.string.another_device), ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index ce4e936446ff..192fa15714c6 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -38,7 +38,9 @@ data class CreateCredentialUiState( ) internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean { - return state.sortedCreateOptionsPairs.isNotEmpty() + return state.sortedCreateOptionsPairs.isNotEmpty() || + (!state.requestDisplayInfo.preferImmediatelyAvailableCredentials && + state.remoteEntry != null) } open class ProviderInfo( @@ -104,6 +106,7 @@ data class RequestDisplayInfo( val type: CredentialType, val appName: String, val typeIcon: Drawable, + val preferImmediatelyAvailableCredentials: Boolean, ) /** diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 5704820444a4..c5028c25c5da 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -20,40 +20,23 @@ import android.text.TextUtils import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult import androidx.activity.result.IntentSenderRequest - -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.outlined.Lock import androidx.compose.material3.Divider -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Snackbar -import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel @@ -62,16 +45,18 @@ import com.android.credentialmanager.common.BaseEntry import com.android.credentialmanager.common.CredentialType import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.ui.ActionButton +import com.android.credentialmanager.common.ui.ActionEntry import com.android.credentialmanager.common.ui.ConfirmButton +import com.android.credentialmanager.common.ui.CredentialContainerCard +import com.android.credentialmanager.common.ui.CtaButtonRow import com.android.credentialmanager.common.ui.Entry import com.android.credentialmanager.common.ui.ModalBottomSheet -import com.android.credentialmanager.common.ui.TextOnSurface -import com.android.credentialmanager.common.ui.TextSecondary -import com.android.credentialmanager.common.ui.TextOnSurfaceVariant -import com.android.credentialmanager.common.ui.ContainerCard -import com.android.credentialmanager.common.ui.TransparentBackgroundEntry -import com.android.credentialmanager.ui.theme.EntryShape -import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme +import com.android.credentialmanager.common.ui.MoreOptionTopAppBar +import com.android.credentialmanager.common.ui.SheetContainerCard +import com.android.credentialmanager.common.ui.SnackbarActionText +import com.android.credentialmanager.common.ui.HeadlineText +import com.android.credentialmanager.common.ui.CredentialListSectionHeader +import com.android.credentialmanager.common.ui.Snackbar @Composable fun GetCredentialScreen( @@ -154,12 +139,9 @@ fun PrimarySelectionCard( val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList - ContainerCard() { - Column() { - TextOnSurface( - modifier = Modifier.padding(all = 24.dp), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.headlineSmall, + SheetContainerCard { + item { + HeadlineText( text = stringResource( if (sortedUserNameToCredentialEntryList .size == 1 && authenticationEntryList.isEmpty() @@ -178,48 +160,42 @@ fun PrimarySelectionCard( requestDisplayInfo.appName ), ) - - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally) - ) { - val usernameForCredentialSize = sortedUserNameToCredentialEntryList - .size - val authenticationEntrySize = authenticationEntryList.size - LazyColumn( - verticalArrangement = Arrangement.spacedBy(2.dp) - ) { + } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + item { + CredentialContainerCard { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size + val authenticationEntrySize = authenticationEntryList.size // Show max 4 entries in this primary page if (usernameForCredentialSize + authenticationEntrySize <= 4) { - items(sortedUserNameToCredentialEntryList) { + sortedUserNameToCredentialEntryList.forEach { CredentialEntryRow( credentialEntryInfo = it.sortedCredentialEntryList.first(), onEntrySelected = onEntrySelected, ) } - items(authenticationEntryList) { + authenticationEntryList.forEach { AuthenticationEntryRow( authenticationEntryInfo = it, onEntrySelected = onEntrySelected, ) } } else if (usernameForCredentialSize < 4) { - items(sortedUserNameToCredentialEntryList) { + sortedUserNameToCredentialEntryList.forEach { CredentialEntryRow( credentialEntryInfo = it.sortedCredentialEntryList.first(), onEntrySelected = onEntrySelected, ) } - items(authenticationEntryList.take(4 - usernameForCredentialSize)) { + authenticationEntryList.take(4 - usernameForCredentialSize).forEach { AuthenticationEntryRow( authenticationEntryInfo = it, onEntrySelected = onEntrySelected, ) } } else { - items(sortedUserNameToCredentialEntryList.take(4)) { + sortedUserNameToCredentialEntryList.take(4).forEach { CredentialEntryRow( credentialEntryInfo = it.sortedCredentialEntryList.first(), onEntrySelected = onEntrySelected, @@ -228,49 +204,39 @@ fun PrimarySelectionCard( } } } - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - var totalEntriesCount = sortedUserNameToCredentialEntryList - .flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList - .size + providerInfoList.flatMap { it.actionEntryList }.size - if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1 - // Row horizontalArrangement differs on only one actionButton(should place on most - // left)/only one confirmButton(should place on most right)/two buttons exist the same - // time(should be one on the left, one on the right) - Row( - horizontalArrangement = - if (totalEntriesCount <= 1 && activeEntry != null) Arrangement.End - else if (totalEntriesCount > 1 && activeEntry == null) Arrangement.Start - else Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - if (totalEntriesCount > 1) { - ActionButton( - stringResource(R.string.get_dialog_use_saved_passkey_for), - onMoreOptionSelected - ) - } - // Only one sign-in options exist - if (activeEntry != null) { - ConfirmButton( - stringResource(R.string.string_continue), - onClick = onConfirm - ) - } - } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 16.dp) + } + item { Divider(thickness = 24.dp, color = Color.Transparent) } + var totalEntriesCount = sortedUserNameToCredentialEntryList + .flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList + .size + providerInfoList.flatMap { it.actionEntryList }.size + if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1 + // Row horizontalArrangement differs on only one actionButton(should place on most + // left)/only one confirmButton(should place on most right)/two buttons exist the same + // time(should be one on the left, one on the right) + item { + CtaButtonRow( + leftButton = if (totalEntriesCount > 1) { + { + ActionButton( + stringResource(R.string.get_dialog_use_saved_passkey_for), + onMoreOptionSelected + ) + } + } else null, + rightButton = if (activeEntry != null) { // Only one sign-in options exist + { + ConfirmButton( + stringResource(R.string.string_continue), + onClick = onConfirm + ) + } + } else null, ) } } } /** Draws the secondary credential selection page, where all sign-in options are listed. */ -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AllSignInOptionCard( providerInfoList: List<ProviderInfo>, @@ -283,88 +249,52 @@ fun AllSignInOptionCard( val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList - ContainerCard() { - Column() { - TopAppBar( - colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent, - ), - title = { - TextOnSurface( - text = stringResource(R.string.get_dialog_title_sign_in_options), - style = MaterialTheme.typography.titleMedium - ) - }, - navigationIcon = { - IconButton(onClick = if (isNoAccount) onCancel else onBackButtonClicked) { - Icon( - Icons.Filled.ArrowBack, - contentDescription = stringResource( - R.string.accessibility_back_arrow_button) - ) - } - }, - modifier = Modifier.padding(top = 12.dp) + SheetContainerCard(topAppBar = { + MoreOptionTopAppBar( + text = stringResource(R.string.get_dialog_title_sign_in_options), + onNavigationIconClicked = if (isNoAccount) onCancel else onBackButtonClicked, + ) + }) { + // For username + items(sortedUserNameToCredentialEntryList) { item -> + PerUserNameCredentials( + perUserNameCredentialEntryList = item, + onEntrySelected = onEntrySelected, ) - - ContainerCard( - shape = MaterialTheme.shapes.large, - modifier = Modifier - .padding(start = 24.dp, end = 24.dp, bottom = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - ) { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - // For username - items(sortedUserNameToCredentialEntryList) { item -> - PerUserNameCredentials( - perUserNameCredentialEntryList = item, - onEntrySelected = onEntrySelected, - ) - } - // Locked password manager - if (authenticationEntryList.isNotEmpty()) { - item { - LockedCredentials( - authenticationEntryList = authenticationEntryList, - onEntrySelected = onEntrySelected, - ) - } - } - item { - Divider( - thickness = 8.dp, - color = Color.Transparent, - ) - } - // From another device - val remoteEntry = providerDisplayInfo.remoteEntry - if (remoteEntry != null) { - item { - RemoteEntryCard( - remoteEntry = remoteEntry, - onEntrySelected = onEntrySelected, - ) - } - } - item { - Divider( - thickness = 1.dp, - color = Color.LightGray, - modifier = Modifier.padding(top = 16.dp) - ) - } - // Manage sign-ins (action chips) - item { - ActionChips( - providerInfoList = providerInfoList, - onEntrySelected = onEntrySelected - ) - } - } + } + // Locked password manager + if (authenticationEntryList.isNotEmpty()) { + item { + LockedCredentials( + authenticationEntryList = authenticationEntryList, + onEntrySelected = onEntrySelected, + ) } } + // From another device + val remoteEntry = providerDisplayInfo.remoteEntry + if (remoteEntry != null) { + item { + RemoteEntryCard( + remoteEntry = remoteEntry, + onEntrySelected = onEntrySelected, + ) + } + } + item { + Divider( + thickness = 1.dp, + color = Color.LightGray, + modifier = Modifier.padding(top = 16.dp) + ) + } + // Manage sign-ins (action chips) + item { + ActionChips( + providerInfoList = providerInfoList, + onEntrySelected = onEntrySelected + ) + } } } @@ -381,17 +311,11 @@ fun ActionChips( return } - TextSecondary( - text = stringResource(R.string.get_dialog_heading_manage_sign_ins), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(vertical = 8.dp) + CredentialListSectionHeader( + text = stringResource(R.string.get_dialog_heading_manage_sign_ins) ) - // TODO: tweak padding. - ContainerCard( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - shape = MaterialTheme.shapes.medium, - ) { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + CredentialContainerCard { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { actionChips.forEach { ActionEntryRow(it, onEntrySelected) } @@ -404,38 +328,20 @@ fun RemoteEntryCard( remoteEntry: RemoteEntryInfo, onEntrySelected: (BaseEntry) -> Unit, ) { - TextSecondary( - text = stringResource(R.string.get_dialog_heading_from_another_device), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(vertical = 8.dp) + CredentialListSectionHeader( + text = stringResource(R.string.get_dialog_heading_from_another_device) ) - ContainerCard( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - shape = MaterialTheme.shapes.medium, - ) { + CredentialContainerCard { Column( modifier = Modifier.fillMaxWidth().wrapContentHeight(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { Entry( onClick = { onEntrySelected(remoteEntry) }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_other_devices), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.padding(start = 10.dp) - ) - }, - label = { - TextOnSurfaceVariant( - text = stringResource( - R.string.get_dialog_option_headline_use_a_different_device), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = 10.dp, top = 18.dp, bottom = 18.dp) - .align(alignment = Alignment.CenterHorizontally) - ) - } + iconPainter = painterResource(R.drawable.ic_other_devices), + entryHeadlineText = stringResource( + R.string.get_dialog_option_headline_use_a_different_device + ), ) } } @@ -446,15 +352,10 @@ fun LockedCredentials( authenticationEntryList: List<AuthenticationEntryInfo>, onEntrySelected: (BaseEntry) -> Unit, ) { - TextSecondary( - text = stringResource(R.string.get_dialog_heading_locked_password_managers), - style = MaterialTheme.typography.labelLarge, - modifier = Modifier.padding(vertical = 8.dp) + CredentialListSectionHeader( + text = stringResource(R.string.get_dialog_heading_locked_password_managers) ) - ContainerCard( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - shape = MaterialTheme.shapes.medium, - ) { + CredentialContainerCard { Column( modifier = Modifier.fillMaxWidth().wrapContentHeight(), verticalArrangement = Arrangement.spacedBy(2.dp), @@ -471,17 +372,12 @@ fun PerUserNameCredentials( perUserNameCredentialEntryList: PerUserNameCredentialEntryList, onEntrySelected: (BaseEntry) -> Unit, ) { - TextSecondary( + CredentialListSectionHeader( text = stringResource( R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName - ), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(vertical = 8.dp) + ) ) - ContainerCard( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - shape = MaterialTheme.shapes.medium, - ) { + CredentialContainerCard { Column( modifier = Modifier.fillMaxWidth().wrapContentHeight(), verticalArrangement = Arrangement.spacedBy(2.dp), @@ -500,53 +396,28 @@ fun CredentialEntryRow( ) { Entry( onClick = { onEntrySelected(credentialEntryInfo) }, - icon = { - if (credentialEntryInfo.icon != null) { - Image( - modifier = Modifier.padding(start = 10.dp).size(32.dp), - bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null, - ) - } else { - Icon( - modifier = Modifier.padding(start = 10.dp).size(32.dp), - painter = painterResource(R.drawable.ic_other_sign_in), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant - ) - } + iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(), + // Fall back to iconPainter if iconImageBitmap isn't available + iconPainter = + if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in) + else null, + entryHeadlineText = credentialEntryInfo.userName, + entrySecondLineText = if ( + credentialEntryInfo.credentialType == CredentialType.PASSWORD) { + "••••••••••••" + } else { + if (TextUtils.isEmpty(credentialEntryInfo.displayName)) + credentialEntryInfo.credentialTypeDisplayName + else + credentialEntryInfo.credentialTypeDisplayName + + stringResource( + R.string.get_dialog_sign_in_type_username_separator + ) + + credentialEntryInfo.displayName }, - label = { - Column() { - // TODO: fix the text values. - TextOnSurfaceVariant( - text = credentialEntryInfo.userName, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp) - ) - TextSecondary( - text = if ( - credentialEntryInfo.credentialType == CredentialType.PASSWORD) { - "••••••••••••" - } else { - if (TextUtils.isEmpty(credentialEntryInfo.displayName)) - credentialEntryInfo.credentialTypeDisplayName - else - credentialEntryInfo.credentialTypeDisplayName + - stringResource( - R.string.get_dialog_sign_in_type_username_separator - ) + - credentialEntryInfo.displayName - }, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp) - ) - } - } ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AuthenticationEntryRow( authenticationEntryInfo: AuthenticationEntryInfo, @@ -554,144 +425,66 @@ fun AuthenticationEntryRow( ) { Entry( onClick = { onEntrySelected(authenticationEntryInfo) }, - icon = { - Image( - modifier = Modifier.padding(start = 10.dp).size(32.dp), - bitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null - ) - }, - label = { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp), - ) { - Column() { - TextOnSurfaceVariant( - text = authenticationEntryInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp) - ) - TextSecondary( - text = stringResource( - if (authenticationEntryInfo.isUnlockedAndEmpty) - R.string.locked_credential_entry_label_subtext_no_sign_in - else R.string.locked_credential_entry_label_subtext_tap_to_unlock - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp) - ) - } - if (!authenticationEntryInfo.isUnlockedAndEmpty) { - Icon( - Icons.Outlined.Lock, - null, - Modifier.align(alignment = Alignment.CenterVertically).padding(end = 10.dp), - ) - } - } - } + iconImageBitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(), + entryHeadlineText = authenticationEntryInfo.title, + entrySecondLineText = stringResource( + if (authenticationEntryInfo.isUnlockedAndEmpty) + R.string.locked_credential_entry_label_subtext_no_sign_in + else R.string.locked_credential_entry_label_subtext_tap_to_unlock + ), + isLockedAuthEntry = !authenticationEntryInfo.isUnlockedAndEmpty, ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun ActionEntryRow( actionEntryInfo: ActionEntryInfo, onEntrySelected: (BaseEntry) -> Unit, ) { - TransparentBackgroundEntry( - icon = { - Image( - modifier = Modifier.padding(start = 10.dp).size(24.dp), - bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null, - ) - }, - label = { - Column() { - TextOnSurfaceVariant( - text = actionEntryInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = 8.dp), - ) - if (actionEntryInfo.subTitle != null) { - TextSecondary( - text = actionEntryInfo.subTitle, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 8.dp), - ) - } - } - }, + ActionEntry( + iconImageBitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(), + entryHeadlineText = actionEntryInfo.title, + entrySecondLineText = actionEntryInfo.subTitle, onClick = { onEntrySelected(actionEntryInfo) }, ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun RemoteCredentialSnackBarScreen( onClick: (Boolean) -> Unit, onCancel: () -> Unit, ) { - // TODO: Change the height, width and position according to the design Snackbar( - modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp), - shape = EntryShape.FullMediumRoundedCorner, - containerColor = LocalAndroidColorScheme.current.colorBackground, - contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, action = { TextButton( + modifier = Modifier.padding(top = 4.dp, bottom = 4.dp, start = 16.dp) + .heightIn(min = 32.dp), onClick = { onClick(true) }, + contentPadding = + PaddingValues(start = 0.dp, top = 6.dp, end = 0.dp, bottom = 6.dp), ) { - Text(text = stringResource(R.string.snackbar_action)) + SnackbarActionText(text = stringResource(R.string.snackbar_action)) } }, - dismissAction = { - IconButton(onClick = onCancel) { - Icon( - Icons.Filled.Close, - contentDescription = stringResource( - R.string.accessibility_close_button - ), - tint = LocalAndroidColorScheme.current.colorAccentTertiary - ) - } - }, - ) { - Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for)) - } + onDismiss = onCancel, + contentText = stringResource(R.string.get_dialog_use_saved_passkey_for), + ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun EmptyAuthEntrySnackBarScreen( authenticationEntryList: List<AuthenticationEntryInfo>, onCancel: () -> Unit, onLastLockedAuthEntryNotFound: () -> Unit, ) { - val lastLocked = authenticationEntryList.firstOrNull({it.isLastUnlocked}) + val lastLocked = authenticationEntryList.firstOrNull({ it.isLastUnlocked }) if (lastLocked == null) { onLastLockedAuthEntryNotFound() return } - // TODO: Change the height, width and position according to the design Snackbar( - modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp), - shape = EntryShape.FullMediumRoundedCorner, - containerColor = LocalAndroidColorScheme.current.colorBackground, - contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - dismissAction = { - IconButton(onClick = onCancel) { - Icon( - Icons.Filled.Close, - contentDescription = stringResource(R.string.accessibility_close_button), - tint = LocalAndroidColorScheme.current.colorAccentTertiary - ) - } - }, - ) { - Text(text = stringResource(R.string.no_sign_in_info_in, lastLocked.title)) - } + onDismiss = onCancel, + contentText = stringResource(R.string.no_sign_in_info_in, lastLocked.providerDisplayName), + ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 49415c01033d..9727d3f39c4a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -37,7 +37,8 @@ data class GetCredentialUiState( internal fun hasContentToDisplay(state: GetCredentialUiState): Boolean { return state.providerDisplayInfo.sortedUserNameToCredentialEntryList.isNotEmpty() || state.providerDisplayInfo.authenticationEntryList.isNotEmpty() || - state.providerDisplayInfo.remoteEntry != null + (state.providerDisplayInfo.remoteEntry != null && + !state.requestDisplayInfo.preferImmediatelyAvailableCredentials) } data class ProviderInfo( @@ -96,6 +97,7 @@ class AuthenticationEntryInfo( pendingIntent: PendingIntent?, fillInIntent: Intent?, val title: String, + val providerDisplayName: String, val icon: Drawable, // The entry had been unlocked and turned out to be empty. Used to determine whether to // show "Tap to unlock" or "No sign-in info" for this entry. @@ -140,11 +142,12 @@ class ActionEntryInfo( entrySubkey, pendingIntent, fillInIntent, - shouldTerminateUiUponSuccessfulProviderResult = false, + shouldTerminateUiUponSuccessfulProviderResult = true, ) data class RequestDisplayInfo( val appName: String, + val preferImmediatelyAvailableCredentials: Boolean, ) /** @@ -245,7 +248,6 @@ private fun toActiveEntry( private fun toGetScreenState( providerDisplayInfo: ProviderDisplayInfo ): GetScreenState { - return if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() && providerDisplayInfo.remoteEntry == null && providerDisplayInfo.authenticationEntryList.all { it.isUnlockedAndEmpty }) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt index 15ae3295416b..120e4938c322 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt @@ -22,12 +22,14 @@ import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color import com.android.internal.R +/** File copied from PlatformComposeCore. */ + /** CompositionLocal used to pass [AndroidColorScheme] down the tree. */ val LocalAndroidColorScheme = staticCompositionLocalOf<AndroidColorScheme> { throw IllegalStateException( "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " + - "Composable surrounded by a CredentialSelectorTheme {}." + "Composable surrounded by a PlatformTheme {}." ) } @@ -38,7 +40,6 @@ val LocalAndroidColorScheme = * most of the colors in this class will be removed in favor of their M3 counterpart. */ class AndroidColorScheme internal constructor(context: Context) { - val colorPrimary = getColor(context, R.attr.colorPrimary) val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark) val colorAccent = getColor(context, R.attr.colorAccent) @@ -66,10 +67,12 @@ class AndroidColorScheme internal constructor(context: Context) { val colorForeground = getColor(context, R.attr.colorForeground) val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse) - private fun getColor(context: Context, attr: Int): Color { - val ta = context.obtainStyledAttributes(intArrayOf(attr)) - @ColorInt val color = ta.getColor(0, 0) - ta.recycle() - return Color(color) + companion object { + fun getColor(context: Context, attr: Int): Color { + val ta = context.obtainStyledAttributes(intArrayOf(attr)) + @ColorInt val color = ta.getColor(0, 0) + ta.recycle() + return Color(color) + } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt index abb4bfbf915e..c9238459633c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt @@ -1,14 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.credentialmanager.ui.theme +import android.annotation.AttrRes +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext -val Grey100 = Color(0xFFF1F3F4) -val Purple200 = Color(0xFFBB86FC) -val Purple500 = Color(0xFF6200EE) -val Purple700 = Color(0xFF3700B3) -val Teal200 = Color(0xFF03DAC5) -val lightColorAccentSecondary = Color(0xFFC2E7FF) -val lightBackgroundColor = Color(0xFFF0F0F0) -val lightSurface1 = Color(0xFF6991D6) -val textColorSecondary = Color(0xFF40484B) -val textColorPrimary = Color(0xFF191C1D) +/** Read the [Color] from the given [attribute]. */ +@Composable +@ReadOnlyComposable +fun colorAttr(@AttrRes attribute: Int): Color { + return AndroidColorScheme.getColor(LocalContext.current, attribute) +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt new file mode 100644 index 000000000000..662199a4bba5 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.android.credentialmanager.ui.theme.typography.TypeScaleTokens +import com.android.credentialmanager.ui.theme.typography.TypefaceNames +import com.android.credentialmanager.ui.theme.typography.TypefaceTokens +import com.android.credentialmanager.ui.theme.typography.TypographyTokens +import com.android.credentialmanager.ui.theme.typography.platformTypography + +/** File copied from PlatformComposeCore. */ + +/** The Material 3 theme that should wrap all Platform Composables. */ +@Composable +fun PlatformTheme( + isDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val context = LocalContext.current + + // TODO(b/230605885): Define our color scheme. + val colorScheme = + if (isDarkTheme) { + dynamicDarkColorScheme(context) + } else { + dynamicLightColorScheme(context) + } + val androidColorScheme = AndroidColorScheme(context) + val typefaceNames = remember(context) { TypefaceNames.get(context) } + val typography = + remember(typefaceNames) { + platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames)))) + } + + MaterialTheme(colorScheme, typography = typography) { + CompositionLocalProvider( + LocalAndroidColorScheme provides androidColorScheme, + ) { + content() + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt deleted file mode 100644 index 3ca0e4494ab6..000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.android.credentialmanager.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.platform.LocalContext - -@Composable -fun CredentialSelectorTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - val context = LocalContext.current - - val colorScheme = - if (darkTheme) { - dynamicDarkColorScheme(context) - } else { - dynamicLightColorScheme(context) - } - val androidColorScheme = AndroidColorScheme(context) - val typography = Typography - - MaterialTheme( - colorScheme, - typography = typography, - shapes = Shapes - ) { - CompositionLocalProvider( - LocalAndroidColorScheme provides androidColorScheme, - ) { - content() - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt deleted file mode 100644 index e09abbb3ffff..000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.android.credentialmanager.ui.theme - -import androidx.compose.material3.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp - -// Set of Material typography styles to start with -val Typography = Typography( - titleMedium = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 24.sp, - lineHeight = 32.sp, - ), - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 14.sp, - lineHeight = 20.sp, - ), - bodyMedium = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 14.sp, - lineHeight = 20.sp, - color = textColorSecondary - ), - labelLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - lineHeight = 20.sp, - ), - titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 16.sp, - lineHeight = 24.sp, - color = textColorPrimary - ), - - /* Other default text styles to override - button = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.W500, - fontSize = 14.sp - ), - caption = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 12.sp - ) - */ -) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt new file mode 100644 index 000000000000..984e4f19e4d4 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.ui.theme.typography + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Typography + +/** File copied from PlatformComposeCore. */ + +/** + * The typography for Platform Compose code. + * + * Do not use directly and call [MaterialTheme.typography] instead to access the different text + * styles. + */ +internal fun platformTypography(typographyTokens: TypographyTokens): Typography { + return Typography( + displayLarge = typographyTokens.displayLarge, + displayMedium = typographyTokens.displayMedium, + displaySmall = typographyTokens.displaySmall, + headlineLarge = typographyTokens.headlineLarge, + headlineMedium = typographyTokens.headlineMedium, + headlineSmall = typographyTokens.headlineSmall, + titleLarge = typographyTokens.titleLarge, + titleMedium = typographyTokens.titleMedium, + titleSmall = typographyTokens.titleSmall, + bodyLarge = typographyTokens.bodyLarge, + bodyMedium = typographyTokens.bodyMedium, + bodySmall = typographyTokens.bodySmall, + labelLarge = typographyTokens.labelLarge, + labelMedium = typographyTokens.labelMedium, + labelSmall = typographyTokens.labelSmall, + ) +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt new file mode 100644 index 000000000000..b2dd20720f6a --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.ui.theme.typography + +import androidx.compose.ui.unit.sp + +/** File copied from PlatformComposeCore. */ +internal class TypeScaleTokens(typefaceTokens: TypefaceTokens) { + val bodyLargeFont = typefaceTokens.plain + val bodyLargeLineHeight = 24.0.sp + val bodyLargeSize = 16.sp + val bodyLargeTracking = 0.0.sp + val bodyLargeWeight = TypefaceTokens.WeightRegular + val bodyMediumFont = typefaceTokens.plain + val bodyMediumLineHeight = 20.0.sp + val bodyMediumSize = 14.sp + val bodyMediumTracking = 0.0.sp + val bodyMediumWeight = TypefaceTokens.WeightRegular + val bodySmallFont = typefaceTokens.plain + val bodySmallLineHeight = 16.0.sp + val bodySmallSize = 12.sp + val bodySmallTracking = 0.1.sp + val bodySmallWeight = TypefaceTokens.WeightRegular + val displayLargeFont = typefaceTokens.brand + val displayLargeLineHeight = 64.0.sp + val displayLargeSize = 57.sp + val displayLargeTracking = 0.0.sp + val displayLargeWeight = TypefaceTokens.WeightRegular + val displayMediumFont = typefaceTokens.brand + val displayMediumLineHeight = 52.0.sp + val displayMediumSize = 45.sp + val displayMediumTracking = 0.0.sp + val displayMediumWeight = TypefaceTokens.WeightRegular + val displaySmallFont = typefaceTokens.brand + val displaySmallLineHeight = 44.0.sp + val displaySmallSize = 36.sp + val displaySmallTracking = 0.0.sp + val displaySmallWeight = TypefaceTokens.WeightRegular + val headlineLargeFont = typefaceTokens.brand + val headlineLargeLineHeight = 40.0.sp + val headlineLargeSize = 32.sp + val headlineLargeTracking = 0.0.sp + val headlineLargeWeight = TypefaceTokens.WeightRegular + val headlineMediumFont = typefaceTokens.brand + val headlineMediumLineHeight = 36.0.sp + val headlineMediumSize = 28.sp + val headlineMediumTracking = 0.0.sp + val headlineMediumWeight = TypefaceTokens.WeightRegular + val headlineSmallFont = typefaceTokens.brand + val headlineSmallLineHeight = 32.0.sp + val headlineSmallSize = 24.sp + val headlineSmallTracking = 0.0.sp + val headlineSmallWeight = TypefaceTokens.WeightRegular + val labelLargeFont = typefaceTokens.plain + val labelLargeLineHeight = 20.0.sp + val labelLargeSize = 14.sp + val labelLargeTracking = 0.0.sp + val labelLargeWeight = TypefaceTokens.WeightMedium + val labelMediumFont = typefaceTokens.plain + val labelMediumLineHeight = 16.0.sp + val labelMediumSize = 12.sp + val labelMediumTracking = 0.1.sp + val labelMediumWeight = TypefaceTokens.WeightMedium + val labelSmallFont = typefaceTokens.plain + val labelSmallLineHeight = 16.0.sp + val labelSmallSize = 11.sp + val labelSmallTracking = 0.1.sp + val labelSmallWeight = TypefaceTokens.WeightMedium + val titleLargeFont = typefaceTokens.brand + val titleLargeLineHeight = 28.0.sp + val titleLargeSize = 22.sp + val titleLargeTracking = 0.0.sp + val titleLargeWeight = TypefaceTokens.WeightRegular + val titleMediumFont = typefaceTokens.plain + val titleMediumLineHeight = 24.0.sp + val titleMediumSize = 16.sp + val titleMediumTracking = 0.0.sp + val titleMediumWeight = TypefaceTokens.WeightMedium + val titleSmallFont = typefaceTokens.plain + val titleSmallLineHeight = 20.0.sp + val titleSmallSize = 14.sp + val titleSmallTracking = 0.0.sp + val titleSmallWeight = TypefaceTokens.WeightMedium +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt new file mode 100644 index 000000000000..3cc761f1cc60 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalTextApi::class) + +package com.android.credentialmanager.ui.theme.typography + +import android.content.Context +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.font.DeviceFontFamilyName +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight + +/** File copied from PlatformComposeCore. */ +internal class TypefaceTokens(typefaceNames: TypefaceNames) { + companion object { + val WeightMedium = FontWeight.Medium + val WeightRegular = FontWeight.Normal + } + + private val brandFont = DeviceFontFamilyName(typefaceNames.brand) + private val plainFont = DeviceFontFamilyName(typefaceNames.plain) + + val brand = + FontFamily( + Font(brandFont, weight = WeightMedium), + Font(brandFont, weight = WeightRegular), + ) + val plain = + FontFamily( + Font(plainFont, weight = WeightMedium), + Font(plainFont, weight = WeightRegular), + ) +} + +internal data class TypefaceNames +private constructor( + val brand: String, + val plain: String, +) { + private enum class Config(val configName: String, val default: String) { + Brand("config_headlineFontFamily", "sans-serif"), + Plain("config_bodyFontFamily", "sans-serif"), + } + + companion object { + fun get(context: Context): TypefaceNames { + return TypefaceNames( + brand = getTypefaceName(context, Config.Brand), + plain = getTypefaceName(context, Config.Plain), + ) + } + + private fun getTypefaceName(context: Context, config: Config): String { + return context + .getString(context.resources.getIdentifier(config.configName, "string", "android")) + .takeIf { it.isNotEmpty() } + ?: config.default + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt new file mode 100644 index 000000000000..aadab92f40cc --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.ui.theme.typography + +import androidx.compose.ui.text.TextStyle + +/** File copied from PlatformComposeCore. */ +internal class TypographyTokens(typeScaleTokens: TypeScaleTokens) { + val bodyLarge = + TextStyle( + fontFamily = typeScaleTokens.bodyLargeFont, + fontWeight = typeScaleTokens.bodyLargeWeight, + fontSize = typeScaleTokens.bodyLargeSize, + lineHeight = typeScaleTokens.bodyLargeLineHeight, + letterSpacing = typeScaleTokens.bodyLargeTracking, + ) + val bodyMedium = + TextStyle( + fontFamily = typeScaleTokens.bodyMediumFont, + fontWeight = typeScaleTokens.bodyMediumWeight, + fontSize = typeScaleTokens.bodyMediumSize, + lineHeight = typeScaleTokens.bodyMediumLineHeight, + letterSpacing = typeScaleTokens.bodyMediumTracking, + ) + val bodySmall = + TextStyle( + fontFamily = typeScaleTokens.bodySmallFont, + fontWeight = typeScaleTokens.bodySmallWeight, + fontSize = typeScaleTokens.bodySmallSize, + lineHeight = typeScaleTokens.bodySmallLineHeight, + letterSpacing = typeScaleTokens.bodySmallTracking, + ) + val displayLarge = + TextStyle( + fontFamily = typeScaleTokens.displayLargeFont, + fontWeight = typeScaleTokens.displayLargeWeight, + fontSize = typeScaleTokens.displayLargeSize, + lineHeight = typeScaleTokens.displayLargeLineHeight, + letterSpacing = typeScaleTokens.displayLargeTracking, + ) + val displayMedium = + TextStyle( + fontFamily = typeScaleTokens.displayMediumFont, + fontWeight = typeScaleTokens.displayMediumWeight, + fontSize = typeScaleTokens.displayMediumSize, + lineHeight = typeScaleTokens.displayMediumLineHeight, + letterSpacing = typeScaleTokens.displayMediumTracking, + ) + val displaySmall = + TextStyle( + fontFamily = typeScaleTokens.displaySmallFont, + fontWeight = typeScaleTokens.displaySmallWeight, + fontSize = typeScaleTokens.displaySmallSize, + lineHeight = typeScaleTokens.displaySmallLineHeight, + letterSpacing = typeScaleTokens.displaySmallTracking, + ) + val headlineLarge = + TextStyle( + fontFamily = typeScaleTokens.headlineLargeFont, + fontWeight = typeScaleTokens.headlineLargeWeight, + fontSize = typeScaleTokens.headlineLargeSize, + lineHeight = typeScaleTokens.headlineLargeLineHeight, + letterSpacing = typeScaleTokens.headlineLargeTracking, + ) + val headlineMedium = + TextStyle( + fontFamily = typeScaleTokens.headlineMediumFont, + fontWeight = typeScaleTokens.headlineMediumWeight, + fontSize = typeScaleTokens.headlineMediumSize, + lineHeight = typeScaleTokens.headlineMediumLineHeight, + letterSpacing = typeScaleTokens.headlineMediumTracking, + ) + val headlineSmall = + TextStyle( + fontFamily = typeScaleTokens.headlineSmallFont, + fontWeight = typeScaleTokens.headlineSmallWeight, + fontSize = typeScaleTokens.headlineSmallSize, + lineHeight = typeScaleTokens.headlineSmallLineHeight, + letterSpacing = typeScaleTokens.headlineSmallTracking, + ) + val labelLarge = + TextStyle( + fontFamily = typeScaleTokens.labelLargeFont, + fontWeight = typeScaleTokens.labelLargeWeight, + fontSize = typeScaleTokens.labelLargeSize, + lineHeight = typeScaleTokens.labelLargeLineHeight, + letterSpacing = typeScaleTokens.labelLargeTracking, + ) + val labelMedium = + TextStyle( + fontFamily = typeScaleTokens.labelMediumFont, + fontWeight = typeScaleTokens.labelMediumWeight, + fontSize = typeScaleTokens.labelMediumSize, + lineHeight = typeScaleTokens.labelMediumLineHeight, + letterSpacing = typeScaleTokens.labelMediumTracking, + ) + val labelSmall = + TextStyle( + fontFamily = typeScaleTokens.labelSmallFont, + fontWeight = typeScaleTokens.labelSmallWeight, + fontSize = typeScaleTokens.labelSmallSize, + lineHeight = typeScaleTokens.labelSmallLineHeight, + letterSpacing = typeScaleTokens.labelSmallTracking, + ) + val titleLarge = + TextStyle( + fontFamily = typeScaleTokens.titleLargeFont, + fontWeight = typeScaleTokens.titleLargeWeight, + fontSize = typeScaleTokens.titleLargeSize, + lineHeight = typeScaleTokens.titleLargeLineHeight, + letterSpacing = typeScaleTokens.titleLargeTracking, + ) + val titleMedium = + TextStyle( + fontFamily = typeScaleTokens.titleMediumFont, + fontWeight = typeScaleTokens.titleMediumWeight, + fontSize = typeScaleTokens.titleMediumSize, + lineHeight = typeScaleTokens.titleMediumLineHeight, + letterSpacing = typeScaleTokens.titleMediumTracking, + ) + val titleSmall = + TextStyle( + fontFamily = typeScaleTokens.titleSmallFont, + fontWeight = typeScaleTokens.titleSmallWeight, + fontSize = typeScaleTokens.titleSmallSize, + lineHeight = typeScaleTokens.titleSmallLineHeight, + letterSpacing = typeScaleTokens.titleSmallTracking, + ) +} diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml index b194738c67b6..c2aaeace1af6 100644 --- a/packages/DynamicSystemInstallationService/AndroidManifest.xml +++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml @@ -21,12 +21,7 @@ android:exported="true" android:permission="android.permission.INSTALL_DYNAMIC_SYSTEM" android:foregroundServiceType="systemExempted" - android:process=":dynsystem"> - <intent-filter> - <action android:name="android.os.image.action.NOTIFY_IF_IN_USE" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </service> + android:process=":dynsystem" /> <activity android:name=".VerificationActivity" android:exported="true" diff --git a/packages/SettingsLib/DeviceStateRotationLock/OWNERS b/packages/SettingsLib/DeviceStateRotationLock/OWNERS new file mode 100644 index 000000000000..091df2610866 --- /dev/null +++ b/packages/SettingsLib/DeviceStateRotationLock/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/packages/SettingsLib/src/com/android/settingslib/devicestate/OWNERS
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt index 724588f794b4..3fdb1d14e667 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt @@ -77,9 +77,8 @@ data class SettingsPage( return true } - fun isEnabled(): Boolean { - return getPageProvider(sppName)?.isEnabled(arguments) ?: false - } + fun isEnabled(): Boolean = + SpaEnvironment.IS_DEBUG || getPageProvider(sppName)?.isEnabled(arguments) ?: false fun getTitle(): String { return getPageProvider(sppName)?.getTitle(arguments) ?: "" diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt index 02962a5815a2..2d956d5eddb2 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt @@ -88,4 +88,13 @@ abstract class SpaEnvironment(context: Context) { open val sliceProviderAuthorities: String? = null // TODO: add other environment setup here. + companion object { + /** + * Whether debug mode is on or off. + * + * If set to true, this will also enable all the pages under development (allows browsing + * and searching). + */ + const val IS_DEBUG = false + } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt index 5e6c61423c24..0552c408a8eb 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -46,6 +45,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.theme.SettingsDimension @@ -100,11 +100,16 @@ private fun RowScope.ActionButton(actionButton: ActionButton) { contentDescription = null, modifier = Modifier.size(SettingsDimension.itemIconSize), ) - Spacer(Modifier.height(4.dp)) - Text( - text = actionButton.text, - style = MaterialTheme.typography.labelMedium, - ) + Box( + modifier = Modifier.padding(top = 4.dp).fillMaxHeight(), + contentAlignment = Alignment.Center, + ) { + Text( + text = actionButton.text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.labelMedium, + ) + } } } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt index 18b207337ad4..1a7d8968f232 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt @@ -19,7 +19,6 @@ package com.android.settingslib.spaprivileged.model.app import android.content.Context import android.content.pm.ApplicationInfo import android.graphics.drawable.Drawable -import android.os.UserManager import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.produceState @@ -28,6 +27,7 @@ import androidx.compose.ui.res.stringResource import com.android.settingslib.Utils import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.framework.common.userManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -42,23 +42,25 @@ interface AppRepository { val context = LocalContext.current return produceState(initialValue = stringResource(R.string.summary_placeholder), app) { withContext(Dispatchers.IO) { - if (isClonedAppPage || isCloneApp(context, app)) { - value = context.getString(R.string.cloned_app_info_label, loadLabel(app)) + value = if (isClonedAppPage || isCloneApp(context, app)) { + context.getString(R.string.cloned_app_info_label, loadLabel(app)) } else { - value = loadLabel(app) + loadLabel(app) } } } } private fun isCloneApp(context: Context, app: ApplicationInfo): Boolean { - val userManager = context.getSystemService(UserManager::class.java)!! - val userInfo = userManager.getUserInfo(app.userId) + val userInfo = context.userManager.getUserInfo(app.userId) return userInfo != null && userInfo.isCloneProfile } @Composable fun produceIcon(app: ApplicationInfo): State<Drawable?> + + @Composable + fun produceIconContentDescription(app: ApplicationInfo): State<String?> } internal class AppRepositoryImpl(private val context: Context) : AppRepository { @@ -69,8 +71,22 @@ internal class AppRepositoryImpl(private val context: Context) : AppRepository { @Composable override fun produceIcon(app: ApplicationInfo) = produceState<Drawable?>(initialValue = null, app) { - withContext(Dispatchers.Default) { + withContext(Dispatchers.IO) { value = Utils.getBadgedIcon(context, app) } } + + @Composable + override fun produceIconContentDescription(app: ApplicationInfo) = + produceState<String?>(initialValue = null, app) { + withContext(Dispatchers.IO) { + value = when { + context.userManager.isManagedProfile(app.userId) -> { + context.getString(R.string.category_work) + } + + else -> null + } + } + } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index 602df54ed3fb..e3ea2e78756f 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.compose.rememberDrawablePainter @@ -50,7 +51,8 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { .padding( horizontal = SettingsDimension.itemPaddingStart, vertical = SettingsDimension.itemPaddingVertical, - ), + ) + .semantics(mergeDescendants = true) {}, horizontalAlignment = Alignment.CenterHorizontally, ) { val app = packageInfo.applicationInfo @@ -93,8 +95,8 @@ internal fun AppIcon(app: ApplicationInfo, size: Dp) { val appRepository = rememberAppRepository() Image( painter = rememberDrawablePainter(appRepository.produceIcon(app).value), - contentDescription = null, - modifier = Modifier.size(size) + contentDescription = appRepository.produceIconContentDescription(app).value, + modifier = Modifier.size(size), ) } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt index fc40aed43c92..4f0cddef078b 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt @@ -103,6 +103,9 @@ class AppListViewModelTest { @Composable override fun produceIcon(app: ApplicationInfo) = stateOf(null) + + @Composable + override fun produceIconContentDescription(app: ApplicationInfo) = stateOf(null) } private companion object { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt new file mode 100644 index 000000000000..26caa01192c5 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt @@ -0,0 +1,96 @@ +/* + * 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.settingslib.spaprivileged.model.app + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.os.UserManager +import androidx.compose.runtime.State +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.testutils.delay +import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.framework.common.userManager +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidJUnit4::class) +class AppRepositoryTest { + @get:Rule + val composeTestRule = createComposeRule() + + @get:Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var userManager: UserManager + + private lateinit var appRepository: AppRepositoryImpl + + @Before + fun setUp() { + whenever(context.userManager).thenReturn(userManager) + appRepository = AppRepositoryImpl(context) + } + + @Test + fun produceIconContentDescription_workProfile() { + whenever(userManager.isManagedProfile(APP.userId)).thenReturn(true) + + val contentDescription = produceIconContentDescription() + + assertThat(contentDescription.value).isEqualTo(context.getString(R.string.category_work)) + } + + @Test + fun produceIconContentDescription_personalProfile() { + whenever(userManager.isManagedProfile(APP.userId)).thenReturn(false) + + val contentDescription = produceIconContentDescription() + + assertThat(contentDescription.value).isNull() + } + + private fun produceIconContentDescription(): State<String?> { + var contentDescription: State<String?> = stateOf(null) + composeTestRule.setContent { + contentDescription = appRepository.produceIconContentDescription(APP) + } + composeTestRule.delay() + return contentDescription + } + + private companion object { + const val UID = 123 + val APP = ApplicationInfo().apply { + uid = UID + } + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 888b09fafd3b..e846480f68d9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -575,9 +575,15 @@ public class Utils { /** Get the corresponding adaptive icon drawable. */ public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) { + UserManager um = context.getSystemService(UserManager.class); + boolean isClone = um.getProfiles(user.getIdentifier()).stream() + .anyMatch(profile -> + profile.isCloneProfile() && profile.id == user.getIdentifier()); try (IconFactory iconFactory = IconFactory.obtain(context)) { return iconFactory - .createBadgedIconBitmap(icon, new IconOptions().setUser(user)) + .createBadgedIconBitmap( + icon, + new IconOptions().setUser(user).setIsCloneProfile(isClone)) .newIcon(context); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index d7c7130bfb3d..ba275ebca168 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -422,22 +422,26 @@ public class SettingsProvider extends ContentProvider { switch (method) { case Settings.CALL_METHOD_GET_CONFIG: { Setting setting = getConfigSetting(name); - return packageValueForCallResult(setting, isTrackingGeneration(args)); + return packageValueForCallResult(SETTINGS_TYPE_CONFIG, name, requestingUserId, + setting, isTrackingGeneration(args)); } case Settings.CALL_METHOD_GET_GLOBAL: { Setting setting = getGlobalSetting(name); - return packageValueForCallResult(setting, isTrackingGeneration(args)); + return packageValueForCallResult(SETTINGS_TYPE_GLOBAL, name, requestingUserId, + setting, isTrackingGeneration(args)); } case Settings.CALL_METHOD_GET_SECURE: { Setting setting = getSecureSetting(name, requestingUserId); - return packageValueForCallResult(setting, isTrackingGeneration(args)); + return packageValueForCallResult(SETTINGS_TYPE_SECURE, name, requestingUserId, + setting, isTrackingGeneration(args)); } case Settings.CALL_METHOD_GET_SYSTEM: { Setting setting = getSystemSetting(name, requestingUserId); - return packageValueForCallResult(setting, isTrackingGeneration(args)); + return packageValueForCallResult(SETTINGS_TYPE_SYSTEM, name, requestingUserId, + setting, isTrackingGeneration(args)); } case Settings.CALL_METHOD_PUT_CONFIG: { @@ -2311,8 +2315,8 @@ public class SettingsProvider extends ContentProvider { "get/set setting for user", null); } - private Bundle packageValueForCallResult(@Nullable Setting setting, - boolean trackingGeneration) { + private Bundle packageValueForCallResult(int type, @NonNull String name, int userId, + @Nullable Setting setting, boolean trackingGeneration) { if (!trackingGeneration) { if (setting == null || setting.isNull()) { return NULL_SETTING_BUNDLE; @@ -2321,24 +2325,36 @@ public class SettingsProvider extends ContentProvider { } Bundle result = new Bundle(); result.putString(Settings.NameValueTable.VALUE, - !setting.isNull() ? setting.getValue() : null); + (setting != null && !setting.isNull()) ? setting.getValue() : null); - if (setting != null && !setting.isNull()) { - // No need to track generation if the setting doesn't exist + if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) { + // Don't track generation for non-existent settings unless the name is predefined synchronized (mLock) { - mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey(), - setting.getName()); + mSettingsRegistry.mGenerationRegistry.addGenerationData(result, + SettingsState.makeKey(type, userId), name); } } return result; } + private boolean isSettingPreDefined(String name, int type) { + if (type == SETTINGS_TYPE_GLOBAL) { + return sAllGlobalSettings.contains(name); + } else if (type == SETTINGS_TYPE_SECURE) { + return sAllSecureSettings.contains(name); + } else if (type == SETTINGS_TYPE_SYSTEM) { + return sAllSystemSettings.contains(name); + } else { + return false; + } + } + private Bundle packageValuesForCallResult(String prefix, @NonNull HashMap<String, String> keyValues, boolean trackingGeneration) { Bundle result = new Bundle(); result.putSerializable(Settings.NameValueTable.VALUE, keyValues); - if (trackingGeneration && !keyValues.isEmpty()) { - // No need to track generation if the namespace is empty + if (trackingGeneration) { + // Track generation even if the namespace is empty because this is for system apps synchronized (mLock) { mSettingsRegistry.mGenerationRegistry.addGenerationData(result, mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 596ff0e9f380..90bec4292313 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -646,7 +646,7 @@ <!-- Permission required for CTS test - ResourceObserverNativeTest --> <uses-permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" /> - <!-- Permission required for CTS test - CtsPermission5TestCases --> + <!-- Permission required for CTS test - CtsAttributionSourceTestCases --> <uses-permission android:name="android.permission.RENOUNCE_PERMISSIONS" /> <!-- Permission required for CTS test - android.widget.cts.ToastTest --> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 9d469611479a..b236ac5af104 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -449,6 +449,7 @@ systemui_optimized_java_defaults { enabled: true, optimize: true, shrink: true, + shrink_resources: true, proguard_compatibility: false, proguard_flags_files: ["proguard.flags"], }, diff --git a/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml b/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml deleted file mode 100644 index 4ff2967b5ddf..000000000000 --- a/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml +++ /dev/null @@ -1,49 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<com.android.systemui.statusbar.notification.fsi.FsiChromeView android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_margin="50dp" - android:orientation="vertical" - xmlns:android="http://schemas.android.com/apk/res/android"> - - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:id="@+id/fsi_chrome" - android:layout_height="50dp" - android:orientation="horizontal"> - - <ImageView - android:id="@+id/fsi_app_icon" - android:layout_width="50dp" - android:layout_height="match_parent" - android:contentDescription="@null" /> - - <TextView - android:id="@+id/fsi_app_name" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:padding="10dp" - android:textSize="22dp" - android:gravity="center" - android:textColor="#FFFFFF" - android:text="AppName" /> - - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1" /> - - <Button - android:id="@+id/fsi_fullscreen_button" - android:layout_width="100dp" - android:layout_height="match_parent" - android:text="fullscreen" /> - - <Button - android:id="@+id/fsi_dismiss_button" - android:layout_width="100dp" - android:layout_height="match_parent" - android:text="dismiss" /> - - </LinearLayout> - -</com.android.systemui.statusbar.notification.fsi.FsiChromeView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml index 8ba1ff38fd63..37b8ae0f40c4 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf.xml @@ -76,7 +76,10 @@ android:layout_height="48dp" android:layout_width="wrap_content" android:layout_gravity="center_vertical" - android:padding="8dp" /> + android:padding="8dp" + android:track="@drawable/settingslib_track_selector" + android:thumb="@drawable/settingslib_thumb_selector" + android:theme="@style/MainSwitch.Settingslib"/> </com.android.systemui.statusbar.notification.row.AppControlView> <!-- ChannelRows get added dynamically --> @@ -101,7 +104,7 @@ android:minWidth="@dimen/notification_importance_toggle_size" android:minHeight="@dimen/notification_importance_toggle_size" android:maxWidth="200dp" - style="@style/TextAppearance.NotificationInfo.Button"/> + style="@style/Widget.Dialog.Button"/> <TextView android:id="@+id/done_button" android:text="@string/inline_ok_button" @@ -113,7 +116,7 @@ android:minWidth="@dimen/notification_importance_toggle_size" android:minHeight="@dimen/notification_importance_toggle_size" android:layout_alignParentEnd="true" - style="@style/TextAppearance.NotificationInfo.Button"/> + style="@style/Widget.Dialog.Button"/> </RelativeLayout> </LinearLayout> </FrameLayout> diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml index d03cd7e87a2d..190f9994b1dc 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml @@ -85,6 +85,9 @@ android:layout_width="wrap_content" android:layout_gravity="center_vertical" android:padding="8dp" + android:track="@drawable/settingslib_track_selector" + android:thumb="@drawable/settingslib_thumb_selector" + android:theme="@style/MainSwitch.Settingslib" /> </LinearLayout> </com.android.systemui.statusbar.notification.row.ChannelRow> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ac07d5647a3e..76f6f8aefc3a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -257,9 +257,6 @@ <!-- Radius for notifications corners with adjacent notifications --> <dimen name="notification_corner_radius_small">4dp</dimen> - <!-- Vertical padding of the FSI container --> - <dimen name="fsi_chrome_vertical_padding">80dp</dimen> - <!-- the padding of the shelf icon container --> <dimen name="shelf_icon_container_padding">13dp</dimen> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java index b92715516a75..8690b36c12d7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java @@ -62,7 +62,7 @@ public class PreviewPositionHelper { */ public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData, int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx, - int taskbarSize, boolean isTablet, + int taskbarSize, boolean isLargeScreen, int currentRotation, boolean isRtl) { boolean isRotated = false; boolean isOrientationDifferent; @@ -95,7 +95,7 @@ public class PreviewPositionHelper { canvasScreenRatio = (float) canvasWidth / screenWidthPx; } scaledTaskbarSize = taskbarSize * canvasScreenRatio; - thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0; + thumbnailClipHint.bottom = isLargeScreen ? scaledTaskbarSize : 0; float scale = thumbnailData.scale; final float thumbnailScale; @@ -103,7 +103,7 @@ public class PreviewPositionHelper { // Landscape vs portrait change. // Note: Disable rotation in grid layout. boolean windowingModeSupportsRotation = - thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isTablet; + thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !isLargeScreen; isOrientationDifferent = isOrientationChange(deltaRotate) && windowingModeSupportsRotation; if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 77a13bd91b90..751a3f8458bd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -137,7 +137,7 @@ public class Utilities { /** @return whether or not {@param context} represents that of a large screen device or not */ @TargetApi(Build.VERSION_CODES.R) - public static boolean isTablet(Context context) { + public static boolean isLargeScreen(Context context) { final WindowManager windowManager = context.getSystemService(WindowManager.class); final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 7d39c4aaacbf..dd60647021ff 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -23,6 +23,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import android.annotation.IntDef; import android.content.Context; import android.content.res.Resources; +import android.os.SystemProperties; import android.view.ViewConfiguration; import android.view.WindowManagerPolicyConstants; @@ -115,6 +116,9 @@ public class QuickStepContract { public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26; // Device dreaming state public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27; + // Whether the back gesture is allowed (or ignored) by the Shade + public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = SystemProperties.getBoolean( + "persist.wm.debug.shade_allow_back_gesture", false); @Retention(RetentionPolicy.SOURCE) @IntDef({SYSUI_STATE_SCREEN_PINNING, @@ -243,9 +247,14 @@ public class QuickStepContract { sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN; } // Disable when in immersive, or the notifications are interactive - int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN - | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED - | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; + int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; + + // EdgeBackGestureHandler ignores Back gesture when SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED. + // To allow Shade to respond to Back, we're bypassing this check (behind a flag). + if (!ALLOW_BACK_GESTURE_IN_SHADE) { + disableFlags |= SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; + } + return (sysuiStateFlags & disableFlags) != 0; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index 90f44a75b005..ae6861812b4c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -22,10 +22,7 @@ import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; -import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; @@ -46,6 +43,8 @@ import android.view.WindowManager; import android.window.TransitionInfo; import android.window.TransitionInfo.Change; +import com.android.wm.shell.util.TransitionUtil; + import java.util.ArrayList; import java.util.function.Predicate; @@ -75,7 +74,7 @@ public class RemoteAnimationTargetCompat { private static void setupLeash(@NonNull SurfaceControl leash, @NonNull TransitionInfo.Change change, int layer, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { - boolean isOpening = info.getType() == TRANSIT_OPEN || info.getType() == TRANSIT_TO_FRONT; + final boolean isOpening = TransitionUtil.isOpeningType(info.getType()); // Put animating stuff above this line and put static stuff below it. int zSplitLine = info.getChanges().size(); // changes should be ordered top-to-bottom in z @@ -88,7 +87,7 @@ public class RemoteAnimationTargetCompat { absBounds.top - info.getRootOffset().y); // Put all the OPEN/SHOW on top - if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { + if (TransitionUtil.isOpeningType(mode)) { if (isOpening) { t.setLayer(leash, zSplitLine + info.getChanges().size() - layer); if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) { @@ -99,7 +98,7 @@ public class RemoteAnimationTargetCompat { // put on bottom and leave it visible t.setLayer(leash, zSplitLine - layer); } - } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { + } else if (TransitionUtil.isClosingType(mode)) { if (isOpening) { // put on bottom and leave visible t.setLayer(leash, zSplitLine - layer); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 80b97588279f..70a36ce7e904 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -19,11 +19,7 @@ package com.android.systemui.shared.system; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget; @@ -50,6 +46,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.wm.shell.util.TransitionUtil; import java.util.ArrayList; @@ -183,7 +180,7 @@ public class RemoteTransitionCompat { final RemoteAnimationTarget target = newTarget(change, info.getChanges().size() - i, info, t, mLeashMap); apps.add(target); - if (change.getMode() == TRANSIT_CLOSE || change.getMode() == TRANSIT_TO_BACK) { + if (TransitionUtil.isClosingType(change.getMode())) { // raise closing (pausing) task to "above" layer so it isn't covered t.setLayer(target.leash, info.getChanges().size() * 3 - i); mPausingTasks.add(new TaskState(change, target.leash)); @@ -200,8 +197,7 @@ public class RemoteTransitionCompat { } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { mRecentsTask = taskInfo.token; mRecentsTaskId = taskInfo.taskId; - } else if (change.getMode() == TRANSIT_OPEN - || change.getMode() == TRANSIT_TO_FRONT) { + } else if (TransitionUtil.isOpeningType(change.getMode())) { mOpeningTasks.add(new TaskState(change, target.leash)); } } @@ -227,7 +223,7 @@ public class RemoteTransitionCompat { final TransitionInfo.Change change = info.getChanges().get(i); final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); final boolean isLeafTask = leafTaskFilter.test(change); - if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { + if (TransitionUtil.isOpeningType(change.getMode())) { if (mRecentsTask.equals(change.getContainer())) { recentsOpening = change; } else if (isLeafTask) { @@ -240,8 +236,7 @@ public class RemoteTransitionCompat { } openingTasks.add(change); } - } else if (change.getMode() == TRANSIT_CLOSE - || change.getMode() == TRANSIT_TO_BACK) { + } else if (TransitionUtil.isClosingType(change.getMode())) { if (mRecentsTask.equals(change.getContainer())) { foundRecentsClosing = true; } else if (isLeafTask) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index b53b868025e8..f4c581552bc4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -21,8 +21,6 @@ import android.util.Slog; import com.android.keyguard.KeyguardClockSwitch.ClockSize; import com.android.keyguard.logging.KeyguardLogger; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ClockAnimations; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -62,7 +60,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV KeyguardUpdateMonitor keyguardUpdateMonitor, ConfigurationController configurationController, DozeParameters dozeParameters, - FeatureFlags featureFlags, ScreenOffAnimationController screenOffAnimationController, KeyguardLogger logger) { super(keyguardStatusView); @@ -73,8 +70,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true, logger.getBuffer()); - mKeyguardVisibilityHelper.setOcclusionTransitionFlagEnabled( - featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index eb4b05b42b91..f038c69788b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -29,6 +29,7 @@ import static android.hardware.biometrics.BiometricConstants.LockoutMode; import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; +import static android.os.PowerManager.WAKE_REASON_UNKNOWN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; @@ -139,9 +140,9 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.logging.KeyguardUpdateMonitorLogger; +import com.android.settingslib.Utils; import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; -import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; @@ -152,6 +153,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.dump.DumpsysTableLogger; +import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -823,6 +825,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + private void onBiometricDetected(int userId, BiometricSourceType biometricSourceType, + boolean isStrongBiometric) { + Assert.isMainThread(); + Trace.beginSection("KeyGuardUpdateMonitor#onBiometricDetected"); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onBiometricDetected(userId, biometricSourceType, isStrongBiometric); + } + } + Trace.endSection(); + } + @VisibleForTesting protected void onFingerprintAuthenticated(int userId, boolean isStrongBiometric) { Assert.isMainThread(); @@ -899,6 +914,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + private void handleBiometricDetected(int authUserId, BiometricSourceType biometricSourceType, + boolean isStrongBiometric) { + Trace.beginSection("KeyGuardUpdateMonitor#handlerBiometricDetected"); + onBiometricDetected(authUserId, biometricSourceType, isStrongBiometric); + if (biometricSourceType == FINGERPRINT) { + mLogger.logFingerprintDetected(authUserId, isStrongBiometric); + } else if (biometricSourceType == FACE) { + mLogger.logFaceDetected(authUserId, isStrongBiometric); + setFaceRunningState(BIOMETRIC_STATE_STOPPED); + } + + Trace.endSection(); + } + private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) { Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated"); if (mHandler.hasCallbacks(mFpCancelNotReceived)) { @@ -950,8 +979,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void onFingerprintCancelNotReceived() { mLogger.e("Fp cancellation not received, transitioning to STOPPED"); + final boolean wasCancellingRestarting = mFingerprintRunningState + == BIOMETRIC_STATE_CANCELLING_RESTARTING; mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; - KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP); + if (wasCancellingRestarting) { + KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); + } else { + KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP); + } } private void handleFingerprintError(int msgId, String errString) { @@ -1038,6 +1073,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab () -> updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE), getBiometricLockoutDelay()); } else { + boolean temporaryLockoutReset = wasLockout && !mFingerprintLockedOut; + if (temporaryLockoutReset) { + mLogger.d("temporaryLockoutReset - stopListeningForFingerprint() to stop" + + " detectFingerprint"); + stopListeningForFingerprint(); + } updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } @@ -1747,10 +1788,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }; + private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback = + (sensorId, userId, isStrongBiometric) -> { + // Trigger the fingerprint detected path so the bouncer can be shown + handleBiometricDetected(userId, FINGERPRINT, isStrongBiometric); + }; + private final FaceManager.FaceDetectionCallback mFaceDetectionCallback = (sensorId, userId, isStrongBiometric) -> { - // Trigger the face success path so the bouncer can be shown - handleFaceAuthenticated(userId, isStrongBiometric); + // Trigger the face detected path so the bouncer can be shown + handleBiometricDetected(userId, FACE, isStrongBiometric); }; @VisibleForTesting @@ -2783,8 +2830,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean shouldListen = shouldListenKeyguardState && shouldListenUserState && shouldListenBouncerState && shouldListenUdfpsState - && shouldListenSideFpsState - && !isFingerprintLockedOut(); + && shouldListenSideFpsState; logListenerModelData( new KeyguardFingerprintListenModel( System.currentTimeMillis(), @@ -2939,11 +2985,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.v("startListeningForFingerprint - detect"); mFpm.detectFingerprint( mFingerprintCancelSignal, - (sensorId, user, isStrongBiometric) -> { - mLogger.d("fingerprint detected"); - // Trigger the fingerprint success path so the bouncer can be shown - handleFingerprintAuthenticated(user, isStrongBiometric); - }, + mFingerprintDetectionCallback, new FingerprintAuthenticateOptions.Builder() .setUserId(userId) .build()); @@ -2983,6 +3025,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (unlockPossible) { mFaceCancelSignal = new CancellationSignal(); + final FaceAuthenticateOptions faceAuthenticateOptions = + new SysUiFaceAuthenticateOptions( + userId, + faceAuthUiEvent, + faceAuthUiEvent == FACE_AUTH_UPDATED_STARTED_WAKING_UP + ? faceAuthUiEvent.getExtraInfo() + : WAKE_REASON_UNKNOWN + ).toFaceAuthenticateOptions(); // This would need to be updated for multi-sensor devices final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty() && mFaceSensorProperties.get(0).supportsFaceDetection; @@ -2993,9 +3043,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Run face detection. (If a face is detected, show the bouncer.) mLogger.v("startListeningForFace - detect"); mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, - new FaceAuthenticateOptions.Builder() - .setUserId(userId) - .build()); + faceAuthenticateOptions); } else { // Don't run face detection. Instead, inform the user // face auth is unavailable and how to proceed. @@ -3014,7 +3062,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean isBypassEnabled = mKeyguardBypassController != null && mKeyguardBypassController.isBypassEnabled(); mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal, - mFaceAuthenticationCallback, null /* handler */, userId); + mFaceAuthenticationCallback, null /* handler */, + faceAuthenticateOptions); } setFaceRunningState(BIOMETRIC_STATE_RUNNING); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 38f3e5065eec..0d4889a4c39f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -214,7 +214,7 @@ public class KeyguardUpdateMonitorCallback { public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { } /** - * Called when a biometric is recognized. + * Called when a biometric is authenticated. * @param userId the user id for which the biometric sample was authenticated * @param biometricSourceType */ @@ -222,6 +222,14 @@ public class KeyguardUpdateMonitorCallback { boolean isStrongBiometric) { } /** + * Called when a biometric is detected but not successfully authenticated. + * @param userId the user id for which the biometric sample was detected + * @param biometricSourceType + */ + public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType, + boolean isStrongBiometric) { } + + /** * Called when biometric authentication provides help string (e.g. "Try again") * @param msgId * @param helpString diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 7e48193bfc62..a678edc0eb06 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -28,7 +28,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; -import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -49,7 +48,6 @@ public class KeyguardVisibilityHelper { private boolean mAnimateYPos; private boolean mKeyguardViewVisibilityAnimating; private boolean mLastOccludedState = false; - private boolean mIsUnoccludeTransitionFlagEnabled = false; private final AnimationProperties mAnimationProperties = new AnimationProperties(); private final LogBuffer mLogBuffer; @@ -77,10 +75,6 @@ public class KeyguardVisibilityHelper { return mKeyguardViewVisibilityAnimating; } - public void setOcclusionTransitionFlagEnabled(boolean enabled) { - mIsUnoccludeTransitionFlagEnabled = enabled; - } - /** * Set the visibility of a keyguard view based on some new state. */ @@ -156,24 +150,9 @@ public class KeyguardVisibilityHelper { // since it may need to be cancelled due to keyguard lifecycle events. mScreenOffAnimationController.animateInKeyguard( mView, mAnimateKeyguardStatusViewVisibleEndRunnable); - } else if (!mIsUnoccludeTransitionFlagEnabled && mLastOccludedState && !isOccluded) { - // An activity was displayed over the lock screen, and has now gone away - log("Unoccluded transition"); - mView.setVisibility(View.VISIBLE); - mView.setAlpha(0f); - - mView.animate() - .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(1f) - .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) - .start(); } else { log("Direct set Visibility to VISIBLE"); mView.setVisibility(View.VISIBLE); - if (!mIsUnoccludeTransitionFlagEnabled) { - mView.setAlpha(1f); - } } } else { log("Direct set Visibility to GONE"); diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index c414c088529c..e53f6adb62a4 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -167,6 +167,20 @@ class KeyguardUpdateMonitorLogger @Inject constructor( }, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"}) } + fun logFaceDetected(userId: Int, isStrongBiometric: Boolean) { + logBuffer.log(TAG, DEBUG, { + int1 = userId + bool1 = isStrongBiometric + }, {"Face detected: userId: $int1, isStrongBiometric: $bool1"}) + } + + fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) { + logBuffer.log(TAG, DEBUG, { + int1 = userId + bool1 = isStrongBiometric + }, {"Fingerprint detected: userId: $int1, isStrongBiometric: $bool1"}) + } + fun logFingerprintError(msgId: Int, originalErrMsg: String) { logBuffer.log(TAG, DEBUG, { str1 = originalErrMsg diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index e698faffd3f6..08efd89029ed 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -85,6 +85,8 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -98,8 +100,6 @@ import java.util.Set; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; - /** * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the * appropriate biometric UI (e.g. BiometricDialogView). @@ -872,7 +872,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } /** - * Stores the callback received from {@link com.android.server.display.DisplayModeDirector}. + * Stores the callback received from + * {@link com.android.server.display.mode.DisplayModeDirector}. * * DisplayModeDirector implements {@link IUdfpsRefreshRateRequestCallback} * and registers it with this class by calling diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index cef415c8a490..98a3e4b55256 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -130,7 +130,8 @@ final class WirelessChargingLayout extends FrameLayout { animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); // For tablet docking animation, we don't play the background scrim. - if (!Utilities.isTablet(context)) { + // TODO(b/270524780): use utility to check for tablet instead. + if (!Utilities.isLargeScreen(context)) { ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 9921b1fcd4bc..b86d419f540f 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -47,10 +47,7 @@ import com.android.systemui.reardisplay.RearDisplayDialogController import com.android.systemui.recents.Recents import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher -import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo import com.android.systemui.statusbar.notification.InstantAppNotifier -import com.android.systemui.statusbar.notification.fsi.FsiChromeViewModelFactory -import com.android.systemui.statusbar.notification.fsi.FsiChromeViewBinder import com.android.systemui.statusbar.phone.KeyguardLiftController import com.android.systemui.stylus.StylusUsiPowerStartable import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator @@ -91,24 +88,6 @@ abstract class SystemUICoreStartableModule { @ClassKey(ClipboardListener::class) abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable - /** Inject into FsiChromeRepo. */ - @Binds - @IntoMap - @ClassKey(FsiChromeRepo::class) - abstract fun bindFSIChromeRepo(sysui: FsiChromeRepo): CoreStartable - - /** Inject into FsiChromeWindowViewModel. */ - @Binds - @IntoMap - @ClassKey(FsiChromeViewModelFactory::class) - abstract fun bindFSIChromeWindowViewModel(sysui: FsiChromeViewModelFactory): CoreStartable - - /** Inject into FsiChromeWindowBinder. */ - @Binds - @IntoMap - @ClassKey(FsiChromeViewBinder::class) - abstract fun bindFsiChromeWindowBinder(sysui: FsiChromeViewBinder): CoreStartable - /** Inject into GlobalActionsComponent. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt new file mode 100644 index 000000000000..1390b4db3576 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt @@ -0,0 +1,32 @@ +/* + * 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.devicepolicy + +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL +import android.content.ComponentName + +/** Returns true if the admin of [userId] disallows keyguard shortcuts. */ +fun DevicePolicyManager.areKeyguardShortcutsDisabled( + admin: ComponentName? = null, + userId: Int +): Boolean { + val flags = getKeyguardDisabledFeatures(admin, userId) + return flags and KEYGUARD_DISABLE_SHORTCUTS_ALL == KEYGUARD_DISABLE_SHORTCUTS_ALL || + flags and KEYGUARD_DISABLE_FEATURES_ALL == KEYGUARD_DISABLE_FEATURES_ALL +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index f32ea7147a6e..cbbd3e6bb747 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -64,9 +64,6 @@ object Flags { // TODO(b/259130119): Tracking Bug val FSI_ON_DND_UPDATE = releasedFlag(259130119, "fsi_on_dnd_update") - // TODO(b/265804648): Tracking Bug - @JvmField val DISABLE_FSI = unreleasedFlag(265804648, "disable_fsi") - // TODO(b/254512538): Tracking Bug val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply") @@ -87,9 +84,6 @@ object Flags { val NOTIFICATION_GROUP_DISMISSAL_ANIMATION = releasedFlag(259217907, "notification_group_dismissal_animation") - // TODO(b/257506350): Tracking Bug - @JvmField val FSI_CHROME = unreleasedFlag(117, "fsi_chrome") - @JvmField val SIMPLIFIED_APPEAR_FRACTION = unreleasedFlag(259395680, "simplified_appear_fraction", teamfood = true) @@ -155,7 +149,7 @@ object Flags { // TODO(b/255618149): Tracking Bug @JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = - unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true) + releasedFlag(216, "customizable_lock_screen_quick_affordances") /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */ // TODO(b/256513609): Tracking Bug @@ -187,11 +181,7 @@ object Flags { // TODO(b/262780002): Tracking Bug @JvmField - val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = true) - - /** A different path for unocclusion transitions back to keyguard */ - // TODO(b/262859270): Tracking Bug - @JvmField val UNOCCLUSION_TRANSITION = releasedFlag(223, "unocclusion_transition") + val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui") // flag for controlling auto pin confirmation and material u shapes in bouncer @JvmField @@ -214,10 +204,9 @@ object Flags { // TODO(b/266242192): Tracking Bug @JvmField val LOCK_SCREEN_LONG_PRESS_ENABLED = - unreleasedFlag( + releasedFlag( 228, - "lock_screen_long_press_enabled", - teamfood = true, + "lock_screen_long_press_enabled" ) // 300 - power menu @@ -383,6 +372,9 @@ object Flags { // TODO(b/265045965): Tracking Bug val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot") + @JvmField + val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(1004, "enable_low_light_clock_undocked") + // 1100 - windowing @Keep @JvmField @@ -491,9 +483,9 @@ object Flags { val WM_ENABLE_PREDICTIVE_BACK_SYSUI = unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true) - // TODO(b/255697805): Tracking Bug + // TODO(b/270987164): Tracking Bug @JvmField - val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = false) + val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = true) // TODO(b/263826204): Tracking Bug @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 0ca9115723a3..57c4b36b8b7a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -945,9 +945,9 @@ class KeyguardUnlockAnimationController @Inject constructor( return false } - // We don't do the shared element on tablets because they're large and the smartspace has to - // fly across large distances, which is distracting. - if (Utilities.isTablet(context)) { + // We don't do the shared element on large screens because the smartspace has to fly across + // large distances, which is distracting. + if (Utilities.isLargeScreen(context)) { return false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 02bee3efbe2f..2ad1ab722d55 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -127,8 +127,6 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -522,8 +520,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private CentralSurfaces mCentralSurfaces; - private boolean mUnocclusionTransitionFlagEnabled = false; - private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -970,9 +966,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { - if (!mUnocclusionTransitionFlagEnabled) { - setOccluded(true /* isOccluded */, true /* animate */); - } if (apps == null || apps.length == 0 || apps[0] == null) { if (DEBUG) { Log.d(TAG, "No apps provided to the OccludeByDream runner; " @@ -1023,7 +1016,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onAnimationEnd(Animator animation) { try { - if (!mIsCancelled && mUnocclusionTransitionFlagEnabled) { + if (!mIsCancelled) { // We're already on the main thread, don't queue this call handleSetOccluded(true /* isOccluded */, false /* animate */); @@ -1200,7 +1193,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, - FeatureFlags featureFlags, Lazy<ShadeController> shadeControllerLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, @@ -1259,7 +1251,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS; mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS; - mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION); } public void userActivity() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 98d3570106ce..47ef0fac17ab 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -39,7 +39,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -113,7 +112,6 @@ public class KeyguardModule { ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, - FeatureFlags featureFlags, Lazy<ShadeController> shadeController, Lazy<NotificationShadeWindowController> notificationShadeWindowController, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, @@ -144,7 +142,6 @@ public class KeyguardModule { screenOnCoordinator, interactionJankMonitor, dreamOverlayStateController, - featureFlags, shadeController, notificationShadeWindowController, activityLaunchAnimator, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index dfbe1c216847..9b5f7f606c60 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -26,6 +26,7 @@ import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig @@ -410,16 +411,10 @@ constructor( ) } - private suspend fun isFeatureDisabledByDevicePolicy(): Boolean { - val flags = - withContext(backgroundDispatcher) { - devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId) - } - val flagsToCheck = - DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL or - DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL - return flagsToCheck and flags != 0 - } + private suspend fun isFeatureDisabledByDevicePolicy(): Boolean = + withContext(backgroundDispatcher) { + devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId) + } companion object { private const val TAG = "KeyguardQuickAffordanceInteractor" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt new file mode 100644 index 000000000000..a79513ebd867 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.model + +import android.hardware.face.FaceAuthenticateOptions +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_ASSISTANT_VISIBLE +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_UDFPS_POINTER_DOWN +import android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_UNKNOWN +import android.hardware.face.FaceAuthenticateOptions.AuthenticateReason +import android.os.PowerManager +import android.os.PowerManager.WAKE_REASON_UNKNOWN +import android.util.Log +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.keyguard.FaceAuthUiEvent + +/** + * Wrapper for [FaceAuthenticateOptions] to convert SystemUI values to their corresponding value in + * [FaceAuthenticateOptions]. + */ +data class SysUiFaceAuthenticateOptions( + val userId: Int, + private val faceAuthUiEvent: UiEventLogger.UiEventEnum, + @PowerManager.WakeReason val wakeReason: Int = WAKE_REASON_UNKNOWN +) { + val authenticateReason = setAuthenticateReason(faceAuthUiEvent) + + /** + * The [FaceAuthUiEvent] for this operation. This method converts the UiEvent to the framework + * [AuthenticateReason]. + */ + @AuthenticateReason + fun setAuthenticateReason(uiEvent: UiEventLogger.UiEventEnum): Int { + return when (uiEvent) { + FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP -> { + AUTHENTICATE_REASON_STARTED_WAKING_UP + } + FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, + FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN -> { + AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN + } + FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED -> { + AUTHENTICATE_REASON_ASSISTANT_VISIBLE + } + FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN -> { + AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN + } + FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED -> { + AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED + } + FaceAuthUiEvent.FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED -> { + AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED + } + FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED -> { + AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED + } + FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER -> { + AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER + } + FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN -> { + AUTHENTICATE_REASON_UDFPS_POINTER_DOWN + } + else -> { + Log.e("FaceAuthenticateOptions", " unmapped FaceAuthUiEvent $uiEvent") + AUTHENTICATE_REASON_UNKNOWN + } + } + } + + /** Builds the instance. */ + fun toFaceAuthenticateOptions(): FaceAuthenticateOptions { + return FaceAuthenticateOptions.Builder() + .setUserId(userId) + .setAuthenticateReason(authenticateReason) + .setWakeReason(wakeReason) + .build() + } +} 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 d4991f90a86b..9b9d561b5180 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 @@ -33,7 +33,7 @@ import com.android.systemui.R import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.shared.recents.model.ThumbnailData import com.android.systemui.shared.recents.utilities.PreviewPositionHelper -import com.android.systemui.shared.recents.utilities.Utilities.isTablet +import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen /** * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData]. @@ -150,9 +150,9 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 val displayWidthPx = windowMetrics.bounds.width() val displayHeightPx = windowMetrics.bounds.height() val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL - val isTablet = isTablet(context) + val isLargeScreen = isLargeScreen(context) val taskbarSize = - if (isTablet) { + if (isLargeScreen) { resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height) } else { 0 @@ -166,7 +166,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 displayWidthPx, displayHeightPx, taskbarSize, - isTablet, + isLargeScreen, currentRotation, isRtl ) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt index 88d5eaaff216..1c901540ed39 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt @@ -23,7 +23,7 @@ import android.view.WindowManager import com.android.internal.R as AndroidR import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener -import com.android.systemui.shared.recents.utilities.Utilities.isTablet +import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener @@ -61,8 +61,8 @@ constructor( val width = windowMetrics.bounds.width() var height = maximumWindowHeight - val isTablet = isTablet(context) - if (isTablet) { + val isLargeScreen = isLargeScreen(context) + if (isLargeScreen) { val taskbarSize = context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height) height -= taskbarSize diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index d5d73258bb08..4db1da3f1c95 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -41,7 +41,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSE import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS; import static com.android.systemui.navigationbar.NavBarHelper.transitionMode; import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; -import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; +import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; @@ -1792,7 +1792,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; - if (!isTablet(mContext)) { + if (!isLargeScreen(mContext)) { // All IME functions handled by launcher via Sysui flags for large screen final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; final boolean oldBackAlt = diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 3c1746532a02..63d977ed33a7 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -21,7 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; -import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; +import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; import android.content.Context; import android.content.pm.ActivityInfo; @@ -91,7 +91,7 @@ public class NavigationBarController implements private final DisplayManager mDisplayManager; private final TaskbarDelegate mTaskbarDelegate; private int mNavMode; - @VisibleForTesting boolean mIsTablet; + @VisibleForTesting boolean mIsLargeScreen; /** A displayId - nav bar maps. */ @VisibleForTesting @@ -138,16 +138,16 @@ public class NavigationBarController implements navBarHelper, navigationModeController, sysUiFlagsContainer, dumpManager, autoHideController, lightBarController, pipOptional, backAnimation.orElse(null), taskStackChangeListeners); - mIsTablet = isTablet(mContext); + mIsLargeScreen = isLargeScreen(mContext); dumpManager.registerDumpable(this); } @Override public void onConfigChanged(Configuration newConfig) { - boolean isOldConfigTablet = mIsTablet; - mIsTablet = isTablet(mContext); + boolean isOldConfigLargeScreen = mIsLargeScreen; + mIsLargeScreen = isLargeScreen(mContext); boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources()); - boolean largeScreenChanged = mIsTablet != isOldConfigTablet; + boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen; // TODO(b/243765256): Disable this logging once b/243765256 is fixed. Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized() @@ -235,8 +235,9 @@ public class NavigationBarController implements /** @return {@code true} if taskbar is enabled, false otherwise */ private boolean initializeTaskbarIfNecessary() { - // Enable for tablet or (phone AND flag is set); assuming phone = !mIsTablet - boolean taskbarEnabled = mIsTablet || mFeatureFlags.isEnabled(Flags.HIDE_NAVBAR_WINDOW); + // Enable for large screens or (phone AND flag is set); assuming phone = !mIsLargeScreen + boolean taskbarEnabled = mIsLargeScreen || mFeatureFlags.isEnabled( + Flags.HIDE_NAVBAR_WINDOW); if (taskbarEnabled) { Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary"); @@ -258,7 +259,7 @@ public class NavigationBarController implements @Override public void onDisplayReady(int displayId) { Display display = mDisplayManager.getDisplay(displayId); - mIsTablet = isTablet(mContext); + mIsLargeScreen = isLargeScreen(mContext); createNavigationBar(display, null /* savedState */, null /* result */); } @@ -470,7 +471,7 @@ public class NavigationBarController implements @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("mIsTablet=" + mIsTablet); + pw.println("mIsLargeScreen=" + mIsLargeScreen); pw.println("mNavMode=" + mNavMode); for (int i = 0; i < mNavigationBars.size(); i++) { if (i > 0) { diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index be615d63a3d7..c65f0aaab91f 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -17,20 +17,26 @@ package com.android.systemui.notetask import android.app.KeyguardManager +import android.app.admin.DevicePolicyManager import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.os.Build import android.os.UserManager import android.util.Log -import com.android.internal.logging.UiEvent -import com.android.internal.logging.UiEventLogger +import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity +import com.android.systemui.settings.UserTracker import com.android.systemui.util.kotlin.getOrNull +import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles +import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener import java.util.Optional +import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject /** @@ -41,18 +47,42 @@ import javax.inject.Inject * Currently, we only support a single task per time. */ @SysUISingleton -internal class NoteTaskController +class NoteTaskController @Inject constructor( private val context: Context, private val resolver: NoteTaskInfoResolver, + private val eventLogger: NoteTaskEventLogger, private val optionalBubbles: Optional<Bubbles>, - private val optionalKeyguardManager: Optional<KeyguardManager>, private val optionalUserManager: Optional<UserManager>, + private val optionalKeyguardManager: Optional<KeyguardManager>, @NoteTaskEnabledKey private val isEnabled: Boolean, - private val uiEventLogger: UiEventLogger, + private val devicePolicyManager: DevicePolicyManager, + private val userTracker: UserTracker, ) { + @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>() + + /** @see BubbleExpandListener */ + fun onBubbleExpandChanged(isExpanding: Boolean, key: String?) { + if (!isEnabled) return + + if (key != Bubble.KEY_APP_BUBBLE) return + + val info = infoReference.getAndSet(null) + + // Safe guard mechanism, this callback should only be called for app bubbles. + if (info?.launchMode != NoteTaskLaunchMode.AppBubble) return + + if (isExpanding) { + logDebug { "onBubbleExpandChanged - expanding: $info" } + eventLogger.logNoteTaskOpened(info) + } else { + logDebug { "onBubbleExpandChanged - collapsing: $info" } + eventLogger.logNoteTaskClosed(info) + } + } + /** * Shows a note task. How the task is shown will depend on when the method is invoked. * @@ -69,32 +99,62 @@ constructor( * That will let users open other apps in full screen, and take contextual notes. */ @JvmOverloads - fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) { - + fun showNoteTask( + entryPoint: NoteTaskEntryPoint, + isInMultiWindowMode: Boolean = false, + ) { if (!isEnabled) return val bubbles = optionalBubbles.getOrNull() ?: return - val keyguardManager = optionalKeyguardManager.getOrNull() ?: return val userManager = optionalUserManager.getOrNull() ?: return + val keyguardManager = optionalKeyguardManager.getOrNull() ?: return // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing. if (!userManager.isUserUnlocked) return - val noteTaskInfo = resolver.resolveInfo() ?: return + val isKeyguardLocked = keyguardManager.isKeyguardLocked + // KeyguardQuickAffordanceInteractor blocks the quick affordance from showing in the + // keyguard if it is not allowed by the admin policy. Here we block any other way to show + // note task when the screen is locked. + if ( + isKeyguardLocked && + devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId) + ) { + logDebug { "Enterprise policy disallows launching note app when the screen is locked." } + return + } + + val info = + resolver.resolveInfo( + entryPoint = entryPoint, + isInMultiWindowMode = isInMultiWindowMode, + isKeyguardLocked = isKeyguardLocked, + ) + ?: return - uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) } + infoReference.set(info) // TODO(b/266686199): We should handle when app not available. For now, we log. - val intent = noteTaskInfo.toCreateNoteIntent() + val intent = createNoteIntent(info) try { - if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) { - context.startActivity(intent) - } else { - bubbles.showOrHideAppBubble(intent) + logDebug { "onShowNoteTask - start: $info" } + when (info.launchMode) { + is NoteTaskLaunchMode.AppBubble -> { + bubbles.showOrHideAppBubble(intent) + // App bubble logging happens on `onBubbleExpandChanged`. + logDebug { "onShowNoteTask - opened as app bubble: $info" } + } + is NoteTaskLaunchMode.Activity -> { + context.startActivity(intent) + eventLogger.logNoteTaskOpened(info) + logDebug { "onShowNoteTask - opened as activity: $info" } + } } + logDebug { "onShowNoteTask - success: $info" } } catch (e: ActivityNotFoundException) { - Log.e(TAG, "Activity not found for action: $ACTION_CREATE_NOTE.", e) + logDebug { "onShowNoteTask - failed: $info" } } + logDebug { "onShowNoteTask - compoleted: $info" } } /** @@ -119,41 +179,12 @@ constructor( enabledState, PackageManager.DONT_KILL_APP, ) - } - - /** IDs of UI events accepted by [showNoteTask]. */ - enum class ShowNoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum { - @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.") - NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294), - /* ktlint-disable max-line-length */ - @UiEvent( - doc = - "User opened a note by pressing the stylus tail button while the screen was unlocked." - ) - NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295), - @UiEvent( - doc = - "User opened a note by pressing the stylus tail button while the screen was locked." - ) - NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296), - @UiEvent(doc = "User opened a note by tapping on an app shortcut.") - NOTE_OPENED_VIA_SHORTCUT(1297); - - override fun getId() = _id + logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" } } companion object { - private val TAG = NoteTaskController::class.simpleName.orEmpty() - - private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent { - return Intent(ACTION_CREATE_NOTE) - .setPackage(packageName) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint - // was used to start it. - .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true) - } + val TAG = NoteTaskController::class.simpleName.orEmpty() // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead. const val NOTE_TASK_KEY_EVENT = 311 @@ -165,3 +196,17 @@ constructor( const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE" } } + +private fun createNoteIntent(info: NoteTaskInfo): Intent = + Intent(NoteTaskController.ACTION_CREATE_NOTE) + .setPackage(info.packageName) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint + // was used to start it. + .putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true) + +private inline fun logDebug(message: () -> String) { + if (Build.IS_DEBUGGABLE) { + Log.d(NoteTaskController.TAG, message()) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt index e0bf1da2f652..a2563919955a 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt @@ -19,4 +19,4 @@ package com.android.systemui.notetask import javax.inject.Qualifier /** Key associated with a [Boolean] flag that enables or disables the note task feature. */ -@Qualifier internal annotation class NoteTaskEnabledKey +@Qualifier annotation class NoteTaskEnabledKey diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt new file mode 100644 index 000000000000..acc537a8eb36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt @@ -0,0 +1,41 @@ +/* + * 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.notetask + +import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceConfig +import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity +import com.android.systemui.screenshot.AppClipsTrampolineActivity + +/** + * Supported entry points for [NoteTaskController.showNoteTask]. + * + * An entry point represents where the note task has ben called from. In rare cases, it may + * represent a "re-entry" (i.e., [APP_CLIPS]). + */ +enum class NoteTaskEntryPoint { + + /** @see [LaunchNoteTaskActivity] */ + WIDGET_PICKER_SHORTCUT, + + /** @see [NoteTaskQuickAffordanceConfig] */ + QUICK_AFFORDANCE, + + /** @see [NoteTaskInitializer.callbacks] */ + TAIL_BUTTON, + + /** @see [AppClipsTrampolineActivity] */ + APP_CLIPS, +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt new file mode 100644 index 000000000000..16dd16ee137e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.notetask + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS +import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE +import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON +import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED +import javax.inject.Inject + +/** + * A wrapper around [UiEventLogger] specialized in the note taking UI events. + * + * if the accepted [NoteTaskInfo] contains a [NoteTaskInfo.entryPoint], it will be logged as the + * correct [NoteTaskUiEvent]. If null, it will be ignored. + * + * @see NoteTaskController for usage examples. + */ +class NoteTaskEventLogger @Inject constructor(private val uiEventLogger: UiEventLogger) { + + /** Logs a [NoteTaskInfo] as an **open** [NoteTaskUiEvent], including package name and uid. */ + fun logNoteTaskOpened(info: NoteTaskInfo) { + val event = + when (info.entryPoint) { + TAIL_BUTTON -> { + if (info.isKeyguardLocked) { + NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED + } else { + NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON + } + } + WIDGET_PICKER_SHORTCUT -> NOTE_OPENED_VIA_SHORTCUT + QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE + APP_CLIPS -> return + null -> return + } + uiEventLogger.log(event, info.uid, info.packageName) + } + + /** Logs a [NoteTaskInfo] as a **closed** [NoteTaskUiEvent], including package name and uid. */ + fun logNoteTaskClosed(info: NoteTaskInfo) { + val event = + when (info.entryPoint) { + TAIL_BUTTON -> { + if (info.isKeyguardLocked) { + NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED + } else { + NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON + } + } + WIDGET_PICKER_SHORTCUT -> return + QUICK_AFFORDANCE -> return + APP_CLIPS -> return + null -> return + } + uiEventLogger.log(event, info.uid, info.packageName) + } + + /** IDs of UI events accepted by [NoteTaskController]. */ + enum class NoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum { + + @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.") + NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294), + + @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was unlocked.") // ktlint-disable max-line-length + NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295), + + @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was locked.") // ktlint-disable max-line-length + NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296), + + @UiEvent(doc = "User opened a note by tapping on an app shortcut.") + NOTE_OPENED_VIA_SHORTCUT(1297), + + @UiEvent(doc = "Note closed via a tail button while device is unlocked") + NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON(1311), + + @UiEvent(doc = "Note closed via a tail button while device is locked") + NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1312); + + override fun getId() = _id + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt new file mode 100644 index 000000000000..28d76474efba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt @@ -0,0 +1,33 @@ +/* + * 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.notetask + +/** Contextual information required to launch a Note Task by [NoteTaskController]. */ +data class NoteTaskInfo( + val packageName: String, + val uid: Int, + val entryPoint: NoteTaskEntryPoint? = null, + val isInMultiWindowMode: Boolean = false, + val isKeyguardLocked: Boolean = false, +) { + + val launchMode: NoteTaskLaunchMode = + if (isInMultiWindowMode || isKeyguardLocked) { + NoteTaskLaunchMode.Activity + } else { + NoteTaskLaunchMode.AppBubble + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt index bd822d40b950..b5d757c6c287 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt @@ -19,51 +19,56 @@ package com.android.systemui.notetask import android.app.role.RoleManager import android.content.Context import android.content.pm.PackageManager +import android.content.pm.PackageManager.ApplicationInfoFlags import android.os.UserHandle import android.util.Log import javax.inject.Inject -internal class NoteTaskInfoResolver +class NoteTaskInfoResolver @Inject constructor( private val context: Context, private val roleManager: RoleManager, private val packageManager: PackageManager, ) { - fun resolveInfo(): NoteTaskInfo? { + fun resolveInfo( + entryPoint: NoteTaskEntryPoint? = null, + isInMultiWindowMode: Boolean = false, + isKeyguardLocked: Boolean = false, + ): NoteTaskInfo? { // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking. val user = context.user val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, user).firstOrNull() if (packageName.isNullOrEmpty()) return null - return NoteTaskInfo(packageName, packageManager.getUidOf(packageName, user)) + return NoteTaskInfo( + packageName = packageName, + uid = packageManager.getUidOf(packageName, user), + entryPoint = entryPoint, + isInMultiWindowMode = isInMultiWindowMode, + isKeyguardLocked = isKeyguardLocked, + ) } - /** Package name and kernel user-ID of a note-taking app. */ - data class NoteTaskInfo(val packageName: String, val uid: Int) - companion object { private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty() - private val EMPTY_APPLICATION_INFO_FLAGS = PackageManager.ApplicationInfoFlags.of(0)!! + // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead. + const val ROLE_NOTES = "android.app.role.NOTES" + + private val EMPTY_APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0)!! /** * Returns the kernel user-ID of [packageName] for a [user]. Returns zero if the app cannot * be found. */ - private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int { - val applicationInfo = - try { - getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user) - } catch (e: PackageManager.NameNotFoundException) { - Log.e(TAG, "Couldn't find notes app UID", e) - return 0 - } - return applicationInfo.uid - } - - // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead. - const val ROLE_NOTES = "android.app.role.NOTES" + private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int = + try { + getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user).uid + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Couldn't find notes app UID", e) + 0 + } } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index d40bf2b49975..3f4f8d538bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.systemui.notetask -import android.app.KeyguardManager import androidx.annotation.VisibleForTesting import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.util.kotlin.getOrNull import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import javax.inject.Inject @@ -28,11 +25,10 @@ import javax.inject.Inject internal class NoteTaskInitializer @Inject constructor( - private val optionalBubbles: Optional<Bubbles>, - private val noteTaskController: NoteTaskController, + private val controller: NoteTaskController, private val commandQueue: CommandQueue, + private val optionalBubbles: Optional<Bubbles>, @NoteTaskEnabledKey private val isEnabled: Boolean, - private val optionalKeyguardManager: Optional<KeyguardManager>, ) { @VisibleForTesting @@ -40,29 +36,17 @@ constructor( object : CommandQueue.Callbacks { override fun handleSystemKey(keyCode: Int) { if (keyCode == NoteTaskController.NOTE_TASK_KEY_EVENT) { - showNoteTask() + controller.showNoteTask(NoteTaskEntryPoint.TAIL_BUTTON) } } } - private fun showNoteTask() { - val uiEvent = - if (optionalKeyguardManager.isKeyguardLocked) { - NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED - } else { - NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON - } - noteTaskController.showNoteTask(uiEvent = uiEvent) - } - fun initialize() { - if (isEnabled && optionalBubbles.isPresent) { - commandQueue.addCallback(callbacks) - } - noteTaskController.setNoteTaskShortcutEnabled(isEnabled) + controller.setNoteTaskShortcutEnabled(isEnabled) + + // Guard against feature not being enabled or mandatory dependencies aren't available. + if (!isEnabled || optionalBubbles.isEmpty) return + + commandQueue.addCallback(callbacks) } } - -private val Optional<KeyguardManager>.isKeyguardLocked: Boolean - // If there's no KeyguardManager, assume that the keyguard is not locked. - get() = getOrNull()?.isKeyguardLocked ?: false diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt new file mode 100644 index 000000000000..836e103f4d69 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt @@ -0,0 +1,33 @@ +/* + * 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.notetask + +import android.content.Context +import com.android.wm.shell.bubbles.Bubbles + +/** + * Supported ways for launching a note taking experience. + * + * @see [NoteTaskController.showNoteTask] + */ +sealed class NoteTaskLaunchMode { + + /** @see Bubbles.showOrHideAppBubble */ + object AppBubble : NoteTaskLaunchMode() + + /** @see Context.startActivity */ + object Activity : NoteTaskLaunchMode() +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt index b8800a242d06..f16110d7cf5b 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt @@ -36,7 +36,7 @@ import java.util.Optional /** Compose all dependencies required by Note Task feature. */ @Module(includes = [NoteTaskQuickAffordanceModule::class]) -internal interface NoteTaskModule { +interface NoteTaskModule { @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)] fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index 43869ccda2b1..30660c492baa 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -27,12 +27,12 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState import com.android.systemui.notetask.NoteTaskController -import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent import com.android.systemui.notetask.NoteTaskEnabledKey +import com.android.systemui.notetask.NoteTaskEntryPoint import javax.inject.Inject import kotlinx.coroutines.flow.flowOf -internal class NoteTaskQuickAffordanceConfig +class NoteTaskQuickAffordanceConfig @Inject constructor( context: Context, @@ -66,7 +66,7 @@ constructor( override fun onTriggered(expandable: Expandable?): OnTriggeredResult { noteTaskController.showNoteTask( - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE + entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE, ) return OnTriggeredResult.Handled } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt index 7cb932aa1916..2d63dbcb82fa 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt @@ -22,7 +22,7 @@ import dagger.Module import dagger.multibindings.IntoSet @Module -internal interface NoteTaskQuickAffordanceModule { +interface NoteTaskQuickAffordanceModule { @[Binds IntoSet] fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt index 6ab0da6fe3b3..0a1d0089735f 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt @@ -36,7 +36,7 @@ import javax.inject.Inject * href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating * a custom shortcut activity</a> */ -internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() { +class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt index 3ac5bfa09aaa..2b84bf8b4e2e 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt @@ -21,11 +21,11 @@ import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import com.android.systemui.notetask.NoteTaskController -import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent +import com.android.systemui.notetask.NoteTaskEntryPoint import javax.inject.Inject /** Activity responsible for launching the note experience, and finish. */ -internal class LaunchNoteTaskActivity +class LaunchNoteTaskActivity @Inject constructor( private val noteTaskController: NoteTaskController, @@ -35,8 +35,8 @@ constructor( super.onCreate(savedInstanceState) noteTaskController.showNoteTask( + entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT, isInMultiWindowMode = isInMultiWindowMode, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, ) finish() diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 645b1256e5f1..346acf958e51 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -16,7 +16,7 @@ package com.android.systemui.recents; -import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; +import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; @@ -265,7 +265,7 @@ public class ScreenPinningRequest implements View.OnClickListener, .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); View buttons = mLayout.findViewById(R.id.screen_pinning_buttons); if (!QuickStepContract.isGesturalMode(mNavBarMode) - && hasSoftNavigationBar(mContext.getDisplayId()) && !isTablet(mContext)) { + && hasSoftNavigationBar(mContext.getDisplayId()) && !isLargeScreen(mContext)) { buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); swapChildrenIfRtlAndVertical(buttons); } else { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java index 1946b8eaee5b..eda38e45c98a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java @@ -50,6 +50,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.notetask.NoteTaskController; +import com.android.systemui.notetask.NoteTaskEntryPoint; import com.android.systemui.settings.UserTracker; import com.android.wm.shell.bubbles.Bubbles; @@ -239,9 +240,8 @@ public class AppClipsTrampolineActivity extends Activity { // Broadcast no longer required, setting it to null. mKillAppClipsBroadcastIntent = null; - // Expand the note bubble before returning the result. As App Clips API is only - // available when in a bubble, isInMultiWindowMode is always false below. - mNoteTaskController.showNoteTask(false); + // Expand the note bubble before returning the result. + mNoteTaskController.showNoteTask(NoteTaskEntryPoint.APP_CLIPS); setResult(RESULT_OK, convertedData); finish(); } diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java index b36f0d7bacfc..10e2afe0baa9 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java @@ -27,6 +27,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Xfermode; import android.graphics.drawable.Drawable; import android.view.animation.DecelerateInterpolator; @@ -41,7 +42,11 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator; public class ScrimDrawable extends Drawable { private static final String TAG = "ScrimDrawable"; + private boolean mShouldUseLargeScreenSize; private final Paint mPaint; + private final Path mPath = new Path(); + private final RectF mBoundsRectF = new RectF(); + private int mAlpha = 255; private int mMainColor; private ValueAnimator mColorAnimation; @@ -49,11 +54,13 @@ public class ScrimDrawable extends Drawable { private float mCornerRadius; private ConcaveInfo mConcaveInfo; private int mBottomEdgePosition; + private float mBottomEdgeRadius = -1; private boolean mCornerRadiusEnabled; public ScrimDrawable() { mPaint = new Paint(); mPaint.setStyle(Paint.Style.FILL); + mShouldUseLargeScreenSize = false; } /** @@ -133,6 +140,10 @@ public class ScrimDrawable extends Drawable { return PixelFormat.TRANSLUCENT; } + public void setShouldUseLargeScreenSize(boolean v) { + mShouldUseLargeScreenSize = v; + } + /** * Corner radius used by either concave or convex corners. */ @@ -191,6 +202,10 @@ public class ScrimDrawable extends Drawable { invalidateSelf(); } + public void setBottomEdgeRadius(float radius) { + mBottomEdgeRadius = radius; + } + @Override public void draw(@NonNull Canvas canvas) { mPaint.setColor(mMainColor); @@ -198,9 +213,46 @@ public class ScrimDrawable extends Drawable { if (mConcaveInfo != null) { drawConcave(canvas); } else if (mCornerRadiusEnabled && mCornerRadius > 0) { - canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right, - getBounds().bottom, - /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint); + float topEdgeRadius = mCornerRadius; + float bottomEdgeRadius = mBottomEdgeRadius == -1.0 ? mCornerRadius : mBottomEdgeRadius; + + mBoundsRectF.set(getBounds()); + + // When the back gesture causes the notification scrim to be scaled down, + // this offset "reveals" the rounded bottom edge as it "pulls away". + // We must *not* make this adjustment on largescreen shades (where the corner is sharp). + if (!mShouldUseLargeScreenSize && mBottomEdgeRadius != -1) { + mBoundsRectF.bottom -= bottomEdgeRadius; + } + + // We need a box with rounded corners but its lower corners are not rounded on large + // screen devices in "portrait" orientation. + // Thus, we cannot draw a symmetric rounded rectangle via canvas.drawRoundRect() + // and must build a box with different corner radii at the top and at the bottom. + // Additionally, when the scrim is pushed to the very bottom of the screen, do not draw + // anything (drawing a rounded box with these specifications is not possible). + // TODO(b/271030611) perhaps this could be accomplished via Path.addRoundRect instead? + if (mBoundsRectF.bottom - mBoundsRectF.top > bottomEdgeRadius) { + mPath.reset(); + mPath.moveTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius); + mPath.cubicTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius, + mBoundsRectF.right, mBoundsRectF.top, + mBoundsRectF.right - topEdgeRadius, mBoundsRectF.top); + mPath.lineTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top); + mPath.cubicTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top, + mBoundsRectF.left, mBoundsRectF.top, + mBoundsRectF.left, mBoundsRectF.top + topEdgeRadius); + mPath.lineTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius); + mPath.cubicTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius, + mBoundsRectF.left, mBoundsRectF.bottom, + mBoundsRectF.left + bottomEdgeRadius, mBoundsRectF.bottom); + mPath.lineTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom); + mPath.cubicTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom, + mBoundsRectF.right, mBoundsRectF.bottom, + mBoundsRectF.right, mBoundsRectF.bottom - bottomEdgeRadius); + mPath.close(); + canvas.drawPath(mPath, mPaint); + } } else { canvas.drawRect(getBounds().left, getBounds().top, getBounds().right, getBounds().bottom, mPaint); diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java index f68e0429ef7c..fc89a9e637ec 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java @@ -20,6 +20,7 @@ import static java.lang.Float.isNaN; import android.annotation.NonNull; import android.content.Context; +import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PorterDuff; @@ -37,6 +38,7 @@ import androidx.core.graphics.ColorUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; +import com.android.systemui.util.LargeScreenUtils; import java.util.concurrent.Executor; @@ -102,6 +104,13 @@ public class ScrimView extends View { @Override protected void onDraw(Canvas canvas) { if (mDrawable.getAlpha() > 0) { + Resources res = getResources(); + // Scrim behind notification shade has sharp (not rounded) corners on large screens + // which scrim itself cannot know, so we set it here. + if (mDrawable instanceof ScrimDrawable) { + ((ScrimDrawable) mDrawable).setShouldUseLargeScreenSize( + LargeScreenUtils.shouldUseLargeScreenShadeHeader(res)); + } mDrawable.draw(canvas); } } @@ -170,6 +179,15 @@ public class ScrimView extends View { }); } + /** + * Set corner radius of the bottom edge of the Notification scrim. + */ + public void setBottomEdgeRadius(float radius) { + if (mDrawable instanceof ScrimDrawable) { + ((ScrimDrawable) mDrawable).setBottomEdgeRadius(radius); + } + } + @VisibleForTesting Drawable getDrawable() { return mDrawable; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index b502b4d02e00..e75320a1f56b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -293,7 +293,19 @@ public final class NotificationPanelViewController implements Dumpable { * custom clock animation is in use. */ private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000; + /** + * Whether the Shade should animate to reflect Back gesture progress. + * To minimize latency at runtime, we cache this, else we'd be reading it every time + * updateQsExpansion() is called... and it's called very often. + * + * Whenever we change this flag, SysUI is restarted, so it's never going to be "stale". + */ + public final boolean mAnimateBack; + /** + * The minimum scale to "squish" the Shade and associated elements down to, for Back gesture + */ + public static final float SHADE_BACK_ANIM_MIN_SCALE = 0.9f; private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; private final Resources mResources; private final KeyguardStateController mKeyguardStateController; @@ -361,6 +373,8 @@ public final class NotificationPanelViewController implements Dumpable { private CentralSurfaces mCentralSurfaces; private HeadsUpManagerPhone mHeadsUpManager; private float mExpandedHeight = 0; + /** The current squish amount for the predictive back animation */ + private float mCurrentBackProgress = 0.0f; private boolean mTracking; private boolean mHintAnimationRunning; private KeyguardBottomAreaView mKeyguardBottomArea; @@ -465,7 +479,7 @@ public final class NotificationPanelViewController implements Dumpable { private int mPanelAlpha; private Runnable mPanelAlphaEndAction; private float mBottomAreaShadeAlpha; - private final ValueAnimator mBottomAreaShadeAlphaAnimator; + final ValueAnimator mBottomAreaShadeAlphaAnimator; private final AnimatableProperty mPanelAlphaAnimator = AnimatableProperty.from("panelAlpha", NotificationPanelView::setPanelAlphaInternal, NotificationPanelView::getCurrentPanelAlpha, @@ -597,7 +611,6 @@ public final class NotificationPanelViewController implements Dumpable { private int mLockscreenToDreamingTransitionTranslationY; private int mGoneToDreamingTransitionTranslationY; private int mLockscreenToOccludedTransitionTranslationY; - private boolean mUnocclusionTransitionFlagEnabled = false; private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */); @@ -816,6 +829,7 @@ public final class NotificationPanelViewController implements Dumpable { mShadeHeaderController = shadeHeaderController; mLayoutInflater = layoutInflater; mFeatureFlags = featureFlags; + mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE); mFalsingCollector = falsingCollector; mPowerManager = powerManager; mWakeUpCoordinator = coordinator; @@ -886,7 +900,6 @@ public final class NotificationPanelViewController implements Dumpable { mNotificationPanelUnfoldAnimationController = unfoldComponent.map( SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController); - mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION); updateUserSwitcherFlags(); mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel; mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; @@ -1045,62 +1058,50 @@ public final class NotificationPanelViewController implements Dumpable { mNotificationPanelUnfoldAnimationController.ifPresent(controller -> controller.setup(mNotificationContainerParent)); - if (mUnocclusionTransitionFlagEnabled) { - // Dreaming->Lockscreen - collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(), - mDreamingToLockscreenTransition, mMainDispatcher); - collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(), - setTransitionAlpha(mNotificationStackScrollLayoutController), - mMainDispatcher); - collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY( - mDreamingToLockscreenTransitionTranslationY), - setTransitionY(mNotificationStackScrollLayoutController), - mMainDispatcher); - - // Occluded->Lockscreen - collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(), - mOccludedToLockscreenTransition, mMainDispatcher); - collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(), - setTransitionAlpha(mNotificationStackScrollLayoutController), - mMainDispatcher); - collectFlow(mView, mOccludedToLockscreenTransitionViewModel.lockscreenTranslationY( - mOccludedToLockscreenTransitionTranslationY), - setTransitionY(mNotificationStackScrollLayoutController), - mMainDispatcher); - - // Lockscreen->Dreaming - collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(), - mLockscreenToDreamingTransition, mMainDispatcher); - collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(), - setTransitionAlpha(mNotificationStackScrollLayoutController), - mMainDispatcher); - collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY( - mLockscreenToDreamingTransitionTranslationY), - setTransitionY(mNotificationStackScrollLayoutController), - mMainDispatcher); - - // Gone->Dreaming - collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(), - mGoneToDreamingTransition, mMainDispatcher); - collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(), - setTransitionAlpha(mNotificationStackScrollLayoutController), - mMainDispatcher); - collectFlow(mView, mGoneToDreamingTransitionViewModel.lockscreenTranslationY( - mGoneToDreamingTransitionTranslationY), - setTransitionY(mNotificationStackScrollLayoutController), - mMainDispatcher); - - // Lockscreen->Occluded - collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(), - mLockscreenToOccludedTransition, mMainDispatcher); - collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(), - setTransitionAlpha(mNotificationStackScrollLayoutController), - mMainDispatcher); - collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY( - mLockscreenToOccludedTransitionTranslationY), - setTransitionY(mNotificationStackScrollLayoutController), - mMainDispatcher); - } + // Dreaming->Lockscreen + collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(), + mDreamingToLockscreenTransition, mMainDispatcher); + collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); + collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY( + mDreamingToLockscreenTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); + + // Occluded->Lockscreen + collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(), + mOccludedToLockscreenTransition, mMainDispatcher); + collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); + collectFlow(mView, mOccludedToLockscreenTransitionViewModel.lockscreenTranslationY( + mOccludedToLockscreenTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); + + // Lockscreen->Dreaming + collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(), + mLockscreenToDreamingTransition, mMainDispatcher); + collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); + collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY( + mLockscreenToDreamingTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); + + // Gone->Dreaming + collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(), + mGoneToDreamingTransition, mMainDispatcher); + collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); + collectFlow(mView, mGoneToDreamingTransitionViewModel.lockscreenTranslationY( + mGoneToDreamingTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); + + // Lockscreen->Occluded + collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(), + mLockscreenToOccludedTransition, mMainDispatcher); + collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); + collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY( + mLockscreenToOccludedTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); } @VisibleForTesting @@ -1965,6 +1966,14 @@ public final class NotificationPanelViewController implements Dumpable { if (mFixedDuration != NO_FIXED_DURATION) { animator.setDuration(mFixedDuration); } + + // Reset Predictive Back animation's transform after Shade is completely hidden. + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + resetBackTransformation(); + } + }); } animator.addListener(new AnimatorListenerAdapter() { private boolean mCancelled; @@ -2189,6 +2198,53 @@ public final class NotificationPanelViewController implements Dumpable { } } + /** + * When the back gesture triggers a fully-expanded shade --> QQS shade collapse transition, + * the expansionFraction goes down from 1.0 --> 0.0 (collapsing), so the current "squish" amount + * (mCurrentBackProgress) must be un-applied from various UI elements in tandem, such that, + * as the shade ends up in its half-expanded state (with QQS above), it is back at 100% scale. + * Without this, the shade would collapse, and stay squished. + */ + public void adjustBackAnimationScale(float expansionFraction) { + if (expansionFraction > 0.0f) { // collapsing + float animatedFraction = expansionFraction * mCurrentBackProgress; + applyBackScaling(animatedFraction); + } else { + // collapsed! reset, so that if we re-expand shade, it won't start off "squished" + mCurrentBackProgress = 0; + } + } + + //TODO(b/270981268): allow cancelling back animation mid-flight + /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */ + public void onBackPressed() { + closeQsIfPossible(); + } + /** Sets back progress. */ + public void onBackProgressed(float progressFraction) { + // TODO: non-linearly transform progress fraction into squish amount (ease-in, linear out) + mCurrentBackProgress = progressFraction; + applyBackScaling(progressFraction); + } + + /** Resets back progress. */ + public void resetBackTransformation() { + mCurrentBackProgress = 0.0f; + applyBackScaling(0.0f); + } + + /** Scales multiple elements in tandem to achieve the illusion of the QS+Shade shrinking + * as a single visual element (used by the Predictive Back Gesture preview animation). + * fraction = 0 implies "no scaling", and 1 means "scale down to minimum size (90%)". + */ + public void applyBackScaling(float fraction) { + if (mNotificationContainerParent == null) { + return; + } + float scale = MathUtils.lerp(1.0f, SHADE_BACK_ANIM_MIN_SCALE, fraction); + mNotificationContainerParent.applyBackScaling(scale, mSplitShadeEnabled); + mScrimController.applyBackScaling(scale); + } /** */ public float getLockscreenShadeDragProgress() { // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade @@ -2480,9 +2536,6 @@ public final class NotificationPanelViewController implements Dumpable { } private void onExpandingFinished() { - if (!mUnocclusionTransitionFlagEnabled) { - mScrimController.onExpandingFinished(); - } mNotificationStackScrollLayoutController.onExpansionStopped(); mHeadsUpManager.onExpandingFinished(); mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed()); @@ -4868,6 +4921,11 @@ public final class NotificationPanelViewController implements Dumpable { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: + if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack) { + // Cache the gesture insets now, so we can quickly query them during + // ACTION_MOVE and decide whether to intercept events for back gesture anim. + mQsController.updateGestureInsetsCache(); + } mShadeLog.logMotionEvent(event, "onTouch: down action"); startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); mMinExpandHeight = 0.0f; @@ -4917,6 +4975,12 @@ public final class NotificationPanelViewController implements Dumpable { } break; case MotionEvent.ACTION_MOVE: + // If the shade is half-collapsed, a horizontal swipe inwards from L/R edge + // must be routed to the back gesture (which shows a preview animation). + if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack + && mQsController.shouldBackBypassQuickSettings(x)) { + return false; + } if (isFullyCollapsed()) { // If panel is fully collapsed, reset haptic effect before adding movement. mHasVibratedOnOpen = false; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 60fa865b83bc..87350b465895 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -38,8 +38,6 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -132,7 +130,6 @@ public class NotificationShadeWindowViewController { NotificationInsetsController notificationInsetsController, AmbientState ambientState, PulsingGestureListener pulsingGestureListener, - FeatureFlags featureFlags, KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, AlternateBouncerInteractor alternateBouncerInteractor, @@ -165,10 +162,8 @@ public class NotificationShadeWindowViewController { keyguardBouncerViewModel, keyguardBouncerComponentFactory); - if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) { - collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), - mLockscreenToDreamingTransition); - } + collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), + mLockscreenToDreamingTransition); } /** diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java index f73dde632051..7dff6ea99029 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java @@ -20,6 +20,7 @@ import android.app.Fragment; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; +import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.view.WindowInsets; @@ -55,6 +56,13 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout private QS mQs; private View mQSContainer; + /** + * These are used to compute the bounding box containing the shade and the notification scrim, + * which is then used to drive the Back gesture animation. + */ + private final Rect mUpperRect = new Rect(); + private final Rect mBoundingBoxRect = new Rect(); + @Nullable private Consumer<Configuration> mConfigurationChangedListener; @@ -172,4 +180,37 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout public void applyConstraints(ConstraintSet constraintSet) { constraintSet.applyTo(this); } + + /** + * Scale multiple elements in tandem, for the predictive back animation. + * This is how the Shade responds to the Back gesture (by scaling). + * Without the common center, individual elements will scale about their respective centers. + * Scaling the entire NotificationsQuickSettingsContainer will also resize the shade header + * (which we don't want). + */ + public void applyBackScaling(float scale, boolean usingSplitShade) { + if (mStackScroller == null || mQSContainer == null) { + return; + } + + mQSContainer.getBoundsOnScreen(mUpperRect); + mStackScroller.getBoundsOnScreen(mBoundingBoxRect); + mBoundingBoxRect.union(mUpperRect); + + float cx = mBoundingBoxRect.centerX(); + float cy = mBoundingBoxRect.centerY(); + + mQSContainer.setPivotX(cx); + mQSContainer.setPivotY(cy); + mQSContainer.setScaleX(scale); + mQSContainer.setScaleY(scale); + + // When in large-screen split-shade mode, the notification stack scroller scales correctly + // only if the pivot point is at the left edge of the screen (because of its dimensions). + // When not in large-screen split-shade mode, we can scale correctly via the (cx,cy) above. + mStackScroller.setPivotX(usingSplitShade ? 0.0f : cx); + mStackScroller.setPivotY(cy); + mStackScroller.setScaleX(scale); + mStackScroller.setScaleY(scale); + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 099ad9473673..6857f4cc9e8e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -31,6 +31,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.Fragment; import android.content.res.Resources; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; import android.util.Log; @@ -40,6 +41,9 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; @@ -63,6 +67,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.transition.ShadeTransitionController; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -83,10 +88,10 @@ import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.LargeScreenUtils; -import javax.inject.Inject; - import dagger.Lazy; +import javax.inject.Inject; + /** Handles QuickSettings touch handling, expansion and animation state * TODO (b/264460656) make this dumpable */ @@ -223,6 +228,13 @@ public class QuickSettingsController { private boolean mAnimatorExpand; /** + * The gesture inset currently in effect -- used to decide whether a back gesture should + * receive a horizontal swipe inwards from the left/right vertical edge of the screen. + * We cache this on ACTION_DOWN, and query it during both ACTION_DOWN and ACTION_MOVE events. + */ + private Insets mCachedGestureInsets; + + /** * The amount of progress we are currently in if we're transitioning to the full shade. * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full * shade. This value can also go beyond 1.1 when we're overshooting! @@ -406,6 +418,7 @@ public class QuickSettingsController { mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight; mEnableClipping = mResources.getBoolean(R.bool.qs_enable_clipping); + updateGestureInsetsCache(); } // TODO (b/265054088): move this and others to a CoreStartable @@ -469,6 +482,26 @@ public class QuickSettingsController { || touchX > mQsFrame.getX() + mQsFrame.getWidth(); } + /** + * Computes (and caches) the gesture insets for the current window. Intended to be called + * on ACTION_DOWN, and safely queried repeatedly thereafter during ACTION_MOVE events. + */ + public void updateGestureInsetsCache() { + WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class); + WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); + mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets( + WindowInsets.Type.systemGestures()); + } + + /** + * Returns whether x coordinate lies in the vertical edges of the screen + * (the only place where a back gesture can be initiated). + */ + public boolean shouldBackBypassQuickSettings(float touchX) { + return (touchX < mCachedGestureInsets.left) + || (touchX > mKeyguardStatusBar.getWidth() - mCachedGestureInsets.right); + } + /** Returns whether touch is within QS area */ private boolean isTouchInQsArea(float x, float y) { if (isSplitShadeAndTouchXOutsideQs(x)) { @@ -926,6 +959,10 @@ public class QuickSettingsController { getHeaderTranslation(), squishiness ); + if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE + && mPanelViewControllerLazy.get().mAnimateBack) { + mPanelViewControllerLazy.get().adjustBackAnimationScale(adjustedExpansionFraction); + } mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction); mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); @@ -1113,6 +1150,7 @@ public class QuickSettingsController { float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius; radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, Math.min(top / (float) mScrimCornerRadius, 1f)); + mScrimController.setNotificationBottomRadius(radius); } if (isQsFragmentCreated()) { float qsTranslation = 0; @@ -1505,18 +1543,31 @@ public class QuickSettingsController { } private void handleDown(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN - && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { - mFalsingCollector.onQsDown(); - mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled"); - mTracking = true; - onExpansionStarted(); - mInitialHeightOnTouch = mExpansionHeight; - mInitialTouchY = event.getY(); - mInitialTouchX = event.getX(); - // TODO (b/265193930): remove dependency on NPVC - // If we interrupt an expansion gesture here, make sure to update the state correctly. - mPanelViewControllerLazy.get().notifyExpandingFinished(); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + // When the shade is fully-expanded, an inward swipe from the L/R edge should first + // allow the back gesture's animation to preview the shade animation (if enabled). + // (swipes starting closer to the center of the screen will not be affected) + if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE + && mPanelViewControllerLazy.get().mAnimateBack) { + updateGestureInsetsCache(); + if (shouldBackBypassQuickSettings(event.getX())) { + return; + } + } + if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { + mFalsingCollector.onQsDown(); + mShadeLog.logMotionEvent(event, + "handleQsDown: down action, QS tracking enabled"); + mTracking = true; + onExpansionStarted(); + mInitialHeightOnTouch = mExpansionHeight; + mInitialTouchY = event.getY(); + mInitialTouchX = event.getX(); + // TODO (b/265193930): remove dependency on NPVC + // If we interrupt an expansion gesture here, make sure to update the state + // correctly. + mPanelViewControllerLazy.get().notifyExpandingFinished(); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java index e9fac28395ea..1cfb400280fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java @@ -39,7 +39,7 @@ public class KeyboardShortcutsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (mIsShortcutListSearchEnabled && Utilities.isTablet(context)) { + if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(context)) { if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) { KeyboardShortcutListSearch.show(context, -1 /* deviceId unknown */); } else if (Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(intent.getAction())) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt index fc89be2c6670..00d8c421c721 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt @@ -28,10 +28,6 @@ class NotifPipelineFlags @Inject constructor( val featureFlags: FeatureFlags, val sysPropFlags: FlagResolver, ) { - init { - featureFlags.addListener(Flags.DISABLE_FSI) { event -> event.requestNoRestart() } - } - fun isDevLoggingEnabled(): Boolean = featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING) @@ -40,8 +36,6 @@ class NotifPipelineFlags @Inject constructor( fun fsiOnDNDUpdate(): Boolean = featureFlags.isEnabled(Flags.FSI_ON_DND_UPDATE) - fun disableFsi(): Boolean = featureFlags.isEnabled(Flags.DISABLE_FSI) - fun forceDemoteFsi(): Boolean = sysPropFlags.isEnabled(NotificationFlags.FSI_FORCE_DEMOTE) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt deleted file mode 100644 index b48322822c86..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepo.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.android.systemui.statusbar.notification.fsi - -import android.app.PendingIntent -import android.content.Context -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable -import android.os.RemoteException -import android.service.dreams.IDreamManager -import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider -import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log -import com.android.systemui.statusbar.phone.CentralSurfaces -import java.util.concurrent.Executor -import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow - -/** - * Class that bridges the gap between clean app architecture and existing code. Provides new - * implementation of StatusBarNotificationActivityStarter launchFullscreenIntent that pipes - * one-directional data => FsiChromeViewModel => FsiChromeView. - */ -@SysUISingleton -class FsiChromeRepo -@Inject -constructor( - private val context: Context, - private val pm: PackageManager, - private val keyguardRepo: KeyguardRepository, - private val launchFullScreenIntentProvider: LaunchFullScreenIntentProvider, - private val featureFlags: FeatureFlags, - private val uiBgExecutor: Executor, - private val dreamManager: IDreamManager, - private val centralSurfaces: CentralSurfaces -) : CoreStartable { - - companion object { - private const val classTag = "FsiChromeRepo" - } - - data class FSIInfo( - val appName: String, - val appIcon: Drawable, - val fullscreenIntent: PendingIntent - ) - - private val _infoFlow = MutableStateFlow<FSIInfo?>(null) - val infoFlow: StateFlow<FSIInfo?> = _infoFlow - - override fun start() { - log("$classTag start listening for FSI notifications") - - // Listen for FSI launch events for the lifetime of SystemUI. - launchFullScreenIntentProvider.registerListener { entry -> launchFullscreenIntent(entry) } - } - - fun dismiss() { - _infoFlow.value = null - } - - fun onFullscreen() { - // TODO(b/243421660) implement transition from container to fullscreen - } - - fun stopScreenSaver() { - uiBgExecutor.execute { - try { - dreamManager.awaken() - } catch (e: RemoteException) { - e.printStackTrace() - } - } - } - - fun launchFullscreenIntent(entry: NotificationEntry) { - if (!featureFlags.isEnabled(Flags.FSI_CHROME)) { - return - } - if (!keyguardRepo.isKeyguardShowing()) { - return - } - stopScreenSaver() - - var appName = pm.getApplicationLabel(context.applicationInfo) as String - val appIcon = pm.getApplicationIcon(context.packageName) - val fullscreenIntent = entry.sbn.notification.fullScreenIntent - - log("FsiChromeRepo launchFullscreenIntent appName=$appName appIcon $appIcon") - _infoFlow.value = FSIInfo(appName, appIcon, fullscreenIntent) - - // If screen is off or we're showing AOD, show lockscreen. - centralSurfaces.wakeUpForFullScreenIntent() - - // Don't show HUN since we're already showing FSI. - entry.notifyFullScreenIntentLaunched() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt deleted file mode 100644 index 6e5fcf40440c..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.android.systemui.statusbar.notification.fsi - -import android.content.Context -import android.graphics.Color -import android.graphics.Color.DKGRAY -import android.graphics.Outline -import android.util.AttributeSet -import android.view.View -import android.view.ViewOutlineProvider -import android.widget.Button -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import com.android.systemui.R -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log - -@SysUISingleton -class FsiChromeView -@JvmOverloads -constructor( - context: Context?, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, - defStyleRes: Int = 0 -) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { - - companion object { - private const val classTag = "FsiChromeView" - } - - lateinit var chromeContainer: LinearLayout - lateinit var appIconImageView: ImageView - lateinit var appNameTextView: TextView - lateinit var dismissButton: Button - lateinit var fullscreenButton: Button - - private val cornerRadius: Float = - resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() - private val vertPadding: Int = - resources.getDimensionPixelSize(R.dimen.fsi_chrome_vertical_padding) - private val sidePadding: Int = - resources.getDimensionPixelSize(R.dimen.notification_side_paddings) - - init { - log("$classTag init") - } - - override fun onFinishInflate() { - log("$classTag onFinishInflate") - super.onFinishInflate() - - setBackgroundColor(Color.TRANSPARENT) - setPadding( - sidePadding, - vertPadding, - sidePadding, - vertPadding - ) // Make smaller than fullscreen. - - chromeContainer = findViewById(R.id.fsi_chrome) - chromeContainer.setBackgroundColor(DKGRAY) - - appIconImageView = findViewById(R.id.fsi_app_icon) - appNameTextView = findViewById(R.id.fsi_app_name) - dismissButton = findViewById(R.id.fsi_dismiss_button) - fullscreenButton = findViewById(R.id.fsi_fullscreen_button) - - outlineProvider = - object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect( - /* left */ sidePadding, - /* top */ vertPadding, - /* right */ view.width - sidePadding, - /* bottom */ view.height - vertPadding, - cornerRadius - ) - } - } - clipToOutline = true - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewBinder.kt deleted file mode 100644 index 1a3927ba9b06..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewBinder.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.android.systemui.statusbar.notification.fsi - -import android.content.Context -import android.view.LayoutInflater -import android.view.WindowManager -import com.android.systemui.CoreStartable -import com.android.systemui.R -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log -import com.android.systemui.statusbar.phone.CentralSurfaces -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.util.concurrent.Executor -import javax.inject.Inject - -@SysUISingleton -class FsiChromeViewBinder -@Inject -constructor( - val context: Context, - val windowManager: WindowManager, - val viewModelFactory: FsiChromeViewModelFactory, - val layoutInflater: LayoutInflater, - val centralSurfaces: CentralSurfaces, - @Main val mainExecutor: Executor, - @Application val scope: CoroutineScope, -) : CoreStartable { - - companion object { - private const val classTag = "FsiChromeViewBinder" - } - - private val fsiChromeView = - layoutInflater.inflate(R.layout.fsi_chrome_view, null /* root */, false /* attachToRoot */) - as FsiChromeView - - var addedToWindowManager = false - var cornerRadius: Int = context.resources.getDimensionPixelSize( - R.dimen.notification_corner_radius) - - override fun start() { - val methodTag = "start" - log("$classTag $methodTag ") - - scope.launch { - log("$classTag $methodTag launch ") - viewModelFactory.viewModelFlow.collect { vm -> updateForViewModel(vm) } - } - } - - private fun updateForViewModel(vm: FsiChromeViewModel?) { - val methodTag = "updateForViewModel" - - if (vm == null) { - log("$classTag $methodTag viewModel is null, removing from window manager") - - if (addedToWindowManager) { - windowManager.removeView(fsiChromeView) - addedToWindowManager = false - } - return - } - - bindViewModel(vm, windowManager) - - if (addedToWindowManager) { - log("$classTag $methodTag already addedToWindowManager") - } else { - windowManager.addView(fsiChromeView, FsiTaskViewConfig.getWmLayoutParams("PackageName")) - addedToWindowManager = true - } - } - - private fun bindViewModel( - vm: FsiChromeViewModel, - windowManager: WindowManager, - ) { - log("$classTag bindViewModel") - - fsiChromeView.appIconImageView.setImageDrawable(vm.appIcon) - fsiChromeView.appNameTextView.text = vm.appName - - fsiChromeView.dismissButton.setOnClickListener { vm.onDismiss() } - fsiChromeView.fullscreenButton.setOnClickListener { vm.onFullscreen() } - - vm.taskView.cornerRadius = cornerRadius.toFloat() - vm.taskView.startActivity( - vm.fsi, - FsiTaskViewConfig.getFillInIntent(), - FsiTaskViewConfig.getActivityOptions(context, windowManager), - FsiTaskViewConfig.getLaunchBounds(windowManager) - ) - - log("$classTag bindViewModel started taskview activity") - fsiChromeView.addView(vm.taskView) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt deleted file mode 100644 index 1ca698b6bd58..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.android.systemui.statusbar.notification.fsi - -import android.annotation.UiContext -import android.app.PendingIntent -import android.content.Context -import android.graphics.drawable.Drawable -import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log -import com.android.wm.shell.TaskView -import com.android.wm.shell.TaskViewFactory -import java.util.Optional -import java.util.concurrent.Executor -import javax.inject.Inject -import kotlin.coroutines.resume -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.suspendCancellableCoroutine - -/** - * Handle view-related data for fullscreen intent container on lockscreen. Wraps FsiChromeRepo, - * transforms events/state into view-relevant representation for FsiChromeView. Alive for lifetime - * of SystemUI. - */ -@SysUISingleton -class FsiChromeViewModelFactory -@Inject -constructor( - val repo: FsiChromeRepo, - val taskViewFactory: Optional<TaskViewFactory>, - @UiContext val context: Context, - @Main val mainExecutor: Executor, -) : CoreStartable { - - companion object { - private const val classTag = "FsiChromeViewModelFactory" - } - - val viewModelFlow: Flow<FsiChromeViewModel?> = - repo.infoFlow.mapLatest { fsiInfo -> - fsiInfo?.let { - log("$classTag viewModelFlow got new fsiInfo") - - // mapLatest emits null when FSIInfo is null - FsiChromeViewModel( - fsiInfo.appName, - fsiInfo.appIcon, - createTaskView(), - fsiInfo.fullscreenIntent, - repo - ) - } - } - - override fun start() { - log("$classTag start") - } - - private suspend fun createTaskView(): TaskView = suspendCancellableCoroutine { k -> - log("$classTag createTaskView") - - taskViewFactory.get().create(context, mainExecutor) { taskView -> k.resume(taskView) } - } -} - -// Alive for lifetime of FSI. -data class FsiChromeViewModel( - val appName: String, - val appIcon: Drawable, - val taskView: TaskView, - val fsi: PendingIntent, - val repo: FsiChromeRepo -) { - companion object { - private const val classTag = "FsiChromeViewModel" - } - - fun onDismiss() { - log("$classTag onDismiss") - repo.dismiss() - } - fun onFullscreen() { - log("$classTag onFullscreen") - repo.onFullscreen() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt deleted file mode 100644 index d9e3f8fbf146..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.android.systemui.statusbar.notification.fsi - -class FsiDebug { - - companion object { - private const val debugTag = "FsiDebug" - private const val debug = true - - fun log(s: Any) { - if (!debug) { - return - } - android.util.Log.d(debugTag, "$s") - } - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiTaskViewConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiTaskViewConfig.kt deleted file mode 100644 index 034ab56d5a65..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiTaskViewConfig.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.android.systemui.statusbar.notification.fsi - -import android.app.ActivityOptions -import android.content.Context -import android.content.Intent -import android.graphics.PixelFormat -import android.graphics.Rect -import android.os.Binder -import android.view.ViewGroup -import android.view.WindowManager - -/** - * Config for adding the FsiChromeView window to WindowManager and starting the FSI activity. - */ -class FsiTaskViewConfig { - - companion object { - - private const val classTag = "FsiTaskViewConfig" - - fun getWmLayoutParams(packageName: String): WindowManager.LayoutParams { - val params: WindowManager.LayoutParams? - params = - WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED or - WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER, - PixelFormat.TRANSLUCENT - ) - params.setTrustedOverlay() - params.fitInsetsTypes = 0 - params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE - params.token = Binder() - params.packageName = packageName - params.layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - params.privateFlags = - params.privateFlags or WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS - return params - } - - fun getFillInIntent(): Intent { - val fillInIntent = Intent() - fillInIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) - fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - // FLAG_ACTIVITY_NEW_TASK is auto-applied because - // we're starting the FSI activity from a non-Activity context - return fillInIntent - } - - fun getLaunchBounds(windowManager: WindowManager): Rect { - // TODO(b/243421660) check this works for non-resizeable activity - return Rect() - } - - fun getActivityOptions(context: Context, windowManager: WindowManager): ActivityOptions { - // Custom options so there is no activity transition animation - val options = - ActivityOptions.makeCustomAnimation(context, 0 /* enterResId */, 0 /* exitResId */) - - options.taskAlwaysOnTop = true - - options.pendingIntentLaunchFlags = - Intent.FLAG_ACTIVITY_NEW_DOCUMENT or - Intent.FLAG_ACTIVITY_MULTIPLE_TASK or - Intent.FLAG_ACTIVITY_NEW_TASK - - options.launchBounds = getLaunchBounds(windowManager) - return options - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java index ae19febadfaa..9001470ad406 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java @@ -35,10 +35,6 @@ public interface NotificationInterruptStateProvider { */ NO_FSI_SHOW_STICKY_HUN(false), /** - * Full screen intents are disabled. - */ - NO_FSI_DISABLED(false), - /** * No full screen intent included, so there is nothing to show. */ NO_FULL_SCREEN_INTENT(false), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 0163dbef2760..9f45b9d67189 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -244,10 +244,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter @Override public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) { - if (mFlags.disableFsi()) { - return FullScreenIntentDecision.NO_FSI_DISABLED; - } - if (entry.getSbn().getNotification().fullScreenIntent == null) { if (entry.isStickyAndNotDemoted()) { return FullScreenIntentDecision.NO_FSI_SHOW_STICKY_HUN; @@ -343,9 +339,6 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter case NO_FSI_SHOW_STICKY_HUN: mLogger.logNoFullscreen(entry, "Permission denied, show sticky HUN"); return; - case NO_FSI_DISABLED: - mLogger.logNoFullscreen(entry, "Disabled"); - return; case NO_FULL_SCREEN_INTENT: return; case NO_FSI_SUPPRESSED_BY_DND: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt index f0b221dd4726..0de3246e01fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt @@ -344,7 +344,7 @@ class ChannelEditorDialogController @Inject constructor( or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) } -class ChannelEditorDialog(context: Context) : Dialog(context) { +class ChannelEditorDialog(context: Context, theme: Int) : Dialog(context, theme) { fun updateDoneButtonText(hasChanges: Boolean) { findViewById<TextView>(R.id.done_button)?.setText( if (hasChanges) @@ -361,7 +361,7 @@ class ChannelEditorDialog(context: Context) : Dialog(context) { } fun build(): ChannelEditorDialog { - return ChannelEditorDialog(context) + return ChannelEditorDialog(context, R.style.Theme_SystemUI_Dialog) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 9f3836105a95..7855cdfeb4c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -374,6 +374,17 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } @Override + public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType, + boolean isStrongBiometric) { + Trace.beginSection("BiometricUnlockController#onBiometricDetected"); + if (mUpdateMonitor.isGoingToSleep()) { + Trace.endSection(); + return; + } + startWakeAndUnlock(MODE_SHOW_BOUNCER); + } + + @Override public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, boolean isStrongBiometric) { Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated"); 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 8dcfec71b68e..9e62817fcc67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -375,8 +375,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void fadeKeyguardAfterLaunchTransition(Runnable beforeFading, Runnable endRunnable, Runnable cancelRunnable); - void animateKeyguardUnoccluding(); - void startLaunchTransitionTimeout(); boolean hideKeyguardImpl(boolean forceStateChange); 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 17fb05547e00..b166446714de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -104,6 +104,8 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.DateTimeView; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; @@ -164,6 +166,7 @@ import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder; import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; +import com.android.systemui.notetask.NoteTaskController; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.OverlayPlugin; @@ -507,6 +510,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final BrightnessSliderController.Factory mBrightnessSliderFactory; private final FeatureFlags mFeatureFlags; + private final boolean mAnimateBack; private final FragmentService mFragmentService; private final ScreenOffAnimationController mScreenOffAnimationController; private final WallpaperController mWallpaperController; @@ -642,7 +646,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private NotificationActivityStarter mNotificationActivityStarter; private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; private final Optional<Bubbles> mBubblesOptional; - private final Bubbles.BubbleExpandListener mBubbleExpandListener; + private final Lazy<NoteTaskController> mNoteTaskControllerLazy; private final Optional<StartingSurface> mStartingSurfaceOptional; private final ActivityIntentHelper mActivityIntentHelper; @@ -653,6 +657,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final InteractionJankMonitor mJankMonitor; + /** Existing callback that handles back gesture invoked for the Shade. */ private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { if (DEBUG) { Log.d(TAG, "mOnBackInvokedCallback() called"); @@ -660,6 +665,33 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { onBackPressed(); }; + private boolean shouldBackBeHandled() { + return (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED + && !isBouncerShowingOverDream()); + } + + /** + * New callback that handles back gesture invoked, cancel, progress + * and provides feedback via Shade animation. + * (enabled via the WM_SHADE_ANIMATE_BACK_GESTURE flag) + */ + private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() { + @Override + public void onBackInvoked() { + onBackPressed(); + } + + @Override + public void onBackProgressed(BackEvent event) { + if (shouldBackBeHandled()) { + if (mNotificationPanelViewController.canPanelBeCollapsed()) { + float fraction = event.getProgress(); + mNotificationPanelViewController.onBackProgressed(fraction); + } + } + } + }; + /** * Public constructor for CentralSurfaces. * @@ -705,6 +737,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { WakefulnessLifecycle wakefulnessLifecycle, SysuiStatusBarStateController statusBarStateController, Optional<Bubbles> bubblesOptional, + Lazy<NoteTaskController> noteTaskControllerLazy, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, AccessibilityFloatingMenuController accessibilityFloatingMenuController, @@ -795,6 +828,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mWakefulnessLifecycle = wakefulnessLifecycle; mStatusBarStateController = statusBarStateController; mBubblesOptional = bubblesOptional; + mNoteTaskControllerLazy = noteTaskControllerLazy; mDeviceProvisionedController = deviceProvisionedController; mNavigationBarController = navigationBarController; mAccessibilityFloatingMenuController = accessibilityFloatingMenuController; @@ -852,9 +886,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged); mShadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); - mBubbleExpandListener = (isExpanding, key) -> - mContext.getMainExecutor().execute(this::updateScrimController); - mActivityIntentHelper = new ActivityIntentHelper(mContext); mActivityLaunchAnimator = activityLaunchAnimator; @@ -882,6 +913,17 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) { mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true); } + // Based on teamfood flag, enable predictive back animation for the Shade. + mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE); + } + + private void initBubbles(Bubbles bubbles) { + final Bubbles.BubbleExpandListener listener = (isExpanding, key) -> + mContext.getMainExecutor().execute(() -> { + updateScrimController(); + mNoteTaskControllerLazy.get().onBubbleExpandChanged(isExpanding, key); + }); + bubbles.setExpandListener(listener); } @Override @@ -889,9 +931,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScreenLifecycle.addObserver(mScreenObserver); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); mUiModeManager = mContext.getSystemService(UiModeManager.class); - if (mBubblesOptional.isPresent()) { - mBubblesOptional.get().setExpandListener(mBubbleExpandListener); - } + mBubblesOptional.ifPresent(this::initBubbles); // Do not restart System UI when the bugreport flag changes. mFeatureFlags.addListener(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, event -> { @@ -2554,7 +2594,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { String action = intent.getAction(); String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { - if (mIsShortcutListSearchEnabled && Utilities.isTablet(mContext)) { + if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) { KeyboardShortcutListSearch.dismiss(); } else { KeyboardShortcuts.dismiss(); @@ -2699,7 +2739,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (viewRootImpl != null) { viewRootImpl.getOnBackInvokedDispatcher() .registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, - mOnBackInvokedCallback); + mAnimateBack ? mOnBackAnimationCallback + : mOnBackInvokedCallback); mIsBackCallbackRegistered = true; if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered"); } @@ -2714,7 +2755,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { ViewRootImpl viewRootImpl = getViewRootImpl(); if (viewRootImpl != null) { viewRootImpl.getOnBackInvokedDispatcher() - .unregisterOnBackInvokedCallback(mOnBackInvokedCallback); + .unregisterOnBackInvokedCallback( + mAnimateBack ? mOnBackAnimationCallback + : mOnBackInvokedCallback); mIsBackCallbackRegistered = false; if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered"); } @@ -2991,16 +3034,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } /** - * Plays the animation when an activity that was occluding Keyguard goes away. - */ - @Override - public void animateKeyguardUnoccluding() { - mNotificationPanelViewController.setExpandedFraction(0f); - mCommandQueueCallbacks.animateExpandNotificationsPanel(); - mScrimController.setUnocclusionAnimationRunning(true); - } - - /** * Starts the timeout when we try to start the affordances on Keyguard. We usually rely that * Keyguard goes away via fadeKeyguardAfterLaunchTransition, however, that might not happen * because the launched app crashed or something else went wrong. @@ -3256,9 +3289,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) { return true; } - if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED - && !isBouncerShowingOverDream()) { + if (shouldBackBeHandled()) { if (mNotificationPanelViewController.canPanelBeCollapsed()) { + // this is the Shade dismiss animation, so make sure QQS closes when it ends. + mNotificationPanelViewController.onBackPressed(); mShadeController.animateCollapseShade(); } return true; @@ -3905,7 +3939,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } protected void toggleKeyboardShortcuts(int deviceId) { - if (mIsShortcutListSearchEnabled && Utilities.isTablet(mContext)) { + if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) { KeyboardShortcutListSearch.toggle(mContext, deviceId); } else { KeyboardShortcuts.toggle(mContext, deviceId); @@ -3913,7 +3947,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } protected void dismissKeyboardShortcuts() { - if (mIsShortcutListSearchEnabled && Utilities.isTablet(mContext)) { + if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) { KeyboardShortcutListSearch.dismiss(); } else { KeyboardShortcuts.dismiss(); 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 80093a3da325..fb8bf523f625 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -138,26 +138,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private boolean mTransitioningToFullShade; /** - * Is there currently an unocclusion animation running. Used to avoid bright flickers - * of the notification scrim. - */ - private boolean mUnOcclusionAnimationRunning; - - /** * The percentage of the bouncer which is hidden. If 1, the bouncer is completely hidden. If * 0, the bouncer is visible. */ @FloatRange(from = 0, to = 1) private float mBouncerHiddenFraction = KeyguardBouncerConstants.EXPANSION_HIDDEN; - /** - * Set whether an unocclusion animation is currently running on the notification panel. Used - * to avoid bright flickers of the notification scrim. - */ - public void setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning) { - mUnOcclusionAnimationRunning = unocclusionAnimationRunning; - } - @IntDef(prefix = {"VISIBILITY_"}, value = { TRANSPARENT, SEMI_TRANSPARENT, @@ -359,9 +345,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } } - /** - * Sets corner radius of scrims. - */ + // TODO(b/270984686) recompute scrim height accurately, based on shade contents. + /** Set corner radius of the bottom edge of the Notification scrim. */ + public void setNotificationBottomRadius(float radius) { + if (mNotificationsScrim == null) { + return; + } + mNotificationsScrim.setBottomEdgeRadius(radius); + } + + /** Sets corner radius of scrims. */ public void setScrimCornerRadius(int radius) { if (mScrimBehind == null || mNotificationsScrim == null) { return; @@ -525,6 +518,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump scheduleUpdate(); } + /** This is used by the predictive back gesture animation to scale the Shade. */ + public void applyBackScaling(float scale) { + mNotificationsScrim.setScaleX(scale); + mNotificationsScrim.setScaleY(scale); + } + public void onTrackingStarted() { mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { @@ -532,10 +531,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } } - public void onExpandingFinished() { - setUnocclusionAnimationRunning(false); - } - @VisibleForTesting protected void onHideWallpaperTimeout() { if (mState != ScrimState.AOD && mState != ScrimState.PULSING) { @@ -875,13 +870,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (mKeyguardOccluded || hideNotificationScrim) { mNotificationsAlpha = 0; } - if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) { - // We're unoccluding the keyguard and don't want to have a bright flash. - mNotificationsAlpha = ScrimState.KEYGUARD.getNotifAlpha(); - mNotificationsTint = ScrimState.KEYGUARD.getNotifTint(); - mBehindAlpha = ScrimState.KEYGUARD.getBehindAlpha(); - mBehindTint = ScrimState.KEYGUARD.getBehindTint(); - } } if (mState != ScrimState.UNLOCKED) { mAnimatingPanelExpansionOnUnlock = false; 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 a127139fcc69..66f5b6508494 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -281,7 +281,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private float mQsExpansion; final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>(); private boolean mIsModernAlternateBouncerEnabled; - private boolean mIsUnoccludeTransitionFlagEnabled; private boolean mIsBackAnimationEnabled; private OnDismissAction mAfterKeyguardGoneAction; @@ -361,7 +360,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER); mAlternateBouncerInteractor = alternateBouncerInteractor; - mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION); mIsBackAnimationEnabled = featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM); } @@ -880,11 +878,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // by a FLAG_DISMISS_KEYGUARD_ACTIVITY. reset(isOccluding /* hideBouncerWhenShowing*/); } - if (!mIsUnoccludeTransitionFlagEnabled) { - if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) { - mCentralSurfaces.animateKeyguardUnoccluding(); - } - } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 3471a4656637..726b2344309f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -561,10 +561,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte mLogger.logFullScreenIntentSuppressedByVR(entry); return; } - if (mFeatureFlags.isEnabled(Flags.FSI_CHROME)) { - // FsiChromeRepo runs its own implementation of launchFullScreenIntent - return; - } // Stop screensaver if the notification has a fullscreen intent. // (like an incoming phone call) mUiBgExecutor.execute(() -> { diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt index 462504e82199..4e27ce6721d7 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt @@ -82,6 +82,8 @@ constructor( fun startListener() { handler.post { if (hasStarted) return@post + logDebug { "Listener has started." } + hasStarted = true isInUsiSession = inputManager.hasInputDevice { @@ -116,6 +118,10 @@ constructor( val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return + logDebug { + "Stylus InputDevice added: $deviceId ${device.name}, " + + "External: ${device.isExternal}" + } if (!device.isExternal) { registerBatteryListener(deviceId) @@ -137,6 +143,7 @@ constructor( val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return + logDebug { "Stylus InputDevice changed: $deviceId ${device.name}" } val currAddress: String? = device.bluetoothAddress val prevAddress: String? = inputDeviceAddressMap[deviceId] @@ -157,6 +164,8 @@ constructor( if (!hasStarted) return if (!inputDeviceAddressMap.contains(deviceId)) return + logDebug { "Stylus InputDevice removed: $deviceId" } + unregisterBatteryListener(deviceId) val btAddress: String? = inputDeviceAddressMap[deviceId] @@ -180,6 +189,11 @@ constructor( val isCharging = String(value) == "true" + logDebug { + "Charging state metadata changed for device $inputDeviceId " + + "${device.address}: $isCharging" + } + executeStylusBatteryCallbacks { cb -> cb.onStylusBluetoothChargingStateChanged(inputDeviceId, device, isCharging) } @@ -194,13 +208,10 @@ constructor( handler.post { if (!hasStarted) return@post - if (DEBUG) { - Log.d( - TAG, - "onBatteryStateChanged for $deviceId. " + - "batteryState present: ${batteryState.isPresent}, " + - "capacity: ${batteryState.capacity}" - ) + logDebug { + "Battery state changed for $deviceId. " + + "batteryState present: ${batteryState.isPresent}, " + + "capacity: ${batteryState.capacity}" } val batteryStateValid = isBatteryStateValid(batteryState) @@ -216,7 +227,7 @@ constructor( } private fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) { - trackAndLogBluetoothSession(deviceId, true) + trackAndLogBluetoothSession(deviceId, btAddress, true) val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return try { bluetoothAdapter.addOnMetadataChangedListener(device, executor, this) @@ -226,7 +237,7 @@ constructor( } private fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) { - trackAndLogBluetoothSession(deviceId, false) + trackAndLogBluetoothSession(deviceId, btAddress, false) val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return try { bluetoothAdapter.removeOnMetadataChangedListener(device, this) @@ -245,6 +256,7 @@ constructor( if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return if (InputSettings.isStylusEverUsed(context)) return + logDebug { "Stylus used for the first time." } InputSettings.setStylusEverUsed(context, true) executeStylusCallbacks { cb -> cb.onStylusFirstUsed() } } @@ -259,12 +271,7 @@ constructor( // TODO(b/268618918) handle cases where an invalid battery callback from a previous stylus // is sent after the actual valid callback if (batteryStateValid && usiSessionId == null) { - if (DEBUG) { - Log.d( - TAG, - "USI battery newly present, entering new USI session. Device ID: $deviceId" - ) - } + logDebug { "USI battery newly present, entering new USI session: $deviceId" } usiSessionId = instanceIdSequence.newInstanceId() uiEventLogger.logWithInstanceId( StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED, @@ -273,9 +280,7 @@ constructor( usiSessionId ) } else if (!batteryStateValid && usiSessionId != null) { - if (DEBUG) { - Log.d(TAG, "USI battery newly absent, exiting USI session Device ID: $deviceId") - } + logDebug { "USI battery newly absent, exiting USI session: $deviceId" } uiEventLogger.logWithInstanceId( StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED, 0, @@ -286,8 +291,17 @@ constructor( } } - private fun trackAndLogBluetoothSession(deviceId: Int, bluetoothConnected: Boolean) { - if (bluetoothConnected) { + private fun trackAndLogBluetoothSession( + deviceId: Int, + btAddress: String, + btConnected: Boolean + ) { + logDebug { + "Bluetooth stylus ${if (btConnected) "connected" else "disconnected"}:" + + " $deviceId $btAddress" + } + + if (btConnected) { inputDeviceBtSessionIdMap[deviceId] = instanceIdSequence.newInstanceId() uiEventLogger.logWithInstanceId( StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED, @@ -383,7 +397,13 @@ constructor( } companion object { - private val TAG = StylusManager::class.simpleName.orEmpty() - private val DEBUG = false + val TAG = StylusManager::class.simpleName.orEmpty() + const val DEBUG = false + } +} + +private inline fun logDebug(message: () -> String) { + if (StylusManager.DEBUG) { + Log.d(StylusManager.TAG, message()) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index dfad15d68375..71449145d668 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -26,7 +26,6 @@ import android.testing.AndroidTestingRunner; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.SysuiTestCase; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ClockAnimations; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; @@ -61,8 +60,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { @Mock DozeParameters mDozeParameters; @Mock - FeatureFlags mFeatureFlags; - @Mock ScreenOffAnimationController mScreenOffAnimationController; @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; @@ -83,7 +80,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor, mConfigurationController, mDozeParameters, - mFeatureFlags, mScreenOffAnimationController, mKeyguardLogger); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 395eb8fefb8d..f5107681635c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -20,6 +20,8 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; +import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN; +import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; @@ -29,7 +31,6 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE; -import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; @@ -81,6 +82,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.SensorProperties; +import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; @@ -632,20 +634,48 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() { - // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks) - // will trigger updateBiometricListeningState(); - clearInvocations(mFingerprintManager); - mKeyguardUpdateMonitor.resetBiometricListeningState(); - - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); - mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); - mTestableLooper.processAllMessages(); + givenDetectFingerprintWithClearingFingerprintManagerInvocations(); verifyFingerprintAuthenticateNeverCalled(); verifyFingerprintDetectCall(); } @Test + public void whenDetectFingerprint_biometricDetectCallback() { + ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor = + ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class); + + givenDetectFingerprintWithClearingFingerprintManagerInvocations(); + verify(mFingerprintManager).detectFingerprint( + any(), fpDetectCallbackCaptor.capture(), any()); + fpDetectCallbackCaptor.getValue().onFingerprintDetected(0, 0, true); + + // THEN verify keyguardUpdateMonitorCallback receives a detect callback + // and NO authenticate callbacks + verify(mTestCallback).onBiometricDetected( + eq(0), eq(BiometricSourceType.FINGERPRINT), eq(true)); + verify(mTestCallback, never()).onBiometricAuthenticated( + anyInt(), any(), anyBoolean()); + } + + @Test + public void whenDetectFace_biometricDetectCallback() { + ArgumentCaptor<FaceManager.FaceDetectionCallback> faceDetectCallbackCaptor = + ArgumentCaptor.forClass(FaceManager.FaceDetectionCallback.class); + + givenDetectFace(); + verify(mFaceManager).detectFace(any(), faceDetectCallbackCaptor.capture(), any()); + faceDetectCallbackCaptor.getValue().onFaceDetected(0, 0, false); + + // THEN verify keyguardUpdateMonitorCallback receives a detect callback + // and NO authenticate callbacks + verify(mTestCallback).onBiometricDetected( + eq(0), eq(BiometricSourceType.FACE), eq(false)); + verify(mTestCallback, never()).onBiometricAuthenticated( + anyInt(), any(), anyBoolean()); + } + + @Test public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() { // GIVEN unlocking with biometric is allowed strongAuthNotRequired(); @@ -673,7 +703,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { strongAuthNotRequired(); // WHEN fingerprint is locked out - fingerprintErrorLockedOut(); + fingerprintErrorTemporaryLockedOut(); // THEN unlocking with face is not allowed Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( @@ -696,7 +726,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { strongAuthNotRequired(); // WHEN fingerprint is locked out - fingerprintErrorLockedOut(); + fingerprintErrorTemporaryLockedOut(); // THEN unlocking with fingerprint is not allowed Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( @@ -720,7 +750,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())); // WHEN fingerprint is locked out - fingerprintErrorLockedOut(); + fingerprintErrorTemporaryLockedOut(); // THEN user is NOT considered as "having trust" and bouncer cannot be skipped Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())); @@ -780,11 +810,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() { - // GIVEN mocked keyguardUpdateMonitorCallback - KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback = - mock(KeyguardUpdateMonitorCallback.class); - mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback); - // GIVEN bypass is enabled, face detection is supported lockscreenBypassIsAllowed(); supportsFaceDetection(); @@ -803,22 +828,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verifyFaceAuthenticateNeverCalled(); // THEN biometric help message sent to callback - verify(keyguardUpdateMonitorCallback).onBiometricHelp( + verify(mTestCallback).onBiometricHelp( eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE)); } @Test public void faceDetect_whenStrongAuthRequiredAndBypass() { - // GIVEN bypass is enabled, face detection is supported and strong auth is required - lockscreenBypassIsAllowed(); - supportsFaceDetection(); - strongAuthRequiredEncrypted(); - keyguardIsVisible(); - // fingerprint is NOT running, UDFPS is NOT supported - - // WHEN the device wakes up - mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); - mTestableLooper.processAllMessages(); + givenDetectFace(); // FACE detect is triggered, not authenticate verifyFaceDetectCall(); @@ -1152,8 +1168,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // Fingerprint should be cancelled on lockout if going to lockout state, else // restarted if it's not assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState) - .isEqualTo(fpLocked - ? BIOMETRIC_STATE_CANCELLING : BIOMETRIC_STATE_CANCELLING_RESTARTING); + .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING); } @Test @@ -1612,7 +1627,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); // Fingerprint is locked out. - fingerprintErrorLockedOut(); + fingerprintErrorTemporaryLockedOut(); assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); } @@ -2465,7 +2480,69 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { eq(false)); } + @Test + public void detectFingerprint_onTemporaryLockoutReset_authenticateFingerprint() { + ArgumentCaptor<FingerprintManager.LockoutResetCallback> fpLockoutResetCallbackCaptor = + ArgumentCaptor.forClass(FingerprintManager.LockoutResetCallback.class); + verify(mFingerprintManager).addLockoutResetCallback(fpLockoutResetCallbackCaptor.capture()); + + // GIVEN device is locked out + fingerprintErrorTemporaryLockedOut(); + + // GIVEN FP detection is running + givenDetectFingerprintWithClearingFingerprintManagerInvocations(); + verifyFingerprintDetectCall(); + verifyFingerprintAuthenticateNeverCalled(); + + // WHEN temporary lockout resets + fpLockoutResetCallbackCaptor.getValue().onLockoutReset(0); + mTestableLooper.processAllMessages(); + + // THEN fingerprint detect state should cancel & then restart (for authenticate call) + assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState) + .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING); + } + + @Test + public void faceAuthenticateOptions_bouncerAuthenticateReason() { + // GIVEN the bouncer is fully visible + bouncerFullyVisible(); + + // WHEN authenticate is called + ArgumentCaptor<FaceAuthenticateOptions> captor = + ArgumentCaptor.forClass(FaceAuthenticateOptions.class); + verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture()); + + // THEN the authenticate reason is attributed to the bouncer + assertThat(captor.getValue().getAuthenticateReason()) + .isEqualTo(AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN); + } + + @Test + public void faceAuthenticateOptions_wakingUpAuthenticateReason_powerButtonWakeReason() { + // GIVEN keyguard is visible + keyguardIsVisible(); + + // WHEN device wakes up from the power button + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + + // THEN face auth is triggered + ArgumentCaptor<FaceAuthenticateOptions> captor = + ArgumentCaptor.forClass(FaceAuthenticateOptions.class); + verify(mFaceManager).authenticate(any(), any(), any(), any(), captor.capture()); + + // THEN the authenticate reason is attributed to the waking + assertThat(captor.getValue().getAuthenticateReason()) + .isEqualTo(AUTHENTICATE_REASON_STARTED_WAKING_UP); + + // THEN the wake reason is attributed to the power button + assertThat(captor.getValue().getWakeReason()) + .isEqualTo(PowerManager.WAKE_REASON_POWER_BUTTON); + } + private void verifyFingerprintAuthenticateNeverCalled() { + verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any()); verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(), anyInt(), anyInt()); } @@ -2484,11 +2561,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void verifyFaceAuthenticateNeverCalled() { + verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), any()); verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt()); } private void verifyFaceAuthenticateCall() { - verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt()); + verify(mFaceManager).authenticate(any(), any(), any(), any(), any()); } private void verifyFaceDetectNeverCalled() { @@ -2568,7 +2646,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.setSwitchingUser(true); } - private void fingerprintErrorLockedOut() { + private void fingerprintErrorTemporaryLockedOut() { mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out"); } @@ -2596,7 +2674,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { any(), mAuthenticationCallbackCaptor.capture(), any(), - anyInt()); + any()); mAuthenticationCallbackCaptor.getValue() .onAuthenticationSucceeded( new FaceManager.AuthenticationResult(null, null, mCurrentUserId, false)); @@ -2707,6 +2785,30 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { receiver.setPendingResult(pendingResult); } + private void givenDetectFingerprintWithClearingFingerprintManagerInvocations() { + // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks) + // will trigger updateBiometricListeningState(); + clearInvocations(mFingerprintManager); + mKeyguardUpdateMonitor.resetBiometricListeningState(); + + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); + mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); + mTestableLooper.processAllMessages(); + } + + private void givenDetectFace() { + // GIVEN bypass is enabled, face detection is supported and strong auth is required + lockscreenBypassIsAllowed(); + supportsFaceDetection(); + strongAuthRequiredEncrypted(); + keyguardIsVisible(); + // fingerprint is NOT running, UDFPS is NOT supported + + // WHEN the device wakes up + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + } + private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) { int subscription = simInited ? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE; diff --git a/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt new file mode 100644 index 000000000000..820329119f8d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt @@ -0,0 +1,94 @@ +/* + * 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.devicepolicy + +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class DevicePolicyManagerExtTest : SysuiTestCase() { + + @Mock lateinit var devicePolicyManager: DevicePolicyManager + @Mock private lateinit var userTracker: UserTracker + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever(userTracker.userId).thenReturn(CURRENT_USER_ID) + } + + // region areKeyguardShortcutsDisabled + @Test + fun areKeyguardShortcutsDisabled_noDisabledKeyguardFeature_shouldReturnFalse() { + whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt())) + .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE) + + assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID)) + .isFalse() + } + + @Test + fun areKeyguardShortcutsDisabled_otherDisabledKeyguardFeatures_shouldReturnFalse() { + whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt())) + .thenReturn(KEYGUARD_DISABLE_SECURE_CAMERA or KEYGUARD_DISABLE_FACE) + + assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID)) + .isFalse() + } + + @Test + fun areKeyguardShortcutsDisabled_disabledShortcutsKeyguardFeature_shouldReturnTrue() { + whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt())) + .thenReturn(KEYGUARD_DISABLE_SHORTCUTS_ALL) + + assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID)) + .isTrue() + } + + @Test + fun areKeyguardShortcutsDisabled_disabledAllKeyguardFeatures_shouldReturnTrue() { + whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt())) + .thenReturn(KEYGUARD_DISABLE_FEATURES_ALL) + + assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID)) + .isTrue() + } + // endregion + + private companion object { + const val CURRENT_USER_ID = 123 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 7c20e3c9baff..c93e677071cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -29,7 +29,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -67,7 +66,6 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -136,7 +134,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock SysuiColorExtractor mColorExtractor; private @Mock AuthController mAuthController; private @Mock ShadeExpansionStateManager mShadeExpansionStateManager; - private @Mock FeatureFlags mFeatureFlags; private @Mock ShadeWindowLogger mShadeWindowLogger; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -545,7 +542,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mScreenOnCoordinator, mInteractionJankMonitor, mDreamOverlayStateController, - mFeatureFlags, () -> mShadeController, () -> mNotificationShadeWindowController, () -> mActivityLaunchAnimator, diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 2212bbda8021..89405c109224 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -141,8 +141,8 @@ public class NavigationBarControllerTest extends SysuiTestCase { @Test public void testCreateNavigationBarsIncludeDefaultTrue() { - // Tablets may be using taskbar and the logic is different - mNavigationBarController.mIsTablet = false; + // Large screens may be using taskbar and the logic is different + mNavigationBarController.mIsLargeScreen = false; doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any()); mNavigationBarController.createNavigationBars(true, null); @@ -290,7 +290,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { @Test public void testConfigurationChange_taskbarNotInitialized() { Configuration configuration = mContext.getResources().getConfiguration(); - when(Utilities.isTablet(any())).thenReturn(true); + when(Utilities.isLargeScreen(any())).thenReturn(true); mNavigationBarController.onConfigChanged(configuration); verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration); } @@ -298,7 +298,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { @Test public void testConfigurationChange_taskbarInitialized() { Configuration configuration = mContext.getResources().getConfiguration(); - when(Utilities.isTablet(any())).thenReturn(true); + when(Utilities.isLargeScreen(any())).thenReturn(true); when(mTaskbarDelegate.isInitialized()).thenReturn(true); mNavigationBarController.onConfigChanged(configuration); verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration); diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 39c4e06ff0bb..b872389ce2fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -16,40 +16,37 @@ package com.android.systemui.notetask import android.app.KeyguardManager +import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.os.UserManager -import android.test.suitebuilder.annotation.SmallTest +import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 -import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE -import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent -import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever +import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles import com.google.common.truth.Truth.assertThat import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations -/** - * Tests for [NoteTaskController]. - * - * Build/Install/Run: - * - atest SystemUITests:NoteTaskControllerTest - */ +/** atest SystemUITests:NoteTaskControllerTest */ @SmallTest @RunWith(AndroidJUnit4::class) internal class NoteTaskControllerTest : SysuiTestCase() { @@ -58,97 +55,204 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Mock lateinit var packageManager: PackageManager @Mock lateinit var resolver: NoteTaskInfoResolver @Mock lateinit var bubbles: Bubbles - @Mock lateinit var optionalBubbles: Optional<Bubbles> @Mock lateinit var keyguardManager: KeyguardManager - @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager> - @Mock lateinit var optionalUserManager: Optional<UserManager> @Mock lateinit var userManager: UserManager - @Mock lateinit var uiEventLogger: UiEventLogger + @Mock lateinit var eventLogger: NoteTaskEventLogger + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var devicePolicyManager: DevicePolicyManager + + private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID) @Before fun setUp() { MockitoAnnotations.initMocks(this) whenever(context.packageManager).thenReturn(packageManager) - whenever(resolver.resolveInfo()).thenReturn(NoteTaskInfo(NOTES_PACKAGE_NAME, NOTES_UID)) - whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) - whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager) - whenever(optionalUserManager.orElse(null)).thenReturn(userManager) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(noteTaskInfo) whenever(userManager.isUserUnlocked).thenReturn(true) + whenever( + devicePolicyManager.getKeyguardDisabledFeatures( + /* admin= */ eq(null), + /* userHandle= */ anyInt() + ) + ) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE) } - private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController { - return NoteTaskController( + private fun createNoteTaskController( + isEnabled: Boolean = true, + bubbles: Bubbles? = this.bubbles, + keyguardManager: KeyguardManager? = this.keyguardManager, + userManager: UserManager? = this.userManager, + ): NoteTaskController = + NoteTaskController( context = context, resolver = resolver, - optionalBubbles = optionalBubbles, - optionalKeyguardManager = optionalKeyguardManager, - optionalUserManager = optionalUserManager, + eventLogger = eventLogger, + optionalBubbles = Optional.ofNullable(bubbles), + optionalUserManager = Optional.ofNullable(userManager), + optionalKeyguardManager = Optional.ofNullable(keyguardManager), isEnabled = isEnabled, - uiEventLogger = uiEventLogger, + devicePolicyManager = devicePolicyManager, + userTracker = userTracker, ) + + // region onBubbleExpandChanged + @Test + fun onBubbleExpandChanged_expanding_logNoteTaskOpened() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false) + + createNoteTaskController() + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = true, + key = Bubble.KEY_APP_BUBBLE, + ) + + verify(eventLogger).logNoteTaskOpened(expectedInfo) + verifyZeroInteractions(context, bubbles, keyguardManager, userManager) } - // region showNoteTask @Test - fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() { - whenever(keyguardManager.isKeyguardLocked).thenReturn(true) + fun onBubbleExpandChanged_collapsing_logNoteTaskClosed() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = false) createNoteTaskController() - .showNoteTask( - isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE, + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = false, + key = Bubble.KEY_APP_BUBBLE, ) - val intentCaptor = argumentCaptor<Intent>() - verify(context).startActivity(capture(intentCaptor)) - intentCaptor.value.let { intent -> - assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) - assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) - assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) - assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() - } - verifyZeroInteractions(bubbles) - verify(uiEventLogger) - .log( - ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE, - NOTES_UID, - NOTES_PACKAGE_NAME + verify(eventLogger).logNoteTaskClosed(expectedInfo) + verifyZeroInteractions(context, bubbles, keyguardManager, userManager) + } + + @Test + fun onBubbleExpandChanged_expandingAndKeyguardLocked_doNothing() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false) + + createNoteTaskController() + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = true, + key = Bubble.KEY_APP_BUBBLE, ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) } @Test - fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesAndLogUiEvent() { - whenever(keyguardManager.isKeyguardLocked).thenReturn(false) + fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_doNothing() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true, isInMultiWindowMode = false) createNoteTaskController() - .showNoteTask( + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = false, + key = Bubble.KEY_APP_BUBBLE, + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) + } + + @Test + fun onBubbleExpandChanged_expandingAndInMultiWindowMode_doNothing() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true) + + createNoteTaskController() + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = true, + key = Bubble.KEY_APP_BUBBLE, + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager) + } + + @Test + fun onBubbleExpandChanged_notExpandingAndInMultiWindowMode_doNothing() { + val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = false, isInMultiWindowMode = true) + + createNoteTaskController() + .apply { infoReference.set(expectedInfo) } + .onBubbleExpandChanged( + isExpanding = false, + key = Bubble.KEY_APP_BUBBLE, + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) + } + + @Test + fun onBubbleExpandChanged_notKeyAppBubble_shouldDoNothing() { + createNoteTaskController() + .onBubbleExpandChanged( + isExpanding = true, + key = "any other key", + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) + } + + @Test + fun onBubbleExpandChanged_flagDisabled_shouldDoNothing() { + createNoteTaskController(isEnabled = false) + .onBubbleExpandChanged( + isExpanding = true, + key = Bubble.KEY_APP_BUBBLE, + ) + + verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger) + } + // endregion + + // region showNoteTask + @Test + fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() { + val expectedInfo = + noteTaskInfo.copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, + isKeyguardLocked = true, + ) + whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) + + createNoteTaskController() + .showNoteTask( + entryPoint = expectedInfo.entryPoint!!, + isInMultiWindowMode = expectedInfo.isInMultiWindowMode, ) - verifyZeroInteractions(context) val intentCaptor = argumentCaptor<Intent>() - verify(bubbles).showOrHideAppBubble(capture(intentCaptor)) + verify(context).startActivity(capture(intentCaptor)) intentCaptor.value.let { intent -> assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() } - verify(uiEventLogger) - .log( - ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, - NOTES_UID, - NOTES_PACKAGE_NAME - ) + verify(eventLogger).logNoteTaskOpened(expectedInfo) + verifyZeroInteractions(bubbles) } @Test - fun showNoteTask_keyguardIsUnlocked_uiEventIsNull_shouldStartBubblesWithoutLoggingUiEvent() { - whenever(keyguardManager.isKeyguardLocked).thenReturn(false) + fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() { + val expectedInfo = + noteTaskInfo.copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isInMultiWindowMode = false, + isKeyguardLocked = false, + ) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) + whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) - createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null) + createNoteTaskController() + .showNoteTask( + entryPoint = expectedInfo.entryPoint!!, + isInMultiWindowMode = expectedInfo.isInMultiWindowMode, + ) verifyZeroInteractions(context) val intentCaptor = argumentCaptor<Intent>() @@ -159,17 +263,24 @@ internal class NoteTaskControllerTest : SysuiTestCase() { assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() } - verifyZeroInteractions(uiEventLogger) + verifyZeroInteractions(eventLogger) } @Test fun showNoteTask_isInMultiWindowMode_shouldStartActivityAndLogUiEvent() { - whenever(keyguardManager.isKeyguardLocked).thenReturn(false) + val expectedInfo = + noteTaskInfo.copy( + entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT, + isInMultiWindowMode = true, + isKeyguardLocked = false, + ) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) + whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) createNoteTaskController() .showNoteTask( - isInMultiWindowMode = true, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, + entryPoint = expectedInfo.entryPoint!!, + isInMultiWindowMode = expectedInfo.isInMultiWindowMode, ) val intentCaptor = argumentCaptor<Intent>() @@ -180,69 +291,65 @@ internal class NoteTaskControllerTest : SysuiTestCase() { assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() } + verify(eventLogger).logNoteTaskOpened(expectedInfo) verifyZeroInteractions(bubbles) - verify(uiEventLogger) - .log(ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, NOTES_UID, NOTES_PACKAGE_NAME) } @Test fun showNoteTask_bubblesIsNull_shouldDoNothing() { - whenever(optionalBubbles.orElse(null)).thenReturn(null) - - createNoteTaskController() + createNoteTaskController(bubbles = null) .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() { - whenever(optionalKeyguardManager.orElse(null)).thenReturn(null) - - createNoteTaskController() + createNoteTaskController(keyguardManager = null) .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test fun showNoteTask_userManagerIsNull_shouldDoNothing() { - whenever(optionalUserManager.orElse(null)).thenReturn(null) - - createNoteTaskController() + createNoteTaskController(userManager = null) .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() { - whenever(resolver.resolveInfo()).thenReturn(null) + whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(null) createNoteTaskController() .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test fun showNoteTask_flagDisabled_shouldDoNothing() { createNoteTaskController(isEnabled = false) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON) + .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isInMultiWindowMode = false, + ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } @Test @@ -251,11 +358,11 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController() .showNoteTask( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isInMultiWindowMode = false, - uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON, ) - verifyZeroInteractions(context, bubbles, uiEventLogger) + verifyZeroInteractions(context, bubbles, eventLogger) } // endregion @@ -291,6 +398,102 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } // endregion + // region keyguard policy + @Test + fun showNoteTask_keyguardLocked_keyguardDisableShortcutsAll_shouldDoNothing() { + whenever(keyguardManager.isKeyguardLocked).thenReturn(true) + whenever( + devicePolicyManager.getKeyguardDisabledFeatures( + /* admin= */ eq(null), + /* userHandle= */ anyInt() + ) + ) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) + + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE + ) + + verifyZeroInteractions(context, bubbles, eventLogger) + } + + @Test + fun showNoteTask_keyguardLocked_keyguardDisableFeaturesAll_shouldDoNothing() { + whenever(keyguardManager.isKeyguardLocked).thenReturn(true) + whenever( + devicePolicyManager.getKeyguardDisabledFeatures( + /* admin= */ eq(null), + /* userHandle= */ anyInt() + ) + ) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) + + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE + ) + + verifyZeroInteractions(context, bubbles, eventLogger) + } + + @Test + fun showNoteTask_keyguardUnlocked_keyguardDisableShortcutsAll_shouldStartBubble() { + whenever(keyguardManager.isKeyguardLocked).thenReturn(false) + whenever( + devicePolicyManager.getKeyguardDisabledFeatures( + /* admin= */ eq(null), + /* userHandle= */ anyInt() + ) + ) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) + + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE + ) + + val intentCaptor = argumentCaptor<Intent>() + verify(bubbles).showOrHideAppBubble(capture(intentCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) + assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) + assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) + assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() + } + } + + @Test + fun showNoteTask_keyguardUnlocked_keyguardDisableFeaturesAll_shouldStartBubble() { + whenever(keyguardManager.isKeyguardLocked).thenReturn(false) + whenever( + devicePolicyManager.getKeyguardDisabledFeatures( + /* admin= */ eq(null), + /* userHandle= */ anyInt() + ) + ) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) + + createNoteTaskController() + .showNoteTask( + isInMultiWindowMode = false, + entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE + ) + + val intentCaptor = argumentCaptor<Intent>() + verify(bubbles).showOrHideAppBubble(capture(intentCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE) + assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) + assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) + assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue() + } + } + // endregion + private companion object { const val NOTES_PACKAGE_NAME = "com.android.note.app" const val NOTES_UID = 123456 diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt new file mode 100644 index 000000000000..a4df346776a0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt @@ -0,0 +1,160 @@ +/* + * 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.notetask + +import android.test.suitebuilder.annotation.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.internal.logging.UiEventLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON +import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +/** atest SystemUITests:MonitoringNoteTaskEventListenerTest */ +@SmallTest +@RunWith(AndroidJUnit4::class) +internal class NoteTaskEventLoggerTest : SysuiTestCase() { + + @Mock lateinit var uiEventLogger: UiEventLogger + + private fun createNoteTaskEventLogger(): NoteTaskEventLogger = + NoteTaskEventLogger(uiEventLogger) + + private fun createNoteTaskInfo(): NoteTaskInfo = + NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + // region logNoteTaskOpened + @Test + fun logNoteTaskOpened_entryPointWidgetPickerShortcut_noteOpenedViaShortcut() { + val info = createNoteTaskInfo().copy(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + val expected = NOTE_OPENED_VIA_SHORTCUT + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun onNoteTaskBubbleExpanded_entryPointQuickAffordance_noteOpenedViaQuickAffordance() { + val info = createNoteTaskInfo().copy(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + val expected = NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun onNoteTaskBubbleExpanded_entryPointTailButtonAndIsKeyguardUnlocked_noteOpenedViaTailButtonUnlocked() { // ktlint-disable max-line-length + val info = + createNoteTaskInfo() + .copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isKeyguardLocked = false, + ) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + val expected = NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun onNoteTaskBubbleExpanded_entryPointTailButtonAndIsKeyguardLocked_noteOpenedViaTailButtonLocked() { // ktlint-disable max-line-length + val info = + createNoteTaskInfo() + .copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isKeyguardLocked = true, + ) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + val expected = NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun onNoteTaskBubbleExpanded_noEntryPoint_noLog() { + val info = createNoteTaskInfo().copy(entryPoint = null) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + verifyNoMoreInteractions(uiEventLogger) + } + // endregion + + // region logNoteTaskClosed + @Test + fun logNoteTaskClosed_entryPointTailButton_noteClosedViaTailButtonUnlocked() { + val info = + createNoteTaskInfo() + .copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isKeyguardLocked = false, + ) + + createNoteTaskEventLogger().logNoteTaskClosed(info) + + val expected = NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun logNoteTaskClosed_entryPointTailButtonAndKeyguardLocked_noteClosedViaTailButtonLocked() { + val info = + createNoteTaskInfo() + .copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isKeyguardLocked = true, + ) + + createNoteTaskEventLogger().logNoteTaskClosed(info) + + val expected = NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED + verify(uiEventLogger).log(expected, info.uid, info.packageName) + } + + @Test + fun logNoteTaskClosed_noEntryPoint_noLog() { + val info = createNoteTaskInfo().copy(entryPoint = null) + + createNoteTaskEventLogger().logNoteTaskOpened(info) + + verifyNoMoreInteractions(uiEventLogger) + } + // endregion + + private companion object { + const val NOTES_PACKAGE_NAME = "com.android.note.app" + const val NOTES_UID = 123456 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt new file mode 100644 index 000000000000..7e975b6732a5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt @@ -0,0 +1,73 @@ +/* + * 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.notetask + +import android.test.suitebuilder.annotation.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +/** atest SystemUITests:NoteTaskInfoTest */ +@SmallTest +@RunWith(AndroidJUnit4::class) +internal class NoteTaskInfoTest : SysuiTestCase() { + + private fun createNoteTaskInfo(): NoteTaskInfo = + NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID) + + @Test + fun launchMode_notInMultiWindowModeAndKeyguardUnlocked_launchModeAppBubble() { + val underTest = + createNoteTaskInfo() + .copy( + isKeyguardLocked = false, + isInMultiWindowMode = false, + ) + + assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble) + } + + @Test + fun launchMode_inMultiWindowMode_launchModeActivity() { + val underTest = + createNoteTaskInfo() + .copy( + isKeyguardLocked = false, + isInMultiWindowMode = true, + ) + + assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity) + } + + @Test + fun launchMode_keyguardLocked_launchModeActivity() { + val underTest = + createNoteTaskInfo() + .copy( + isKeyguardLocked = true, + isInMultiWindowMode = false, + ) + + assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity) + } + + private companion object { + const val NOTES_PACKAGE_NAME = "com.android.note.app" + const val NOTES_UID = 123456 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index 53720ffdff94..a8eab50e0798 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -15,15 +15,11 @@ */ package com.android.systemui.notetask -import android.app.KeyguardManager import android.test.suitebuilder.annotation.SmallTest import android.view.KeyEvent import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase -import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import org.junit.Before @@ -48,27 +44,22 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { @Mock lateinit var commandQueue: CommandQueue @Mock lateinit var bubbles: Bubbles - @Mock lateinit var optionalBubbles: Optional<Bubbles> @Mock lateinit var noteTaskController: NoteTaskController @Before fun setUp() { MockitoAnnotations.initMocks(this) - - whenever(optionalBubbles.isPresent).thenReturn(true) - whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) } private fun createNoteTaskInitializer( isEnabled: Boolean = true, - optionalKeyguardManager: Optional<KeyguardManager> = Optional.empty(), + bubbles: Bubbles? = this.bubbles, ): NoteTaskInitializer { return NoteTaskInitializer( - optionalBubbles = optionalBubbles, - noteTaskController = noteTaskController, + controller = noteTaskController, commandQueue = commandQueue, + optionalBubbles = Optional.ofNullable(bubbles), isEnabled = isEnabled, - optionalKeyguardManager = optionalKeyguardManager, ) } @@ -89,9 +80,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { @Test fun initialize_bubblesNotPresent_shouldDoNothing() { - whenever(optionalBubbles.isPresent).thenReturn(false) - - createNoteTaskInitializer().initialize() + createNoteTaskInitializer(bubbles = null).initialize() verify(commandQueue, never()).addCallback(any()) } @@ -113,37 +102,12 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { // region handleSystemKey @Test - fun handleSystemKey_receiveValidSystemKey_keyguardNotLocked_shouldShowNoteTaskWithUnlocked() { - val keyguardManager = - mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(false) } - createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager)) - .callbacks - .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) - - verify(noteTaskController) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON) - } - - @Test - fun handleSystemKey_receiveValidSystemKey_keyguardLocked_shouldShowNoteTaskWithLocked() { - val keyguardManager = - mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(true) } - createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager)) - .callbacks - .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) - - verify(noteTaskController) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED) - } - - @Test - fun handleSystemKey_receiveValidSystemKey_nullKeyguardManager_shouldShowNoteTaskWithUnlocked() { - createNoteTaskInitializer(optionalKeyguardManager = Optional.empty()) + fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() { + createNoteTaskInitializer() .callbacks .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT) - verify(noteTaskController) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON) + verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt index cdc683f8f8f8..e57d0d943aab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt @@ -27,7 +27,7 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState import com.android.systemui.notetask.NoteTaskController -import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent +import com.android.systemui.notetask.NoteTaskEntryPoint import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -95,7 +95,6 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { underTest.onTriggered(expandable = null) - verify(noteTaskController) - .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE) + verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java new file mode 100644 index 000000000000..52b0b6abda1d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -0,0 +1,753 @@ +/* + * 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.shade; + +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; + +import static com.android.keyguard.KeyguardClockSwitch.LARGE; + +import static com.google.common.truth.Truth.assertThat; + +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + +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.atLeast; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.IdRes; +import android.content.ContentResolver; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; +import android.os.UserManager; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.ViewPropertyAnimator; +import android.view.ViewStub; +import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityManager; + +import androidx.constraintlayout.widget.ConstraintSet; + +import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.testing.UiEventLoggerFake; +import com.android.internal.util.LatencyTracker; +import com.android.keyguard.KeyguardClockSwitch; +import com.android.keyguard.KeyguardClockSwitchController; +import com.android.keyguard.KeyguardStatusView; +import com.android.keyguard.KeyguardStatusViewController; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.LockIconViewController; +import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; +import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; +import com.android.keyguard.dagger.KeyguardStatusViewComponent; +import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.classifier.FalsingCollectorFake; +import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.common.ui.view.LongPressHandlingView; +import com.android.systemui.doze.DozeLog; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.fragments.FragmentService; +import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository; +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; +import com.android.systemui.media.controls.pipeline.MediaDataManager; +import com.android.systemui.media.controls.ui.KeyguardMediaController; +import com.android.systemui.media.controls.ui.MediaHierarchyManager; +import com.android.systemui.model.SysUiState; +import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.qs.QSFragment; +import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.shade.transition.ShadeTransitionController; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.NotificationShelfController; +import com.android.systemui.statusbar.PulseExpansionHandler; +import com.android.systemui.statusbar.QsFrameTranslateController; +import com.android.systemui.statusbar.StatusBarStateControllerImpl; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.notification.ConversationNotificationManager; +import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; +import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.stack.AmbientState; +import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; +import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; +import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.KeyguardStatusBarView; +import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger; +import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; +import com.android.systemui.statusbar.phone.TapAgainViewController; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; +import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; +import com.android.systemui.unfold.SysUIUnfoldComponent; +import com.android.systemui.util.time.FakeSystemClock; +import com.android.systemui.util.time.SystemClock; +import com.android.wm.shell.animation.FlingAnimationUtils; + +import org.junit.After; +import org.junit.Before; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; + +import java.util.List; +import java.util.Optional; + +import dagger.Lazy; +import kotlinx.coroutines.CoroutineDispatcher; + +public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { + + protected static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400; + protected static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50; + protected static final int PANEL_WIDTH = 500; // Random value just for the test. + + @Mock protected CentralSurfaces mCentralSurfaces; + @Mock protected NotificationStackScrollLayout mNotificationStackScrollLayout; + @Mock protected KeyguardBottomAreaView mKeyguardBottomArea; + @Mock protected KeyguardBottomAreaViewController mKeyguardBottomAreaViewController; + @Mock protected KeyguardBottomAreaView mQsFrame; + @Mock protected HeadsUpManagerPhone mHeadsUpManager; + @Mock protected NotificationShelfController mNotificationShelfController; + @Mock protected NotificationGutsManager mGutsManager; + @Mock protected KeyguardStatusBarView mKeyguardStatusBar; + @Mock protected KeyguardUserSwitcherView mUserSwitcherView; + @Mock protected ViewStub mUserSwitcherStubView; + @Mock protected HeadsUpTouchHelper.Callback mHeadsUpCallback; + @Mock protected KeyguardUpdateMonitor mUpdateMonitor; + @Mock protected KeyguardBypassController mKeyguardBypassController; + @Mock protected DozeParameters mDozeParameters; + @Mock protected ScreenOffAnimationController mScreenOffAnimationController; + @Mock protected NotificationPanelView mView; + @Mock protected LayoutInflater mLayoutInflater; + @Mock protected FeatureFlags mFeatureFlags; + @Mock protected DynamicPrivacyController mDynamicPrivacyController; + @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; + @Mock protected KeyguardStateController mKeyguardStateController; + @Mock protected DozeLog mDozeLog; + @Mock protected ShadeLogger mShadeLog; + @Mock protected ShadeHeightLogger mShadeHeightLogger; + @Mock protected CommandQueue mCommandQueue; + @Mock protected VibratorHelper mVibratorHelper; + @Mock protected LatencyTracker mLatencyTracker; + @Mock protected PowerManager mPowerManager; + @Mock protected AccessibilityManager mAccessibilityManager; + @Mock protected MetricsLogger mMetricsLogger; + @Mock protected Resources mResources; + @Mock protected Configuration mConfiguration; + @Mock protected KeyguardClockSwitch mKeyguardClockSwitch; + @Mock protected MediaHierarchyManager mMediaHierarchyManager; + @Mock protected ConversationNotificationManager mConversationNotificationManager; + @Mock protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + @Mock protected KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; + @Mock protected KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory; + @Mock protected KeyguardQsUserSwitchComponent mKeyguardQsUserSwitchComponent; + @Mock protected KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; + @Mock protected KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; + @Mock protected KeyguardUserSwitcherComponent mKeyguardUserSwitcherComponent; + @Mock protected KeyguardUserSwitcherController mKeyguardUserSwitcherController; + @Mock protected KeyguardStatusViewComponent mKeyguardStatusViewComponent; + @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; + @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; + @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; + @Mock protected KeyguardStatusViewController mKeyguardStatusViewController; + @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController; + @Mock protected NotificationStackScrollLayoutController + mNotificationStackScrollLayoutController; + @Mock protected NotificationShadeDepthController mNotificationShadeDepthController; + @Mock protected LockscreenShadeTransitionController mLockscreenShadeTransitionController; + @Mock protected AuthController mAuthController; + @Mock protected ScrimController mScrimController; + @Mock protected MediaDataManager mMediaDataManager; + @Mock protected AmbientState mAmbientState; + @Mock protected UserManager mUserManager; + @Mock protected UiEventLogger mUiEventLogger; + @Mock protected LockIconViewController mLockIconViewController; + @Mock protected KeyguardMediaController mKeyguardMediaController; + @Mock protected NavigationModeController mNavigationModeController; + @Mock protected NavigationBarController mNavigationBarController; + @Mock protected QuickSettingsController mQsController; + @Mock protected ShadeHeaderController mShadeHeaderController; + @Mock protected ContentResolver mContentResolver; + @Mock protected TapAgainViewController mTapAgainViewController; + @Mock protected KeyguardIndicationController mKeyguardIndicationController; + @Mock protected FragmentService mFragmentService; + @Mock protected FragmentHostManager mFragmentHostManager; + @Mock protected NotificationRemoteInputManager mNotificationRemoteInputManager; + @Mock protected RecordingController mRecordingController; + @Mock protected LockscreenGestureLogger mLockscreenGestureLogger; + @Mock protected DumpManager mDumpManager; + @Mock protected InteractionJankMonitor mInteractionJankMonitor; + @Mock protected NotificationsQSContainerController mNotificationsQSContainerController; + @Mock protected QsFrameTranslateController mQsFrameTranslateController; + @Mock protected StatusBarWindowStateController mStatusBarWindowStateController; + @Mock protected KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; + @Mock protected NotificationShadeWindowController mNotificationShadeWindowController; + @Mock protected SysUiState mSysUiState; + @Mock protected NotificationListContainer mNotificationListContainer; + @Mock protected NotificationStackSizeCalculator mNotificationStackSizeCalculator; + @Mock protected UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + @Mock protected ShadeTransitionController mShadeTransitionController; + @Mock protected QS mQs; + @Mock protected QSFragment mQSFragment; + @Mock protected ViewGroup mQsHeader; + @Mock protected ViewParent mViewParent; + @Mock protected ViewTreeObserver mViewTreeObserver; + @Mock protected KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; + @Mock protected DreamingToLockscreenTransitionViewModel + mDreamingToLockscreenTransitionViewModel; + @Mock protected OccludedToLockscreenTransitionViewModel + mOccludedToLockscreenTransitionViewModel; + @Mock protected LockscreenToDreamingTransitionViewModel + mLockscreenToDreamingTransitionViewModel; + @Mock protected LockscreenToOccludedTransitionViewModel + mLockscreenToOccludedTransitionViewModel; + @Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel; + + @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; + @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel; + @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor; + @Mock protected MotionEvent mDownMotionEvent; + @Mock protected CoroutineDispatcher mMainDispatcher; + @Captor + protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> + mEmptySpaceClickListenerCaptor; + + protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; + protected KeyguardInteractor mKeyguardInteractor; + protected NotificationPanelViewController.TouchHandler mTouchHandler; + protected ConfigurationController mConfigurationController; + protected SysuiStatusBarStateController mStatusBarStateController; + protected NotificationPanelViewController mNotificationPanelViewController; + protected View.AccessibilityDelegate mAccessibilityDelegate; + protected NotificationsQuickSettingsContainer mNotificationContainerParent; + protected List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners; + protected Handler mMainHandler; + protected View.OnLayoutChangeListener mLayoutChangeListener; + + protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake(); + protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); + protected final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + protected final ShadeExpansionStateManager mShadeExpansionStateManager = + new ShadeExpansionStateManager(); + + protected QuickSettingsController mQuickSettingsController; + @Mock protected Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy; + + protected FragmentHostManager.FragmentListener mFragmentListener; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mMainDispatcher = getMainDispatcher(); + mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor( + new FakeKeyguardRepository()); + mKeyguardInteractor = new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue, + mFeatureFlags, new FakeKeyguardBouncerRepository()); + SystemClock systemClock = new FakeSystemClock(); + mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, + mInteractionJankMonitor, mShadeExpansionStateManager); + + KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); + keyguardStatusView.setId(R.id.keyguard_status_view); + + when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false); + when(mHeadsUpCallback.getContext()).thenReturn(mContext); + when(mView.getResources()).thenReturn(mResources); + when(mView.getWidth()).thenReturn(PANEL_WIDTH); + when(mResources.getConfiguration()).thenReturn(mConfiguration); + mConfiguration.orientation = ORIENTATION_PORTRAIT; + when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + mDisplayMetrics.density = 100; + when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true); + when(mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade)) + .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE); + when(mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)) + .thenReturn(10); + when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance)) + .thenReturn(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); + when(mView.getContext()).thenReturn(getContext()); + when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar); + when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView); + when(mView.findViewById(R.id.keyguard_user_switcher_stub)).thenReturn( + mUserSwitcherStubView); + when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch); + when(mView.findViewById(R.id.notification_stack_scroller)) + .thenReturn(mNotificationStackScrollLayout); + when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000); + when(mNotificationStackScrollLayoutController.getHeadsUpCallback()) + .thenReturn(mHeadsUpCallback); + when(mKeyguardBottomAreaViewController.getView()).thenReturn(mKeyguardBottomArea); + when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea); + when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class)); + when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame); + when(mView.findViewById(R.id.keyguard_status_view)) + .thenReturn(mock(KeyguardStatusView.class)); + mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null); + mNotificationContainerParent.addView(keyguardStatusView); + mNotificationContainerParent.onFinishInflate(); + when(mView.findViewById(R.id.notification_container_parent)) + .thenReturn(mNotificationContainerParent); + when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager); + FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder( + mDisplayMetrics); + when(mKeyguardQsUserSwitchComponentFactory.build(any())) + .thenReturn(mKeyguardQsUserSwitchComponent); + when(mKeyguardQsUserSwitchComponent.getKeyguardQsUserSwitchController()) + .thenReturn(mKeyguardQsUserSwitchController); + when(mKeyguardUserSwitcherComponentFactory.build(any())) + .thenReturn(mKeyguardUserSwitcherComponent); + when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController()) + .thenReturn(mKeyguardUserSwitcherController); + when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true); + when(mQs.getView()).thenReturn(mView); + when(mQSFragment.getView()).thenReturn(mView); + doAnswer(invocation -> { + mFragmentListener = invocation.getArgument(1); + return null; + }).when(mFragmentHostManager).addTagListener(eq(QS.TAG), any()); + doAnswer((Answer<Void>) invocation -> { + mTouchHandler = invocation.getArgument(0); + return null; + }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class)); + + // Dreaming->Lockscreen + when(mKeyguardTransitionInteractor.getDreamingToLockscreenTransition()) + .thenReturn(emptyFlow()); + when(mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha()) + .thenReturn(emptyFlow()); + when(mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(anyInt())) + .thenReturn(emptyFlow()); + + // Occluded->Lockscreen + when(mKeyguardTransitionInteractor.getOccludedToLockscreenTransition()) + .thenReturn(emptyFlow()); + when(mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha()) + .thenReturn(emptyFlow()); + when(mOccludedToLockscreenTransitionViewModel.lockscreenTranslationY(anyInt())) + .thenReturn(emptyFlow()); + + // Lockscreen->Dreaming + when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition()) + .thenReturn(emptyFlow()); + when(mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha()) + .thenReturn(emptyFlow()); + when(mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(anyInt())) + .thenReturn(emptyFlow()); + + // Gone->Dreaming + when(mKeyguardTransitionInteractor.getGoneToDreamingTransition()) + .thenReturn(emptyFlow()); + when(mGoneToDreamingTransitionViewModel.getLockscreenAlpha()) + .thenReturn(emptyFlow()); + when(mGoneToDreamingTransitionViewModel.lockscreenTranslationY(anyInt())) + .thenReturn(emptyFlow()); + + // Lockscreen->Occluded + when(mKeyguardTransitionInteractor.getLockscreenToOccludedTransition()) + .thenReturn(emptyFlow()); + when(mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha()) + .thenReturn(emptyFlow()); + when(mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(anyInt())) + .thenReturn(emptyFlow()); + + NotificationWakeUpCoordinator coordinator = + new NotificationWakeUpCoordinator( + mDumpManager, + mock(HeadsUpManagerPhone.class), + new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, + mInteractionJankMonitor, mShadeExpansionStateManager), + mKeyguardBypassController, + mDozeParameters, + mScreenOffAnimationController, + mock(NotificationWakeUpCoordinatorLogger.class)); + mConfigurationController = new ConfigurationControllerImpl(mContext); + PulseExpansionHandler expansionHandler = new PulseExpansionHandler( + mContext, + coordinator, + mKeyguardBypassController, mHeadsUpManager, + mock(NotificationRoundnessManager.class), + mConfigurationController, + mStatusBarStateController, + mFalsingManager, + mShadeExpansionStateManager, + mLockscreenShadeTransitionController, + new FalsingCollectorFake(), + mDumpManager); + when(mKeyguardStatusViewComponentFactory.build(any())) + .thenReturn(mKeyguardStatusViewComponent); + when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController()) + .thenReturn(mKeyguardClockSwitchController); + when(mKeyguardStatusViewComponent.getKeyguardStatusViewController()) + .thenReturn(mKeyguardStatusViewController); + when(mKeyguardStatusBarViewComponentFactory.build(any(), any())) + .thenReturn(mKeyguardStatusBarViewComponent); + when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController()) + .thenReturn(mKeyguardStatusBarViewController); + when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean())) + .thenReturn(keyguardStatusView); + when(mLayoutInflater.inflate(eq(R.layout.keyguard_user_switcher), any(), anyBoolean())) + .thenReturn(mUserSwitcherView); + when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean())) + .thenReturn(mKeyguardBottomArea); + when(mNotificationRemoteInputManager.isRemoteInputActive()) + .thenReturn(false); + when(mInteractionJankMonitor.begin(any(), anyInt())) + .thenReturn(true); + when(mInteractionJankMonitor.end(anyInt())) + .thenReturn(true); + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); + doAnswer(invocation -> { + mLayoutChangeListener = invocation.getArgument(0); + return null; + }).when(mView).addOnLayoutChangeListener(any()); + + when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver); + when(mView.getParent()).thenReturn(mViewParent); + when(mQs.getHeader()).thenReturn(mQsHeader); + when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN); + when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + + mMainHandler = new Handler(Looper.getMainLooper()); + + when(mView.requireViewById(R.id.keyguard_long_press)) + .thenReturn(mock(LongPressHandlingView.class)); + + mNotificationPanelViewController = new NotificationPanelViewController( + mView, + mMainHandler, + mLayoutInflater, + mFeatureFlags, + coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController, + mFalsingManager, new FalsingCollectorFake(), + mKeyguardStateController, + mStatusBarStateController, + mStatusBarWindowStateController, + mNotificationShadeWindowController, + mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper, + mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor, + mMetricsLogger, + mShadeLog, + mShadeHeightLogger, + mConfigurationController, + () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager, + mConversationNotificationManager, mMediaHierarchyManager, + mStatusBarKeyguardViewManager, + mGutsManager, + mNotificationsQSContainerController, + mNotificationStackScrollLayoutController, + mKeyguardStatusViewComponentFactory, + mKeyguardQsUserSwitchComponentFactory, + mKeyguardUserSwitcherComponentFactory, + mKeyguardStatusBarViewComponentFactory, + mLockscreenShadeTransitionController, + mAuthController, + mScrimController, + mUserManager, + mMediaDataManager, + mNotificationShadeDepthController, + mAmbientState, + mLockIconViewController, + mKeyguardMediaController, + mTapAgainViewController, + mNavigationModeController, + mNavigationBarController, + mQsController, + mFragmentService, + mContentResolver, + mRecordingController, + mShadeHeaderController, + mScreenOffAnimationController, + mLockscreenGestureLogger, + mShadeExpansionStateManager, + mNotificationRemoteInputManager, + mSysUIUnfoldComponent, + mSysUiState, + () -> mKeyguardBottomAreaViewController, + mKeyguardUnlockAnimationController, + mKeyguardIndicationController, + mNotificationListContainer, + mNotificationStackSizeCalculator, + mUnlockedScreenOffAnimationController, + mShadeTransitionController, + systemClock, + mKeyguardBottomAreaViewModel, + mKeyguardBottomAreaInteractor, + mAlternateBouncerInteractor, + mDreamingToLockscreenTransitionViewModel, + mOccludedToLockscreenTransitionViewModel, + mLockscreenToDreamingTransitionViewModel, + mGoneToDreamingTransitionViewModel, + mLockscreenToOccludedTransitionViewModel, + mMainDispatcher, + mKeyguardTransitionInteractor, + mDumpManager, + mKeyuardLongPressViewModel, + mKeyguardInteractor); + mNotificationPanelViewController.initDependencies( + mCentralSurfaces, + null, + () -> {}, + mNotificationShelfController); + mNotificationPanelViewController.setTrackingStartedListener(() -> {}); + mNotificationPanelViewController.setOpenCloseListener( + new NotificationPanelViewController.OpenCloseListener() { + @Override + public void onClosingFinished() {} + + @Override + public void onOpenStarted() {} + }); + mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); + ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor = + ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); + verify(mView, atLeast(1)).addOnAttachStateChangeListener( + onAttachStateChangeListenerArgumentCaptor.capture()); + mOnAttachStateChangeListeners = onAttachStateChangeListenerArgumentCaptor.getAllValues(); + + ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor = + ArgumentCaptor.forClass(View.AccessibilityDelegate.class); + verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture()); + mAccessibilityDelegate = accessibilityDelegateArgumentCaptor.getValue(); + mNotificationPanelViewController.getStatusBarStateController() + .addCallback(mNotificationPanelViewController.getStatusBarStateListener()); + mNotificationPanelViewController + .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); + verify(mNotificationStackScrollLayoutController) + .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture()); + verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); + reset(mKeyguardStatusViewController); + + when(mNotificationPanelViewControllerLazy.get()) + .thenReturn(mNotificationPanelViewController); + mQuickSettingsController = new QuickSettingsController( + mNotificationPanelViewControllerLazy, + mView, + mQsFrameTranslateController, + mShadeTransitionController, + expansionHandler, + mNotificationRemoteInputManager, + mShadeExpansionStateManager, + mStatusBarKeyguardViewManager, + mNotificationStackScrollLayoutController, + mLockscreenShadeTransitionController, + mNotificationShadeDepthController, + mShadeHeaderController, + mStatusBarTouchableRegionManager, + mKeyguardStateController, + mKeyguardBypassController, + mUpdateMonitor, + mScrimController, + mMediaDataManager, + mMediaHierarchyManager, + mAmbientState, + mRecordingController, + mFalsingManager, + new FalsingCollectorFake(), + mAccessibilityManager, + mLockscreenGestureLogger, + mMetricsLogger, + mFeatureFlags, + mInteractionJankMonitor, + mShadeLog + ); + } + + @After + public void tearDown() { + mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel(); + mNotificationPanelViewController.cancelHeightAnimator(); + mMainHandler.removeCallbacksAndMessages(null); + } + + protected void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding, + int ambientPadding) { + + when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0); + when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(stackBottom); + when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(stackBottom); + when(mLockIconViewController.getTop()).thenReturn((float) (stackBottom - lockIconPadding)); + + when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding)) + .thenReturn(indicationPadding); + mNotificationPanelViewController.loadDimens(); + + mNotificationPanelViewController.setAmbientIndicationTop( + /* ambientIndicationTop= */ stackBottom - ambientPadding, + /* ambientTextVisible= */ true); + } + + protected void triggerPositionClockAndNotifications() { + mNotificationPanelViewController.onQsSetExpansionHeightCalled(false); + } + + protected FalsingManager.FalsingTapListener getFalsingTapListener() { + for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { + listener.onViewAttachedToWindow(mView); + } + assertThat(mFalsingManager.getTapListeners().size()).isEqualTo(1); + return mFalsingManager.getTapListeners().get(0); + } + + protected void givenViewAttached() { + for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { + listener.onViewAttachedToWindow(mView); + } + } + + protected ConstraintSet.Layout getConstraintSetLayout(@IdRes int id) { + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(mNotificationContainerParent); + return constraintSet.getConstraint(id).layout; + } + + protected void enableSplitShade(boolean enabled) { + when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled); + mNotificationPanelViewController.updateResources(); + } + + protected void updateMultiUserSetting(boolean enabled) { + when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false); + when(mUserManager.isUserSwitcherEnabled(false)).thenReturn(enabled); + final ArgumentCaptor<ContentObserver> observerCaptor = + ArgumentCaptor.forClass(ContentObserver.class); + verify(mContentResolver) + .registerContentObserver(any(), anyBoolean(), observerCaptor.capture()); + observerCaptor.getValue().onChange(/* selfChange */ false); + } + + protected void updateSmallestScreenWidth(int smallestScreenWidthDp) { + Configuration configuration = new Configuration(); + configuration.smallestScreenWidthDp = smallestScreenWidthDp; + mConfigurationController.onConfigurationChanged(configuration); + } + + protected void onTouchEvent(MotionEvent ev) { + mTouchHandler.onTouch(mView, ev); + } + + protected void setDozing(boolean dozing, boolean dozingAlwaysOn) { + when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn); + mNotificationPanelViewController.setDozing( + /* dozing= */ dozing, + /* animate= */ false + ); + } + + protected void assertKeyguardStatusViewCentered() { + mNotificationPanelViewController.updateResources(); + assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isAnyOf( + ConstraintSet.PARENT_ID, ConstraintSet.UNSET); + } + + protected void assertKeyguardStatusViewNotCentered() { + mNotificationPanelViewController.updateResources(); + assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isEqualTo( + R.id.qs_edge_guideline); + } + + protected void setIsFullWidth(boolean fullWidth) { + float nsslWidth = fullWidth ? PANEL_WIDTH : PANEL_WIDTH / 2f; + when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(nsslWidth); + triggerLayoutChange(); + } + + protected void triggerLayoutChange() { + mLayoutChangeListener.onLayoutChange( + mView, + /* left= */ 0, + /* top= */ 0, + /* right= */ 0, + /* bottom= */ 0, + /* oldLeft= */ 0, + /* oldTop= */ 0, + /* oldRight= */ 0, + /* oldBottom= */ 0 + ); + } + + protected CoroutineDispatcher getMainDispatcher() { + return mMainDispatcher; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 8f6653f1d601..d86ff671a222 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.shade; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; - import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; @@ -31,592 +29,72 @@ import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; 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.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.annotation.IdRes; -import android.content.ContentResolver; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.Looper; -import android.os.PowerManager; -import android.os.UserManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.ViewPropertyAnimator; -import android.view.ViewStub; -import android.view.ViewTreeObserver; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import androidx.constraintlayout.widget.ConstraintSet; import androidx.test.filters.SmallTest; -import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.testing.UiEventLoggerFake; -import com.android.internal.util.CollectionUtils; -import com.android.internal.util.LatencyTracker; import com.android.keyguard.FaceAuthApiRequestReason; -import com.android.keyguard.KeyguardClockSwitch; -import com.android.keyguard.KeyguardClockSwitchController; -import com.android.keyguard.KeyguardStatusView; -import com.android.keyguard.KeyguardStatusViewController; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.LockIconViewController; -import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; -import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; -import com.android.keyguard.dagger.KeyguardStatusViewComponent; -import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.biometrics.AuthController; -import com.android.systemui.classifier.FalsingCollectorFake; -import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.common.ui.view.LongPressHandlingView; -import com.android.systemui.doze.DozeLog; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.fragments.FragmentService; -import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; -import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; -import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; -import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; -import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; -import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; -import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; -import com.android.systemui.media.controls.pipeline.MediaDataManager; -import com.android.systemui.media.controls.ui.KeyguardMediaController; -import com.android.systemui.media.controls.ui.MediaHierarchyManager; -import com.android.systemui.model.SysUiState; -import com.android.systemui.navigationbar.NavigationBarController; -import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.QSFragment; -import com.android.systemui.screenrecord.RecordingController; -import com.android.systemui.shade.transition.ShadeTransitionController; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.KeyguardIndicationController; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.NotificationShelfController; -import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.QsFrameTranslateController; -import com.android.systemui.statusbar.StatusBarStateControllerImpl; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.notification.ConversationNotificationManager; -import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; -import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.AmbientState; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; -import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; -import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; -import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; -import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; -import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; -import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.KeyguardStatusBarView; -import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; -import com.android.systemui.statusbar.phone.LockscreenGestureLogger; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; -import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.phone.TapAgainViewController; -import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; -import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; -import com.android.systemui.statusbar.window.StatusBarWindowStateController; -import com.android.systemui.unfold.SysUIUnfoldComponent; -import com.android.systemui.util.time.FakeSystemClock; -import com.android.systemui.util.time.SystemClock; -import com.android.wm.shell.animation.FlingAnimationUtils; - -import org.junit.After; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; import java.util.List; -import java.util.Optional; - -import dagger.Lazy; -import kotlinx.coroutines.CoroutineDispatcher; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class NotificationPanelViewControllerTest extends SysuiTestCase { - - private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400; - private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50; - private static final int PANEL_WIDTH = 500; // Random value just for the test. - - @Mock private CentralSurfaces mCentralSurfaces; - @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; - @Mock private KeyguardBottomAreaView mKeyguardBottomArea; - @Mock private KeyguardBottomAreaViewController mKeyguardBottomAreaViewController; - @Mock private KeyguardBottomAreaView mQsFrame; - @Mock private HeadsUpManagerPhone mHeadsUpManager; - @Mock private NotificationShelfController mNotificationShelfController; - @Mock private NotificationGutsManager mGutsManager; - @Mock private KeyguardStatusBarView mKeyguardStatusBar; - @Mock private KeyguardUserSwitcherView mUserSwitcherView; - @Mock private ViewStub mUserSwitcherStubView; - @Mock private HeadsUpTouchHelper.Callback mHeadsUpCallback; - @Mock private KeyguardUpdateMonitor mUpdateMonitor; - @Mock private KeyguardBypassController mKeyguardBypassController; - @Mock private DozeParameters mDozeParameters; - @Mock private ScreenOffAnimationController mScreenOffAnimationController; - @Mock private NotificationPanelView mView; - @Mock private LayoutInflater mLayoutInflater; - @Mock private FeatureFlags mFeatureFlags; - @Mock private DynamicPrivacyController mDynamicPrivacyController; - @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; - @Mock private KeyguardStateController mKeyguardStateController; - @Mock private DozeLog mDozeLog; - @Mock private ShadeLogger mShadeLog; - @Mock private ShadeHeightLogger mShadeHeightLogger; - @Mock private CommandQueue mCommandQueue; - @Mock private VibratorHelper mVibratorHelper; - @Mock private LatencyTracker mLatencyTracker; - @Mock private PowerManager mPowerManager; - @Mock private AccessibilityManager mAccessibilityManager; - @Mock private MetricsLogger mMetricsLogger; - @Mock private Resources mResources; - @Mock private Configuration mConfiguration; - @Mock private KeyguardClockSwitch mKeyguardClockSwitch; - @Mock private MediaHierarchyManager mMediaHierarchyManager; - @Mock private ConversationNotificationManager mConversationNotificationManager; - @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; - @Mock private KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory; - @Mock private KeyguardQsUserSwitchComponent mKeyguardQsUserSwitchComponent; - @Mock private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; - @Mock private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; - @Mock private KeyguardUserSwitcherComponent mKeyguardUserSwitcherComponent; - @Mock private KeyguardUserSwitcherController mKeyguardUserSwitcherController; - @Mock private KeyguardStatusViewComponent mKeyguardStatusViewComponent; - @Mock private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; - @Mock private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; - @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController; - @Mock private KeyguardStatusViewController mKeyguardStatusViewController; - @Mock private KeyguardStatusBarViewController mKeyguardStatusBarViewController; - @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; - @Mock private NotificationShadeDepthController mNotificationShadeDepthController; - @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; - @Mock private AuthController mAuthController; - @Mock private ScrimController mScrimController; - @Mock private MediaDataManager mMediaDataManager; - @Mock private AmbientState mAmbientState; - @Mock private UserManager mUserManager; - @Mock private UiEventLogger mUiEventLogger; - @Mock private LockIconViewController mLockIconViewController; - @Mock private KeyguardMediaController mKeyguardMediaController; - @Mock private NavigationModeController mNavigationModeController; - @Mock private NavigationBarController mNavigationBarController; - @Mock private QuickSettingsController mQsController; - @Mock private ShadeHeaderController mShadeHeaderController; - @Mock private ContentResolver mContentResolver; - @Mock private TapAgainViewController mTapAgainViewController; - @Mock private KeyguardIndicationController mKeyguardIndicationController; - @Mock private FragmentService mFragmentService; - @Mock private FragmentHostManager mFragmentHostManager; - @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager; - @Mock private RecordingController mRecordingController; - @Mock private LockscreenGestureLogger mLockscreenGestureLogger; - @Mock private DumpManager mDumpManager; - @Mock private InteractionJankMonitor mInteractionJankMonitor; - @Mock private NotificationsQSContainerController mNotificationsQSContainerController; - @Mock private QsFrameTranslateController mQsFrameTranslateController; - @Mock private StatusBarWindowStateController mStatusBarWindowStateController; - @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; - @Mock private NotificationShadeWindowController mNotificationShadeWindowController; - @Mock private SysUiState mSysUiState; - @Mock private NotificationListContainer mNotificationListContainer; - @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; - @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; - @Mock private ShadeTransitionController mShadeTransitionController; - @Mock private QS mQs; - @Mock private QSFragment mQSFragment; - @Mock private ViewGroup mQsHeader; - @Mock private ViewParent mViewParent; - @Mock private ViewTreeObserver mViewTreeObserver; - @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; - @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; - @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; - @Mock private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; - @Mock private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel; - @Mock private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel; - @Mock private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; - @Mock private GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel; - - @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; - @Mock private KeyguardInteractor mKeyguardInteractor; - @Mock private KeyguardLongPressViewModel mKeyuardLongPressViewModel; - @Mock private CoroutineDispatcher mMainDispatcher; - @Mock private MotionEvent mDownMotionEvent; - @Captor - private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> - mEmptySpaceClickListenerCaptor; - - private NotificationPanelViewController.TouchHandler mTouchHandler; - private ConfigurationController mConfigurationController; - private SysuiStatusBarStateController mStatusBarStateController; - private NotificationPanelViewController mNotificationPanelViewController; - private View.AccessibilityDelegate mAccessibilityDelegate; - private NotificationsQuickSettingsContainer mNotificationContainerParent; - private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners; - private Handler mMainHandler; - private View.OnLayoutChangeListener mLayoutChangeListener; - - private final FalsingManagerFake mFalsingManager = new FalsingManagerFake(); - private final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); - private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); - private final ShadeExpansionStateManager mShadeExpansionStateManager = - new ShadeExpansionStateManager(); - - private QuickSettingsController mQuickSettingsController; - @Mock private Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy; - - private FragmentHostManager.FragmentListener mFragmentListener; +public class NotificationPanelViewControllerTest extends NotificationPanelViewControllerBaseTest { @Before - public void setup() { - MockitoAnnotations.initMocks(this); - SystemClock systemClock = new FakeSystemClock(); - mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager); - - KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); - keyguardStatusView.setId(R.id.keyguard_status_view); + public void before() { DejankUtils.setImmediate(true); + } - when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false); - when(mHeadsUpCallback.getContext()).thenReturn(mContext); - when(mView.getResources()).thenReturn(mResources); - when(mView.getWidth()).thenReturn(PANEL_WIDTH); - when(mResources.getConfiguration()).thenReturn(mConfiguration); - mConfiguration.orientation = ORIENTATION_PORTRAIT; - when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); - mDisplayMetrics.density = 100; - when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true); - when(mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade)) - .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE); - when(mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)) - .thenReturn(10); - when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance)) - .thenReturn(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); - when(mView.getContext()).thenReturn(getContext()); - when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar); - when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView); - when(mView.findViewById(R.id.keyguard_user_switcher_stub)).thenReturn( - mUserSwitcherStubView); - when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch); - when(mView.findViewById(R.id.notification_stack_scroller)) - .thenReturn(mNotificationStackScrollLayout); - when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000); - when(mNotificationStackScrollLayoutController.getHeadsUpCallback()) - .thenReturn(mHeadsUpCallback); - when(mKeyguardBottomAreaViewController.getView()).thenReturn(mKeyguardBottomArea); - when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea); - when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class)); - when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame); - when(mView.findViewById(R.id.keyguard_status_view)) - .thenReturn(mock(KeyguardStatusView.class)); - mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null); - mNotificationContainerParent.addView(keyguardStatusView); - mNotificationContainerParent.onFinishInflate(); - when(mView.findViewById(R.id.notification_container_parent)) - .thenReturn(mNotificationContainerParent); - when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager); - FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder( - mDisplayMetrics); - when(mKeyguardQsUserSwitchComponentFactory.build(any())) - .thenReturn(mKeyguardQsUserSwitchComponent); - when(mKeyguardQsUserSwitchComponent.getKeyguardQsUserSwitchController()) - .thenReturn(mKeyguardQsUserSwitchController); - when(mKeyguardUserSwitcherComponentFactory.build(any())) - .thenReturn(mKeyguardUserSwitcherComponent); - when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController()) - .thenReturn(mKeyguardUserSwitcherController); - when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true); - when(mQs.getView()).thenReturn(mView); - when(mQSFragment.getView()).thenReturn(mView); - doAnswer(invocation -> { - mFragmentListener = invocation.getArgument(1); - return null; - }).when(mFragmentHostManager).addTagListener(eq(QS.TAG), any()); - doAnswer((Answer<Void>) invocation -> { - mTouchHandler = invocation.getArgument(0); - return null; - }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class)); - - NotificationWakeUpCoordinator coordinator = - new NotificationWakeUpCoordinator( - mDumpManager, - mock(HeadsUpManagerPhone.class), - new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager), - mKeyguardBypassController, - mDozeParameters, - mScreenOffAnimationController, - mock(NotificationWakeUpCoordinatorLogger.class)); - mConfigurationController = new ConfigurationControllerImpl(mContext); - PulseExpansionHandler expansionHandler = new PulseExpansionHandler( - mContext, - coordinator, - mKeyguardBypassController, mHeadsUpManager, - mock(NotificationRoundnessManager.class), - mConfigurationController, - mStatusBarStateController, - mFalsingManager, - mShadeExpansionStateManager, - mLockscreenShadeTransitionController, - new FalsingCollectorFake(), - mDumpManager); - when(mKeyguardStatusViewComponentFactory.build(any())) - .thenReturn(mKeyguardStatusViewComponent); - when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController()) - .thenReturn(mKeyguardClockSwitchController); - when(mKeyguardStatusViewComponent.getKeyguardStatusViewController()) - .thenReturn(mKeyguardStatusViewController); - when(mKeyguardStatusBarViewComponentFactory.build(any(), any())) - .thenReturn(mKeyguardStatusBarViewComponent); - when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController()) - .thenReturn(mKeyguardStatusBarViewController); - when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean())) - .thenReturn(keyguardStatusView); - when(mLayoutInflater.inflate(eq(R.layout.keyguard_user_switcher), any(), anyBoolean())) - .thenReturn(mUserSwitcherView); - when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean())) - .thenReturn(mKeyguardBottomArea); - when(mNotificationRemoteInputManager.isRemoteInputActive()) - .thenReturn(false); - when(mInteractionJankMonitor.begin(any(), anyInt())) - .thenReturn(true); - when(mInteractionJankMonitor.end(anyInt())) - .thenReturn(true); - doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); - doAnswer(invocation -> { - mLayoutChangeListener = invocation.getArgument(0); - return null; - }).when(mView).addOnLayoutChangeListener(any()); - - when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver); - when(mView.getParent()).thenReturn(mViewParent); - when(mQs.getHeader()).thenReturn(mQsHeader); - when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); - - mMainHandler = new Handler(Looper.getMainLooper()); - - when(mView.requireViewById(R.id.keyguard_long_press)) - .thenReturn(mock(LongPressHandlingView.class)); - - mNotificationPanelViewController = new NotificationPanelViewController( - mView, - mMainHandler, - mLayoutInflater, - mFeatureFlags, - coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController, - mFalsingManager, new FalsingCollectorFake(), - mKeyguardStateController, - mStatusBarStateController, - mStatusBarWindowStateController, - mNotificationShadeWindowController, - mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper, - mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor, - mMetricsLogger, - mShadeLog, - mShadeHeightLogger, - mConfigurationController, - () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager, - mConversationNotificationManager, mMediaHierarchyManager, - mStatusBarKeyguardViewManager, - mGutsManager, - mNotificationsQSContainerController, - mNotificationStackScrollLayoutController, - mKeyguardStatusViewComponentFactory, - mKeyguardQsUserSwitchComponentFactory, - mKeyguardUserSwitcherComponentFactory, - mKeyguardStatusBarViewComponentFactory, - mLockscreenShadeTransitionController, - mAuthController, - mScrimController, - mUserManager, - mMediaDataManager, - mNotificationShadeDepthController, - mAmbientState, - mLockIconViewController, - mKeyguardMediaController, - mTapAgainViewController, - mNavigationModeController, - mNavigationBarController, - mQsController, - mFragmentService, - mContentResolver, - mRecordingController, - mShadeHeaderController, - mScreenOffAnimationController, - mLockscreenGestureLogger, - mShadeExpansionStateManager, - mNotificationRemoteInputManager, - mSysUIUnfoldComponent, - mSysUiState, - () -> mKeyguardBottomAreaViewController, - mKeyguardUnlockAnimationController, - mKeyguardIndicationController, - mNotificationListContainer, - mNotificationStackSizeCalculator, - mUnlockedScreenOffAnimationController, - mShadeTransitionController, - systemClock, - mKeyguardBottomAreaViewModel, - mKeyguardBottomAreaInteractor, - mAlternateBouncerInteractor, - mDreamingToLockscreenTransitionViewModel, - mOccludedToLockscreenTransitionViewModel, - mLockscreenToDreamingTransitionViewModel, - mGoneToDreamingTransitionViewModel, - mLockscreenToOccludedTransitionViewModel, - mMainDispatcher, - mKeyguardTransitionInteractor, - mDumpManager, - mKeyuardLongPressViewModel, - mKeyguardInteractor); - mNotificationPanelViewController.initDependencies( - mCentralSurfaces, - null, - () -> {}, - mNotificationShelfController); - mNotificationPanelViewController.setTrackingStartedListener(() -> {}); - mNotificationPanelViewController.setOpenCloseListener( - new NotificationPanelViewController.OpenCloseListener() { - @Override - public void onClosingFinished() {} - - @Override - public void onOpenStarted() {} - }); - mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); - ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor = - ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); - verify(mView, atLeast(1)).addOnAttachStateChangeListener( - onAttachStateChangeListenerArgumentCaptor.capture()); - mOnAttachStateChangeListeners = onAttachStateChangeListenerArgumentCaptor.getAllValues(); - - ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor = - ArgumentCaptor.forClass(View.AccessibilityDelegate.class); - verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture()); - mAccessibilityDelegate = accessibilityDelegateArgumentCaptor.getValue(); - mNotificationPanelViewController.getStatusBarStateController() - .addCallback(mNotificationPanelViewController.getStatusBarStateListener()); - mNotificationPanelViewController - .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); - verify(mNotificationStackScrollLayoutController) - .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture()); - verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); - reset(mKeyguardStatusViewController); - - when(mNotificationPanelViewControllerLazy.get()) - .thenReturn(mNotificationPanelViewController); - mQuickSettingsController = new QuickSettingsController( - mNotificationPanelViewControllerLazy, - mView, - mQsFrameTranslateController, - mShadeTransitionController, - expansionHandler, - mNotificationRemoteInputManager, - mShadeExpansionStateManager, - mStatusBarKeyguardViewManager, - mNotificationStackScrollLayoutController, - mLockscreenShadeTransitionController, - mNotificationShadeDepthController, - mShadeHeaderController, - mStatusBarTouchableRegionManager, - mKeyguardStateController, - mKeyguardBypassController, - mUpdateMonitor, - mScrimController, - mMediaDataManager, - mMediaHierarchyManager, - mAmbientState, - mRecordingController, - mFalsingManager, - new FalsingCollectorFake(), - mAccessibilityManager, - mLockscreenGestureLogger, - mMetricsLogger, - mFeatureFlags, - mInteractionJankMonitor, - mShadeLog - ); + /** + * When the Back gesture starts (progress 0%), the scrim will stay at 100% scale (1.0f). + */ + @Test + public void testBackGesture_min_scrimAtMaxScale() { + mNotificationPanelViewController.onBackProgressed(0.0f); + verify(mScrimController).applyBackScaling(1.0f); } - @After - public void tearDown() { - mNotificationPanelViewController.cancelHeightAnimator(); - mMainHandler.removeCallbacksAndMessages(null); + /** + * When the Back gesture is at max (progress 100%), the scrim will be scaled to its minimum. + */ + @Test + public void testBackGesture_max_scrimAtMinScale() { + mNotificationPanelViewController.onBackProgressed(1.0f); + verify(mScrimController).applyBackScaling( + NotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE); } @Test @@ -671,23 +149,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .isNotEqualTo(-1); } - private void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding, - int ambientPadding) { - - when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0); - when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(stackBottom); - when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(stackBottom); - when(mLockIconViewController.getTop()).thenReturn((float) (stackBottom - lockIconPadding)); - - when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding)) - .thenReturn(indicationPadding); - mNotificationPanelViewController.loadDimens(); - - mNotificationPanelViewController.setAmbientIndicationTop( - /* ambientIndicationTop= */ stackBottom - ambientPadding, - /* ambientTextVisible= */ true); - } - @Test @Ignore("b/261472011 - Test appears inconsistent across environments") public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() { @@ -992,68 +453,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() { - givenViewAttached(); - when(mResources.getBoolean( - com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true); - updateMultiUserSetting(true); - clearInvocations(mView); - - updateMultiUserSetting(false); - - ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class); - verify(mView, atLeastOnce()).addView(captor.capture(), anyInt()); - final View userSwitcherStub = CollectionUtils.find(captor.getAllValues(), - view -> view.getId() == R.id.keyguard_user_switcher_stub); - assertThat(userSwitcherStub).isNotNull(); - assertThat(userSwitcherStub).isInstanceOf(ViewStub.class); - } - - @Test - public void testChangeSmallestScreenWidthAndUserSwitchEnabled_inflatesUserSwitchView() { - givenViewAttached(); - when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(null); - updateSmallestScreenWidth(300); - when(mResources.getBoolean( - com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true); - when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false); - when(mUserManager.isUserSwitcherEnabled(false)).thenReturn(true); - - updateSmallestScreenWidth(800); - - verify(mUserSwitcherStubView).inflate(); - } - - @Test - public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() { - givenViewAttached(); - when(mResources.getBoolean( - com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true); - when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false); - when(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */)) - .thenReturn(false); - - mNotificationPanelViewController.onFinishInflate(); - - verify(mUserSwitcherStubView, never()).inflate(); - verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true); - } - - @Test - public void testReInflateViews_userSwitcherDisabled_doNotInflateUserSwitchView() { - givenViewAttached(); - when(mResources.getBoolean( - com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true); - when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false); - when(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */)) - .thenReturn(false); - - mNotificationPanelViewController.reInflateViews(); - - verify(mUserSwitcherStubView, never()).inflate(); - } - - @Test public void testCanCollapsePanelOnTouch_trueForKeyGuard() { mStatusBarStateController.setState(KEYGUARD); @@ -1129,26 +528,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testDoubleTapRequired_Keyguard() { - FalsingManager.FalsingTapListener listener = getFalsingTapListener(); - mStatusBarStateController.setState(KEYGUARD); - - listener.onAdditionalTapRequired(); - - verify(mKeyguardIndicationController).showTransientIndication(anyInt()); - } - - @Test - public void testDoubleTapRequired_ShadeLocked() { - FalsingManager.FalsingTapListener listener = getFalsingTapListener(); - mStatusBarStateController.setState(SHADE_LOCKED); - - listener.onAdditionalTapRequired(); - - verify(mTapAgainViewController).show(); - } - - @Test public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() { mStatusBarStateController.setState(KEYGUARD); when(mQsController.getExpanded()).thenReturn(true); @@ -1423,19 +802,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testOnAttachRefreshStatusBarState() { - mStatusBarStateController.setState(KEYGUARD); - when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(false); - for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { - listener.onViewAttachedToWindow(mView); - } - verify(mKeyguardStatusViewController).setKeyguardStatusViewVisibility( - KEYGUARD/*statusBarState*/, - false/*keyguardFadingAway*/, - false/*goingToFullShade*/, SHADE/*oldStatusBarState*/); - } - - @Test public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() { enableSplitShade(true); mNotificationPanelViewController.expandWithQs(); @@ -1635,98 +1001,4 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mStatusBarStateController.setState(SHADE_LOCKED); assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isTrue(); } - - private static MotionEvent createMotionEvent(int x, int y, int action) { - return MotionEvent.obtain( - /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0); - } - - private void triggerPositionClockAndNotifications() { - mNotificationPanelViewController.onQsSetExpansionHeightCalled(false); - } - - private FalsingManager.FalsingTapListener getFalsingTapListener() { - for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { - listener.onViewAttachedToWindow(mView); - } - assertThat(mFalsingManager.getTapListeners().size()).isEqualTo(1); - return mFalsingManager.getTapListeners().get(0); - } - - private void givenViewAttached() { - for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) { - listener.onViewAttachedToWindow(mView); - } - } - - private ConstraintSet.Layout getConstraintSetLayout(@IdRes int id) { - ConstraintSet constraintSet = new ConstraintSet(); - constraintSet.clone(mNotificationContainerParent); - return constraintSet.getConstraint(id).layout; - } - - private void enableSplitShade(boolean enabled) { - when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled); - mNotificationPanelViewController.updateResources(); - } - - private void updateMultiUserSetting(boolean enabled) { - when(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)).thenReturn(false); - when(mUserManager.isUserSwitcherEnabled(false)).thenReturn(enabled); - final ArgumentCaptor<ContentObserver> observerCaptor = - ArgumentCaptor.forClass(ContentObserver.class); - verify(mContentResolver) - .registerContentObserver(any(), anyBoolean(), observerCaptor.capture()); - observerCaptor.getValue().onChange(/* selfChange */ false); - } - - private void updateSmallestScreenWidth(int smallestScreenWidthDp) { - Configuration configuration = new Configuration(); - configuration.smallestScreenWidthDp = smallestScreenWidthDp; - mConfigurationController.onConfigurationChanged(configuration); - } - - private void onTouchEvent(MotionEvent ev) { - mTouchHandler.onTouch(mView, ev); - } - - private void setDozing(boolean dozing, boolean dozingAlwaysOn) { - when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn); - mNotificationPanelViewController.setDozing( - /* dozing= */ dozing, - /* animate= */ false - ); - } - - private void assertKeyguardStatusViewCentered() { - mNotificationPanelViewController.updateResources(); - assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isAnyOf( - ConstraintSet.PARENT_ID, ConstraintSet.UNSET); - } - - private void assertKeyguardStatusViewNotCentered() { - mNotificationPanelViewController.updateResources(); - assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isEqualTo( - R.id.qs_edge_guideline); - } - - private void setIsFullWidth(boolean fullWidth) { - float nsslWidth = fullWidth ? PANEL_WIDTH : PANEL_WIDTH / 2f; - when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(nsslWidth); - triggerLayoutChange(); - } - - private void triggerLayoutChange() { - mLayoutChangeListener.onLayoutChange( - mView, - /* left= */ 0, - /* top= */ 0, - /* right= */ 0, - /* bottom= */ 0, - /* oldLeft= */ 0, - /* oldTop= */ 0, - /* oldRight= */ 0, - /* oldBottom= */ 0 - ); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt new file mode 100644 index 000000000000..0c046e93ee20 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt @@ -0,0 +1,179 @@ +/* + * 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.shade + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.view.ViewStub +import androidx.test.filters.SmallTest +import com.android.internal.util.CollectionUtils +import com.android.keyguard.KeyguardClockSwitch.LARGE +import com.android.systemui.R +import com.android.systemui.statusbar.StatusBarState.KEYGUARD +import com.android.systemui.statusbar.StatusBarState.SHADE +import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class NotificationPanelViewControllerWithCoroutinesTest : + NotificationPanelViewControllerBaseTest() { + + @Captor private lateinit var viewCaptor: ArgumentCaptor<View> + + override fun getMainDispatcher() = Dispatchers.Main.immediate + + @Test + fun testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() = runTest { + launch(Dispatchers.Main.immediate) { givenViewAttached() } + advanceUntilIdle() + + whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher)) + .thenReturn(true) + updateMultiUserSetting(true) + clearInvocations(mView) + + updateMultiUserSetting(false) + + verify(mView, atLeastOnce()).addView(viewCaptor.capture(), anyInt()) + val userSwitcherStub = + CollectionUtils.find( + viewCaptor.getAllValues(), + { view -> view.getId() == R.id.keyguard_user_switcher_stub } + ) + assertThat(userSwitcherStub).isNotNull() + assertThat(userSwitcherStub).isInstanceOf(ViewStub::class.java) + } + + @Test + fun testChangeSmallestScreenWidthAndUserSwitchEnabled_inflatesUserSwitchView() = runTest { + launch(Dispatchers.Main.immediate) { givenViewAttached() } + advanceUntilIdle() + + whenever(mView.findViewById<View>(R.id.keyguard_user_switcher_view)).thenReturn(null) + updateSmallestScreenWidth(300) + whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher)) + .thenReturn(true) + whenever(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)) + .thenReturn(false) + whenever(mUserManager.isUserSwitcherEnabled(false)).thenReturn(true) + + updateSmallestScreenWidth(800) + + verify(mUserSwitcherStubView).inflate() + } + + @Test + fun testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() = runTest { + launch(Dispatchers.Main.immediate) { givenViewAttached() } + advanceUntilIdle() + + whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher)) + .thenReturn(true) + whenever(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)) + .thenReturn(false) + whenever(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */)) + .thenReturn(false) + + mNotificationPanelViewController.onFinishInflate() + + verify(mUserSwitcherStubView, never()).inflate() + verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true) + + coroutineContext.cancelChildren() + } + + @Test + fun testReInflateViews_userSwitcherDisabled_doNotInflateUserSwitchView() = runTest { + launch(Dispatchers.Main.immediate) { givenViewAttached() } + advanceUntilIdle() + + whenever(mResources.getBoolean(com.android.internal.R.bool.config_keyguardUserSwitcher)) + .thenReturn(true) + whenever(mResources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)) + .thenReturn(false) + whenever(mUserManager.isUserSwitcherEnabled(false /* showEvenIfNotActionable */)) + .thenReturn(false) + + mNotificationPanelViewController.reInflateViews() + + verify(mUserSwitcherStubView, never()).inflate() + + coroutineContext.cancelChildren() + } + + @Test + fun testDoubleTapRequired_Keyguard() = runTest { + launch(Dispatchers.Main.immediate) { + val listener = getFalsingTapListener() + mStatusBarStateController.setState(KEYGUARD) + + listener.onAdditionalTapRequired() + + verify(mKeyguardIndicationController).showTransientIndication(anyInt()) + } + advanceUntilIdle() + } + + @Test + fun testDoubleTapRequired_ShadeLocked() = runTest { + launch(Dispatchers.Main.immediate) { + val listener = getFalsingTapListener() + mStatusBarStateController.setState(SHADE_LOCKED) + + listener.onAdditionalTapRequired() + + verify(mTapAgainViewController).show() + } + advanceUntilIdle() + } + + @Test + fun testOnAttachRefreshStatusBarState() = runTest { + launch(Dispatchers.Main.immediate) { + mStatusBarStateController.setState(KEYGUARD) + whenever(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(false) + mOnAttachStateChangeListeners.forEach { it.onViewAttachedToWindow(mView) } + verify(mKeyguardStatusViewController) + .setKeyguardStatusViewVisibility( + KEYGUARD /*statusBarState*/, + false /*keyguardFadingAway*/, + false /*goingToFullShade*/, + SHADE /*oldStatusBarState*/ + ) + } + advanceUntilIdle() + } +} 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 d229a08ad7c4..0a401b09b6cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -28,10 +28,10 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager -import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -47,6 +47,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.emptyFlow import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -81,8 +82,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock - private lateinit var featureFlags: FeatureFlags - @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel @@ -124,6 +123,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { .thenReturn(keyguardBouncerComponent) whenever(keyguardBouncerComponent.securityContainerController) .thenReturn(keyguardSecurityContainerController) + whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) + .thenReturn(emptyFlow<TransitionStep>()) underTest = NotificationShadeWindowViewController( lockscreenShadeTransitionController, FalsingCollectorFake(), @@ -143,7 +144,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { notificationInsetsController, ambientState, pulsingGestureListener, - featureFlags, keyguardBouncerViewModel, keyguardBouncerComponentFactory, alternateBouncerInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index 5e9c2199897d..5d719790386a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import android.os.SystemClock; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -40,7 +42,6 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dock.DockManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -93,7 +94,6 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock private AmbientState mAmbientState; @Mock private PulsingGestureListener mPulsingGestureListener; - @Mock private FeatureFlags mFeatureFlags; @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel; @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent; @@ -125,6 +125,9 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { when(mDockManager.isDocked()).thenReturn(false); + when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition()) + .thenReturn(emptyFlow()); + mController = new NotificationShadeWindowViewController( mLockscreenShadeTransitionController, new FalsingCollectorFake(), @@ -144,7 +147,6 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mNotificationInsetsController, mAmbientState, mPulsingGestureListener, - mFeatureFlags, mKeyguardBouncerViewModel, mKeyguardBouncerComponentFactory, mAlternateBouncerInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java index bea2cfb8bb5c..bedb2b33d07b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java @@ -73,7 +73,7 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase { .startMocking(); mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false); mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags)); - when(Utilities.isTablet(mContext)).thenReturn(true); + when(Utilities.isLargeScreen(mContext)).thenReturn(true); mKeyboardShortcutsReceiver.onReceive(mContext, mIntent); @@ -90,7 +90,7 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase { .startMocking(); mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false); mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags)); - when(Utilities.isTablet(mContext)).thenReturn(false); + when(Utilities.isLargeScreen(mContext)).thenReturn(false); mKeyboardShortcutsReceiver.onReceive(mContext, mIntent); @@ -107,7 +107,7 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase { .startMocking(); mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true); mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags)); - when(Utilities.isTablet(mContext)).thenReturn(true); + when(Utilities.isLargeScreen(mContext)).thenReturn(true); mKeyboardShortcutsReceiver.onReceive(mContext, mIntent); @@ -124,7 +124,7 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase { .startMocking(); mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true); mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags)); - when(Utilities.isTablet(mContext)).thenReturn(false); + when(Utilities.isLargeScreen(mContext)).thenReturn(false); mKeyboardShortcutsReceiver.onReceive(mContext, mIntent); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt deleted file mode 100644 index a6a9e51aa555..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeRepoTest.kt +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.fsi - -import android.R -import android.app.Notification -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable -import android.os.UserHandle -import android.service.dreams.IDreamManager -import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper.RunWithLooper -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider -import com.android.systemui.statusbar.phone.CentralSurfaces -import java.util.concurrent.Executor -import junit.framework.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.never -import org.mockito.Mockito.times -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@RunWithLooper(setAsMainLooper = true) -class FsiChromeRepoTest : SysuiTestCase() { - - @Mock lateinit var centralSurfaces: CentralSurfaces - @Mock lateinit var fsiChromeRepo: FsiChromeRepo - @Mock lateinit var packageManager: PackageManager - - var keyguardRepo = FakeKeyguardRepository() - @Mock private lateinit var applicationInfo: ApplicationInfo - - @Mock lateinit var launchFullScreenIntentProvider: LaunchFullScreenIntentProvider - var featureFlags = FakeFeatureFlags() - @Mock lateinit var dreamManager: IDreamManager - - // Execute all foreground & background requests immediately - private val uiBgExecutor = Executor { r -> r.run() } - - private val appName: String = "appName" - private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android) - private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java) - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - // Set up package manager mocks - whenever(packageManager.getApplicationIcon(anyString())).thenReturn(appIcon) - whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) - .thenReturn(appIcon) - whenever(packageManager.getApplicationLabel(any())).thenReturn(appName) - mContext.setMockPackageManager(packageManager) - - fsiChromeRepo = - FsiChromeRepo( - mContext, - packageManager, - keyguardRepo, - launchFullScreenIntentProvider, - featureFlags, - uiBgExecutor, - dreamManager, - centralSurfaces - ) - } - - private fun createFsiEntry(fsi: PendingIntent): NotificationEntry { - val nb = - Notification.Builder(mContext, "a") - .setContentTitle("foo") - .setSmallIcon(R.drawable.sym_def_app_icon) - .setFullScreenIntent(fsi, /* highPriority= */ true) - - val sbn = - StatusBarNotification( - "pkg", - "opPkg", - /* id= */ 0, - "tag" + System.currentTimeMillis(), - /* uid= */ 0, - /* initialPid */ 0, - nb.build(), - UserHandle(0), - /* overrideGroupKey= */ null, - /* postTime= */ 0 - ) - - val entry = Mockito.mock(NotificationEntry::class.java) - whenever(entry.importance).thenReturn(NotificationManager.IMPORTANCE_HIGH) - whenever(entry.sbn).thenReturn(sbn) - return entry - } - - @Test - fun testLaunchFullscreenIntent_flagNotEnabled_noLaunch() { - // Setup - featureFlags.set(Flags.FSI_CHROME, false) - - // Test - val entry = createFsiEntry(fsi) - fsiChromeRepo.launchFullscreenIntent(entry) - - // Verify - Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent() - } - - @Test - fun testLaunchFullscreenIntent_notOnKeyguard_noLaunch() { - // Setup - featureFlags.set(Flags.FSI_CHROME, true) - keyguardRepo.setKeyguardShowing(false) - - // Test - val entry = createFsiEntry(fsi) - fsiChromeRepo.launchFullscreenIntent(entry) - - // Verify - Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent() - } - - @Test - fun testLaunchFullscreenIntent_stopsScreensaver() { - // Setup - featureFlags.set(Flags.FSI_CHROME, true) - keyguardRepo.setKeyguardShowing(true) - - // Test - val entry = createFsiEntry(fsi) - fsiChromeRepo.launchFullscreenIntent(entry) - - // Verify - Mockito.verify(dreamManager, times(1)).awaken() - } - - @Test - fun testLaunchFullscreenIntent_updatesFsiInfoFlow() { - // Setup - featureFlags.set(Flags.FSI_CHROME, true) - keyguardRepo.setKeyguardShowing(true) - - // Test - val entry = createFsiEntry(fsi) - fsiChromeRepo.launchFullscreenIntent(entry) - - // Verify - val expectedFsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi) - assertEquals(expectedFsiInfo, fsiChromeRepo.infoFlow.value) - } - - @Test - fun testLaunchFullscreenIntent_notifyFsiLaunched() { - // Setup - featureFlags.set(Flags.FSI_CHROME, true) - keyguardRepo.setKeyguardShowing(true) - - // Test - val entry = createFsiEntry(fsi) - fsiChromeRepo.launchFullscreenIntent(entry) - - // Verify - Mockito.verify(entry, times(1)).notifyFullScreenIntentLaunched() - } - - @Test - fun testLaunchFullscreenIntent_wakesUpDevice() { - // Setup - featureFlags.set(Flags.FSI_CHROME, true) - keyguardRepo.setKeyguardShowing(true) - - // Test - val entry = createFsiEntry(fsi) - fsiChromeRepo.launchFullscreenIntent(entry) - - // Verify - Mockito.verify(centralSurfaces, times(1)).wakeUpForFullScreenIntent() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt deleted file mode 100644 index 5cee9e377dfb..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.fsi - -import android.app.PendingIntent -import android.graphics.drawable.Drawable -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper.RunWithLooper -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.mockito.withArgCaptor -import com.android.systemui.util.time.FakeSystemClock -import com.android.wm.shell.TaskView -import com.android.wm.shell.TaskViewFactory -import com.google.common.truth.Truth.assertThat -import java.util.Optional -import java.util.function.Consumer -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@RunWithLooper(setAsMainLooper = true) -class FsiChromeViewModelFactoryTest : SysuiTestCase() { - @Mock private lateinit var taskViewFactoryOptional: Optional<TaskViewFactory> - @Mock private lateinit var taskViewFactory: TaskViewFactory - @Mock lateinit var taskView: TaskView - - @Main var mainExecutor = FakeExecutor(FakeSystemClock()) - lateinit var viewModelFactory: FsiChromeViewModelFactory - - private val fakeInfoFlow = MutableStateFlow<FsiChromeRepo.FSIInfo?>(null) - private var fsiChromeRepo: FsiChromeRepo = - mock<FsiChromeRepo>().apply { whenever(infoFlow).thenReturn(fakeInfoFlow) } - - private val appName = "appName" - private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android) - private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java) - private val fsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi) - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - whenever(taskViewFactoryOptional.get()).thenReturn(taskViewFactory) - - viewModelFactory = - FsiChromeViewModelFactory(fsiChromeRepo, taskViewFactoryOptional, context, mainExecutor) - } - - @Test - fun testViewModelFlow_update_createsTaskView() { - runTest { - val latestViewModel = - viewModelFactory.viewModelFlow - .onStart { FsiDebug.log("viewModelFactory.viewModelFlow.onStart") } - .stateIn( - backgroundScope, // stateIn runs forever, don't count it as test coroutine - SharingStarted.Eagerly, - null - ) - runCurrent() // Drain queued backgroundScope operations - - // Test: emit the fake FSIInfo - fakeInfoFlow.emit(fsiInfo) - runCurrent() - - val taskViewFactoryCallback: Consumer<TaskView> = withArgCaptor { - verify(taskViewFactory).create(any(), any(), capture()) - } - taskViewFactoryCallback.accept(taskView) // this will call k.resume - runCurrent() - - // Verify that the factory has produced a new ViewModel - // containing the relevant data from FsiInfo - val expectedViewModel = - FsiChromeViewModel(appName, appIcon, taskView, fsi, fsiChromeRepo) - - assertThat(latestViewModel.value).isEqualTo(expectedViewModel) - } - } -} 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 ec294b12a11a..68d67ca20007 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 @@ -517,6 +517,26 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { verify(mVibratorHelper).vibrateAuthError(anyString()); } + @Test + public void onFingerprintDetect_showBouncer() { + // WHEN fingerprint detect occurs + mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT, + BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */); + + // THEN shows primary bouncer + verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean()); + } + + @Test + public void onFaceDetect_showBouncer() { + // WHEN face detect occurs + mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT, + BiometricSourceType.FACE, false /* isStrongBiometric */); + + // THEN shows primary bouncer + verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean()); + } + private void givenFingerprintModeUnlockCollapsing() { when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); 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 38e82281856f..a1684320e219 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 @@ -77,6 +77,8 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewRootImpl; import android.view.WindowManager; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; @@ -114,6 +116,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.notetask.NoteTaskController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; @@ -181,6 +184,8 @@ import com.android.systemui.volume.VolumeComponent; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.startingsurface.StartingSurface; +import dagger.Lazy; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -193,8 +198,6 @@ import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.util.Optional; -import dagger.Lazy; - @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @@ -259,6 +262,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private StatusBarWindowStateController mStatusBarWindowStateController; @Mock private UserSwitcherController mUserSwitcherController; @Mock private Bubbles mBubbles; + @Mock private NoteTaskController mNoteTaskController; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private NotificationIconAreaController mNotificationIconAreaController; @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController; @@ -331,6 +335,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false); // Set default value to avoid IllegalStateException. mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false); + // For the Shade to respond to Back gesture, we must enable the event routing + mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true); + // For the Shade to animate during the Back gesture, we must enable the animation flag. + mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true); IThermalService thermalService = mock(IThermalService.class); mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService, @@ -472,6 +480,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mWakefulnessLifecycle, mStatusBarStateController, Optional.of(mBubbles), + () -> mNoteTaskController, mDeviceProvisionedController, mNavigationBarController, mAccessibilityFloatingMenuController, @@ -852,6 +861,50 @@ public class CentralSurfacesImplTest extends SysuiTestCase { verify(mShadeController).animateCollapseShade(); } + /** + * When back progress is at 100%, the onBackProgressed animation driver inside + * NotificationPanelViewController should be invoked appropriately (with 1.0f passed in). + */ + @Test + public void testPredictiveBackAnimation_progressMaxScalesPanel() { + mCentralSurfaces.setNotificationShadeWindowViewController( + mNotificationShadeWindowViewController); + mCentralSurfaces.handleVisibleToUserChanged(true); + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), + mOnBackInvokedCallback.capture()); + + OnBackAnimationCallback onBackAnimationCallback = + (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue()); + when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true); + + BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT); + onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge); + verify(mNotificationPanelViewController).onBackProgressed(eq(1.0f)); + } + + /** + * When back progress is at 0%, the onBackProgressed animation driver inside + * NotificationPanelViewController should be invoked appropriately (with 0.0f passed in). + */ + @Test + public void testPredictiveBackAnimation_progressMinScalesPanel() { + mCentralSurfaces.setNotificationShadeWindowViewController( + mNotificationShadeWindowViewController); + mCentralSurfaces.handleVisibleToUserChanged(true); + verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback( + eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), + mOnBackInvokedCallback.capture()); + + OnBackAnimationCallback onBackAnimationCallback = + (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue()); + when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true); + + BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT); + onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge); + verify(mNotificationPanelViewController).onBackProgressed(eq(0.0f)); + } + @Test public void testPanelOpenForHeadsUp() { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); 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 c0537a6dc4cf..dc5a0472f49e 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 @@ -1356,33 +1356,10 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test - public void notificationAlpha_unnocclusionAnimating_bouncerActive_usesKeyguardNotifAlpha() { - when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true); - mScrimController.setClipsQsScrim(true); - - mScrimController.transitionTo(ScrimState.KEYGUARD); - mScrimController.setUnocclusionAnimationRunning(true); - - assertAlphaAfterExpansion( - mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 0f); - assertAlphaAfterExpansion( - mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 0.4f); - assertAlphaAfterExpansion( - mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 1.0f); - - // Verify normal behavior after - mScrimController.setUnocclusionAnimationRunning(false); - float expansion = 0.4f; - float alpha = 1 - BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion); - assertAlphaAfterExpansion(mNotificationsScrim, alpha, expansion); - } - - @Test public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() { when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false); mScrimController.transitionTo(ScrimState.KEYGUARD); - mScrimController.setUnocclusionAnimationRunning(true); assertAlphaAfterExpansion( mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 0f); @@ -1392,7 +1369,6 @@ public class ScrimControllerTest extends SysuiTestCase { mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 1.0f); // Verify normal behavior after - mScrimController.setUnocclusionAnimationRunning(false); float expansion = 0.4f; float alpha = 1 - ShadeInterpolation.getNotificationScrimAlpha(expansion); assertAlphaAfterExpansion(mNotificationsScrim, alpha, expansion); @@ -1598,7 +1574,6 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void setUnOccludingAnimationKeyguard() { - mScrimController.setUnocclusionAnimationRunning(true); mScrimController.transitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); assertThat(mNotificationsScrim.getViewAlpha()) 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 1aad83eb73ae..158e9adcff43 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 @@ -368,17 +368,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() { - mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */); - verify(mCentralSurfaces).animateKeyguardUnoccluding(); - - when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true); - clearInvocations(mCentralSurfaces); - mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */); - verify(mCentralSurfaces, never()).animateKeyguardUnoccluding(); - } - - @Test public void setOccluded_onKeyguardOccludedChangedCalled() { clearInvocations(mKeyguardStateController); clearInvocations(mKeyguardUpdateMonitor); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 1f8a77965bf5..7fba72b74e8d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -3801,6 +3801,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public boolean registerProxyForDisplay(IAccessibilityServiceClient client, int displayId) throws RemoteException { mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); + mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE); if (client == null) { return false; } @@ -3837,6 +3838,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public boolean unregisterProxyForDisplay(int displayId) { mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); + mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE); final long identity = Binder.clearCallingIdentity(); try { return mProxyManager.unregisterProxy(displayId); @@ -3928,6 +3930,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mGlobalClients.getRegisteredCallbackCookie(i); pw.append(Arrays.toString(client.mPackageNames)); } + pw.println(); + mProxyManager.dump(fd, pw, args); } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index 094053ed5396..0e25a06b4727 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -910,6 +910,10 @@ public class AccessibilityWindowManager { pw.println(" Top Focused Window Id = " + mTopFocusedWindowId); pw.println(" Accessibility Focused Window Id = " + mAccessibilityFocusedWindowId + " ]"); + if (mIsProxy) { + pw.println("Proxy accessibility focused window = " + + mProxyDisplayAccessibilityFocusedWindow); + } pw.println(); if (mWindows != null) { final int windowCount = mWindows.size(); diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java index 945d43b94dac..b19a502547ab 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java @@ -38,14 +38,18 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.view.KeyEvent; import android.view.accessibility.AccessibilityDisplayProxy; +import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import androidx.annotation.Nullable; import com.android.internal.R; +import com.android.internal.util.DumpUtils; import com.android.server.wm.WindowManagerInternal; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -61,6 +65,8 @@ import java.util.Set; * TODO(241429275): Initialize this when a proxy is registered. */ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection { + private static final String LOG_TAG = "ProxyAccessibilityServiceConnection"; + private int mDisplayId; private List<AccessibilityServiceInfo> mInstalledAndEnabledServices; @@ -565,4 +571,25 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon public void setAnimationScale(float scale) throws UnsupportedOperationException { throw new UnsupportedOperationException("setAnimationScale is not supported"); } + + @Override + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; + synchronized (mLock) { + pw.append("Proxy[displayId=" + mDisplayId); + pw.append(", feedbackType" + + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType)); + pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities()); + pw.append(", eventTypes=" + + AccessibilityEvent.eventTypeToString(mEventTypes)); + pw.append(", notificationTimeout=" + mNotificationTimeout); + pw.append(", focusStrokeWidth=").append(String.valueOf(mFocusStrokeWidth)); + pw.append(", focusColor=").append(String.valueOf(mFocusColor)); + pw.append(", installedAndEnabledServiceCount=").append(String.valueOf( + mInstalledAndEnabledServices.size())); + pw.append(", installedAndEnabledServices=").append( + mInstalledAndEnabledServices.toString()); + pw.append("]"); + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java index 9d91d10a3aa1..e258de16caf5 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java @@ -23,6 +23,7 @@ import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.accessibility.AccessibilityEvent; @@ -30,6 +31,8 @@ import android.view.accessibility.AccessibilityManager; import com.android.server.wm.WindowManagerInternal; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.List; /** @@ -42,6 +45,9 @@ import java.util.List; * TODO(262244375): Add unit tests. */ public class ProxyManager { + private static final boolean DEBUG = false; + private static final String LOG_TAG = "ProxyManager"; + // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in // the infos of connection.setInstalledAndEnabledServices static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage"; @@ -79,6 +85,9 @@ public class ProxyManager { AbstractAccessibilityServiceConnection.SystemSupport systemSupport, AccessibilityTrace trace, WindowManagerInternal windowManagerInternal) throws RemoteException { + if (DEBUG) { + Slog.v(LOG_TAG, "Register proxy for display id: " + displayId); + } // Set a default AccessibilityServiceInfo that is used before the proxy's info is // populated. A proxy has the touch exploration and window capabilities. @@ -134,6 +143,9 @@ public class ProxyManager { if (mProxyA11yServiceConnections.contains(displayId)) { mProxyA11yServiceConnections.remove(displayId); removed = true; + if (DEBUG) { + Slog.v(LOG_TAG, "Unregister proxy for display id " + displayId); + } } } if (removed) { @@ -155,19 +167,25 @@ public class ProxyManager { */ public boolean isProxyed(int displayId) { synchronized (mLock) { - return mProxyA11yServiceConnections.contains(displayId); + final boolean tracked = mProxyA11yServiceConnections.contains(displayId); + if (DEBUG) { + Slog.v(LOG_TAG, "Tracking proxy display " + displayId + " : " + tracked); + } + return tracked; } } /** - * Sends AccessibilityEvents to all proxies. - * {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display. - * TODO(b/250929565): Filtering should happen in the system, not in the proxy. + * Sends AccessibilityEvents to a proxy given the event's displayId. */ public void sendAccessibilityEventLocked(AccessibilityEvent event) { final ProxyAccessibilityServiceConnection proxy = mProxyA11yServiceConnections.get(event.getDisplayId()); if (proxy != null) { + if (DEBUG) { + Slog.v(LOG_TAG, "Send proxy event " + event + " for display id " + + event.getDisplayId()); + } proxy.notifyAccessibilityEvent(event); } } @@ -186,6 +204,9 @@ public class ProxyManager { break; } } + if (DEBUG) { + Slog.v(LOG_TAG, "At least one proxy can retrieve windows: " + observingWindows); + } return observingWindows; } @@ -205,6 +226,14 @@ public class ProxyManager { clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; } } + + if (DEBUG) { + Slog.v(LOG_TAG, "Accessibility is enabled for all proxies: " + + ((clientState & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0)); + Slog.v(LOG_TAG, "Touch exploration is enabled for all proxies: " + + ((clientState & AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED) + != 0)); + } return clientState; // TODO(b/254545943): When A11yManager is separated, include support for other properties. } @@ -234,6 +263,10 @@ public class ProxyManager { mProxyA11yServiceConnections.valueAt(i); relevantEventTypes |= proxy.getRelevantEventTypes(); } + if (DEBUG) { + Slog.v(LOG_TAG, "Relevant event types for all proxies: " + + AccessibilityEvent.eventTypeToString(relevantEventTypes)); + } return relevantEventTypes; } @@ -275,4 +308,25 @@ public class ProxyManager { void setAccessibilityInputFilter(AccessibilityInputFilter filter) { mA11yInputFilter = filter; } + + + /** + * Prints information belonging to each display that is controlled by an + * AccessibilityDisplayProxy. + */ + void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + synchronized (mLock) { + pw.println(); + pw.println("Proxy manager state:"); + pw.println(" Number of proxy connections: " + mProxyA11yServiceConnections.size()); + pw.println(" Registered proxy connections:"); + for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) { + final ProxyAccessibilityServiceConnection proxy = + mProxyA11yServiceConnections.valueAt(i); + if (proxy != null) { + proxy.dump(fd, pw, args); + } + } + } + } }
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 9c84c048003d..f85ef43f99fe 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -44,6 +44,8 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.provider.Settings; import android.util.Log; import android.util.MathUtils; @@ -279,7 +281,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH return mTempPointerProperties; } - private void transitionTo(State state) { + @VisibleForTesting + void transitionTo(State state) { if (DEBUG_STATE_TRANSITIONS) { Slog.i(mLogTag, (State.nameOf(mCurrentState) + " -> " + State.nameOf(state) @@ -287,6 +290,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH .replace(getClass().getName(), "")); } mPreviousState = mCurrentState; + if (state == mPanningScalingState) { + mPanningScalingState.prepareForState(); + } mCurrentState = state; } @@ -317,18 +323,34 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH final class PanningScalingState extends SimpleOnGestureListener implements OnScaleGestureListener, State { + private final Context mContext; private final ScaleGestureDetector mScaleGestureDetector; private final GestureDetector mScrollGestureDetector; final float mScalingThreshold; float mInitialScaleFactor = -1; - boolean mScaling; + @VisibleForTesting boolean mScaling; + + /** + * Whether it needs to detect the target scale passes + * {@link FullScreenMagnificationController#getPersistedScale} during panning scale. + */ + @VisibleForTesting boolean mDetectingPassPersistedScale; + + // The threshold for relative difference from given scale to persisted scale. If the + // difference >= threshold, we can start detecting if the scale passes the persisted + // scale during panning. + @VisibleForTesting static final float CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD = 0.2f; + // The threshold for relative difference from given scale to persisted scale. If the + // difference < threshold, we can decide that the scale passes the persisted scale. + @VisibleForTesting static final float PASSING_PERSISTED_SCALE_THRESHOLD = 0.01f; PanningScalingState(Context context) { final TypedValue scaleValue = new TypedValue(); context.getResources().getValue( R.dimen.config_screen_magnification_scaling_threshold, scaleValue, false); + mContext = context; mScalingThreshold = scaleValue.getFloat(); mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain()); mScaleGestureDetector.setQuickScaleEnabled(false); @@ -351,12 +373,59 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } } + + void prepareForState() { + checkShouldDetectPassPersistedScale(); + } + + private void checkShouldDetectPassPersistedScale() { + if (mDetectingPassPersistedScale) { + return; + } + + final float currentScale = + mFullScreenMagnificationController.getScale(mDisplayId); + final float persistedScale = + mFullScreenMagnificationController.getPersistedScale(mDisplayId); + + mDetectingPassPersistedScale = + (abs(currentScale - persistedScale) / persistedScale) + >= CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD; + } + public void persistScaleAndTransitionTo(State state) { mFullScreenMagnificationController.persistScale(mDisplayId); clear(); transitionTo(state); } + @VisibleForTesting + void setScaleAndClearIfNeeded(float scale, float pivotX, float pivotY) { + if (mDetectingPassPersistedScale) { + final float persistedScale = + mFullScreenMagnificationController.getPersistedScale(mDisplayId); + // If the scale passes the persisted scale during panning, perform a vibration + // feedback to user. Also, call {@link clear} to create a buffer zone so that + // user needs to panning more than {@link mScalingThreshold} to change scale again. + if (abs(scale - persistedScale) / persistedScale + < PASSING_PERSISTED_SCALE_THRESHOLD) { + scale = persistedScale; + final Vibrator vibrator = mContext.getSystemService(Vibrator.class); + if (vibrator != null) { + vibrator.vibrate( + VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)); + } + clear(); + } + } + + if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x"); + mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + + checkShouldDetectPassPersistedScale(); + } + @Override public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX, float distanceY) { @@ -402,11 +471,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH scale = targetScale; } - final float pivotX = detector.getFocusX(); - final float pivotY = detector.getFocusY(); - if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x"); - mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + setScaleAndClearIfNeeded(scale, detector.getFocusX(), detector.getFocusY()); return /* handled: */ true; } @@ -424,6 +489,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH public void clear() { mInitialScaleFactor = -1; mScaling = false; + mDetectingPassPersistedScale = false; } @Override diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index 494c5a6c0779..6a53adfeea9d 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -18,51 +18,31 @@ package com.android.server.companion.transport; import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; +import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.Build; import android.os.ParcelFileDescriptor; -import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; -import com.android.server.companion.securechannel.SecureChannel; - -import libcore.io.IoUtils; -import libcore.io.Streams; -import libcore.util.EmptyArray; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; @SuppressLint("LongLogTag") public class CompanionTransportManager { private static final String TAG = "CDM_CompanionTransportManager"; - // TODO: flip to false - private static final boolean DEBUG = true; - - private static final int HEADER_LENGTH = 12; - - private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN - private static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES - - private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC - private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI + private static final boolean DEBUG = false; private boolean mSecureTransportEnabled = true; @@ -127,9 +107,9 @@ public class CompanionTransportManager { final Transport transport; if (isSecureTransportEnabled(associationId)) { - transport = new SecureTransport(associationId, fd); + transport = new SecureTransport(associationId, fd, mContext, mListener); } else { - transport = new RawTransport(associationId, fd); + transport = new RawTransport(associationId, fd, mContext, mListener); } transport.start(); @@ -172,296 +152,4 @@ public class CompanionTransportManager { // TODO: version comparison logic return enabled; } - - // TODO: Make Transport inner classes into standalone classes. - private abstract class Transport { - protected final int mAssociationId; - protected final InputStream mRemoteIn; - protected final OutputStream mRemoteOut; - - @GuardedBy("mPendingRequests") - protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests = - new SparseArray<>(); - protected final AtomicInteger mNextSequence = new AtomicInteger(); - - Transport(int associationId, ParcelFileDescriptor fd) { - this(associationId, - new ParcelFileDescriptor.AutoCloseInputStream(fd), - new ParcelFileDescriptor.AutoCloseOutputStream(fd)); - } - - Transport(int associationId, InputStream in, OutputStream out) { - this.mAssociationId = associationId; - this.mRemoteIn = in; - this.mRemoteOut = out; - } - - public abstract void start(); - public abstract void stop(); - - protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data) - throws IOException; - - public Future<byte[]> requestForResponse(int message, byte[] data) { - if (DEBUG) Slog.d(TAG, "Requesting for response"); - final int sequence = mNextSequence.incrementAndGet(); - final CompletableFuture<byte[]> pending = new CompletableFuture<>(); - synchronized (mPendingRequests) { - mPendingRequests.put(sequence, pending); - } - - try { - sendMessage(message, sequence, data); - } catch (IOException e) { - synchronized (mPendingRequests) { - mPendingRequests.remove(sequence); - } - pending.completeExceptionally(e); - } - - return pending; - } - - protected final void handleMessage(int message, int sequence, @NonNull byte[] data) - throws IOException { - if (DEBUG) { - Slog.d(TAG, "Received message 0x" + Integer.toHexString(message) - + " sequence " + sequence + " length " + data.length - + " from association " + mAssociationId); - } - - if (isRequest(message)) { - try { - processRequest(message, sequence, data); - } catch (IOException e) { - Slog.w(TAG, "Failed to respond to 0x" + Integer.toHexString(message), e); - } - } else if (isResponse(message)) { - processResponse(message, sequence, data); - } else { - Slog.w(TAG, "Unknown message 0x" + Integer.toHexString(message)); - } - } - - private void processRequest(int message, int sequence, byte[] data) - throws IOException { - switch (message) { - case MESSAGE_REQUEST_PING: { - sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data); - break; - } - case MESSAGE_REQUEST_PERMISSION_RESTORE: { - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) - && !Build.isDebuggable()) { - Slog.w(TAG, "Restoring permissions only supported on watches"); - sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE); - break; - } - try { - mListener.onRequestPermissionRestore(data); - sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE); - } catch (Exception e) { - Slog.w(TAG, "Failed to restore permissions"); - sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE); - } - break; - } - default: { - Slog.w(TAG, "Unknown request 0x" + Integer.toHexString(message)); - sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE); - break; - } - } - } - - private void processResponse(int message, int sequence, byte[] data) { - final CompletableFuture<byte[]> future; - synchronized (mPendingRequests) { - future = mPendingRequests.removeReturnOld(sequence); - } - if (future == null) { - Slog.w(TAG, "Ignoring unknown sequence " + sequence); - return; - } - - switch (message) { - case MESSAGE_RESPONSE_SUCCESS: { - future.complete(data); - break; - } - case MESSAGE_RESPONSE_FAILURE: { - future.completeExceptionally(new RuntimeException("Remote failure")); - break; - } - default: { - Slog.w(TAG, "Ignoring unknown response 0x" + Integer.toHexString(message)); - } - } - } - } - - private class RawTransport extends Transport { - private volatile boolean mStopped; - - RawTransport(int associationId, ParcelFileDescriptor fd) { - super(associationId, fd); - } - - @Override - public void start() { - new Thread(() -> { - try { - while (!mStopped) { - receiveMessage(); - } - } catch (IOException e) { - if (!mStopped) { - Slog.w(TAG, "Trouble during transport", e); - stop(); - } - } - }).start(); - } - - @Override - public void stop() { - mStopped = true; - - IoUtils.closeQuietly(mRemoteIn); - IoUtils.closeQuietly(mRemoteOut); - } - - @Override - protected void sendMessage(int message, int sequence, @NonNull byte[] data) - throws IOException { - if (DEBUG) { - Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message) - + " sequence " + sequence + " length " + data.length - + " to association " + mAssociationId); - } - - synchronized (mRemoteOut) { - final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH) - .putInt(message) - .putInt(sequence) - .putInt(data.length); - mRemoteOut.write(header.array()); - mRemoteOut.write(data); - mRemoteOut.flush(); - } - } - - private void receiveMessage() throws IOException { - final byte[] headerBytes = new byte[HEADER_LENGTH]; - Streams.readFully(mRemoteIn, headerBytes); - final ByteBuffer header = ByteBuffer.wrap(headerBytes); - final int message = header.getInt(); - final int sequence = header.getInt(); - final int length = header.getInt(); - final byte[] data = new byte[length]; - Streams.readFully(mRemoteIn, data); - - handleMessage(message, sequence, data); - } - } - - private class SecureTransport extends Transport implements SecureChannel.Callback { - private final SecureChannel mSecureChannel; - - private volatile boolean mShouldProcessRequests = false; - - private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100); - - SecureTransport(int associationId, ParcelFileDescriptor fd) { - super(associationId, fd); - mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, mContext); - } - - @Override - public void start() { - mSecureChannel.start(); - } - - @Override - public void stop() { - mSecureChannel.stop(); - mShouldProcessRequests = false; - } - - @Override - public Future<byte[]> requestForResponse(int message, byte[] data) { - // Check if channel is secured and start securing - if (!mShouldProcessRequests) { - Slog.d(TAG, "Establishing secure connection."); - try { - mSecureChannel.establishSecureConnection(); - } catch (Exception e) { - Slog.w(TAG, "Failed to initiate secure channel handshake.", e); - onError(e); - } - } - - return super.requestForResponse(message, data); - } - - @Override - protected void sendMessage(int message, int sequence, @NonNull byte[] data) - throws IOException { - if (DEBUG) { - Slog.d(TAG, "Queueing message 0x" + Integer.toHexString(message) - + " sequence " + sequence + " length " + data.length - + " to association " + mAssociationId); - } - - // Queue up a message to send - mRequestQueue.add(ByteBuffer.allocate(HEADER_LENGTH + data.length) - .putInt(message) - .putInt(sequence) - .putInt(data.length) - .put(data) - .array()); - } - - @Override - public void onSecureConnection() { - mShouldProcessRequests = true; - Slog.d(TAG, "Secure connection established."); - - // TODO: find a better way to handle incoming requests than a dedicated thread. - new Thread(() -> { - try { - while (mShouldProcessRequests) { - byte[] request = mRequestQueue.poll(); - if (request != null) { - mSecureChannel.sendSecureMessage(request); - } - } - } catch (IOException e) { - onError(e); - } - }).start(); - } - - @Override - public void onSecureMessageReceived(byte[] data) { - final ByteBuffer payload = ByteBuffer.wrap(data); - final int message = payload.getInt(); - final int sequence = payload.getInt(); - final int length = payload.getInt(); - final byte[] content = new byte[length]; - payload.get(content); - - try { - handleMessage(message, sequence, content); - } catch (IOException error) { - onError(error); - } - } - - @Override - public void onError(Throwable error) { - mShouldProcessRequests = false; - Slog.e(TAG, error.getMessage(), error); - } - } } diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java new file mode 100644 index 000000000000..7c0c7cf7ac68 --- /dev/null +++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.transport; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.ParcelFileDescriptor; +import android.util.Slog; + +import com.android.server.companion.transport.CompanionTransportManager.Listener; + +import libcore.io.IoUtils; +import libcore.io.Streams; + +import java.io.IOException; +import java.nio.ByteBuffer; + +class RawTransport extends Transport { + private volatile boolean mStopped; + + RawTransport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) { + super(associationId, fd, context, listener); + } + + @Override + public void start() { + new Thread(() -> { + try { + while (!mStopped) { + receiveMessage(); + } + } catch (IOException e) { + if (!mStopped) { + Slog.w(TAG, "Trouble during transport", e); + stop(); + } + } + }).start(); + } + + @Override + public void stop() { + mStopped = true; + + IoUtils.closeQuietly(mRemoteIn); + IoUtils.closeQuietly(mRemoteOut); + } + + @Override + protected void sendMessage(int message, int sequence, @NonNull byte[] data) + throws IOException { + if (DEBUG) { + Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message) + + " sequence " + sequence + " length " + data.length + + " to association " + mAssociationId); + } + + synchronized (mRemoteOut) { + final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH) + .putInt(message) + .putInt(sequence) + .putInt(data.length); + mRemoteOut.write(header.array()); + mRemoteOut.write(data); + mRemoteOut.flush(); + } + } + + private void receiveMessage() throws IOException { + final byte[] headerBytes = new byte[HEADER_LENGTH]; + Streams.readFully(mRemoteIn, headerBytes); + final ByteBuffer header = ByteBuffer.wrap(headerBytes); + final int message = header.getInt(); + final int sequence = header.getInt(); + final int length = header.getInt(); + final byte[] data = new byte[length]; + Streams.readFully(mRemoteIn, data); + + handleMessage(message, sequence, data); + } +} diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java new file mode 100644 index 000000000000..4194130f7e84 --- /dev/null +++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.transport; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.ParcelFileDescriptor; +import android.util.Slog; + +import com.android.server.companion.securechannel.SecureChannel; +import com.android.server.companion.transport.CompanionTransportManager.Listener; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Future; + +class SecureTransport extends Transport implements SecureChannel.Callback { + private final SecureChannel mSecureChannel; + + private volatile boolean mShouldProcessRequests = false; + + private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100); + + SecureTransport(int associationId, + ParcelFileDescriptor fd, + Context context, + Listener listener) { + super(associationId, fd, context, listener); + mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context); + } + + @Override + public void start() { + mSecureChannel.start(); + } + + @Override + public void stop() { + mSecureChannel.stop(); + mShouldProcessRequests = false; + } + + @Override + public Future<byte[]> requestForResponse(int message, byte[] data) { + // Check if channel is secured and start securing + if (!mShouldProcessRequests) { + Slog.d(TAG, "Establishing secure connection."); + try { + mSecureChannel.establishSecureConnection(); + } catch (Exception e) { + Slog.w(TAG, "Failed to initiate secure channel handshake.", e); + onError(e); + } + } + + return super.requestForResponse(message, data); + } + + @Override + protected void sendMessage(int message, int sequence, @NonNull byte[] data) + throws IOException { + if (DEBUG) { + Slog.d(TAG, "Queueing message 0x" + Integer.toHexString(message) + + " sequence " + sequence + " length " + data.length + + " to association " + mAssociationId); + } + + // Queue up a message to send + mRequestQueue.add(ByteBuffer.allocate(HEADER_LENGTH + data.length) + .putInt(message) + .putInt(sequence) + .putInt(data.length) + .put(data) + .array()); + } + + @Override + public void onSecureConnection() { + mShouldProcessRequests = true; + Slog.d(TAG, "Secure connection established."); + + // TODO: find a better way to handle incoming requests than a dedicated thread. + new Thread(() -> { + try { + while (mShouldProcessRequests) { + byte[] request = mRequestQueue.poll(); + if (request != null) { + mSecureChannel.sendSecureMessage(request); + } + } + } catch (IOException e) { + onError(e); + } + }).start(); + } + + @Override + public void onSecureMessageReceived(byte[] data) { + final ByteBuffer payload = ByteBuffer.wrap(data); + final int message = payload.getInt(); + final int sequence = payload.getInt(); + final int length = payload.getInt(); + final byte[] content = new byte[length]; + payload.get(content); + + try { + handleMessage(message, sequence, content); + } catch (IOException error) { + onError(error); + } + } + + @Override + public void onError(Throwable error) { + mShouldProcessRequests = false; + Slog.e(TAG, error.getMessage(), error); + } +} diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java new file mode 100644 index 000000000000..923d4243a34c --- /dev/null +++ b/services/companion/java/com/android/server/companion/transport/Transport.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.transport; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.companion.transport.CompanionTransportManager.Listener; + +import libcore.util.EmptyArray; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; + +abstract class Transport { + protected static final String TAG = "CDM_CompanionTransport"; + protected static final boolean DEBUG = Build.IS_DEBUGGABLE; + + static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN + static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES + + static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC + static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI + + protected static final int HEADER_LENGTH = 12; + + protected final int mAssociationId; + protected final InputStream mRemoteIn; + protected final OutputStream mRemoteOut; + protected final Context mContext; + + private final Listener mListener; + + private static boolean isRequest(int message) { + return (message & 0xFF000000) == 0x63000000; + } + + private static boolean isResponse(int message) { + return (message & 0xFF000000) == 0x33000000; + } + + @GuardedBy("mPendingRequests") + protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests = + new SparseArray<>(); + protected final AtomicInteger mNextSequence = new AtomicInteger(); + + Transport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) { + this.mAssociationId = associationId; + this.mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd); + this.mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + this.mContext = context; + this.mListener = listener; + } + + public abstract void start(); + public abstract void stop(); + + public Future<byte[]> requestForResponse(int message, byte[] data) { + if (DEBUG) Slog.d(TAG, "Requesting for response"); + final int sequence = mNextSequence.incrementAndGet(); + final CompletableFuture<byte[]> pending = new CompletableFuture<>(); + synchronized (mPendingRequests) { + mPendingRequests.put(sequence, pending); + } + + try { + sendMessage(message, sequence, data); + } catch (IOException e) { + synchronized (mPendingRequests) { + mPendingRequests.remove(sequence); + } + pending.completeExceptionally(e); + } + + return pending; + } + + protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data) + throws IOException; + + protected final void handleMessage(int message, int sequence, @NonNull byte[] data) + throws IOException { + if (DEBUG) { + Slog.d(TAG, "Received message 0x" + Integer.toHexString(message) + + " sequence " + sequence + " length " + data.length + + " from association " + mAssociationId); + } + + if (isRequest(message)) { + try { + processRequest(message, sequence, data); + } catch (IOException e) { + Slog.w(TAG, "Failed to respond to 0x" + Integer.toHexString(message), e); + } + } else if (isResponse(message)) { + processResponse(message, sequence, data); + } else { + Slog.w(TAG, "Unknown message 0x" + Integer.toHexString(message)); + } + } + + private void processRequest(int message, int sequence, byte[] data) + throws IOException { + switch (message) { + case MESSAGE_REQUEST_PING: { + sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data); + break; + } + case MESSAGE_REQUEST_PERMISSION_RESTORE: { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) + && !Build.isDebuggable()) { + Slog.w(TAG, "Restoring permissions only supported on watches"); + sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE); + break; + } + try { + mListener.onRequestPermissionRestore(data); + sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE); + } catch (Exception e) { + Slog.w(TAG, "Failed to restore permissions"); + sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE); + } + break; + } + default: { + Slog.w(TAG, "Unknown request 0x" + Integer.toHexString(message)); + sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE); + break; + } + } + } + + private void processResponse(int message, int sequence, byte[] data) { + final CompletableFuture<byte[]> future; + synchronized (mPendingRequests) { + future = mPendingRequests.removeReturnOld(sequence); + } + if (future == null) { + Slog.w(TAG, "Ignoring unknown sequence " + sequence); + return; + } + + switch (message) { + case MESSAGE_RESPONSE_SUCCESS: { + future.complete(data); + break; + } + case MESSAGE_RESPONSE_FAILURE: { + future.completeExceptionally(new RuntimeException("Remote failure")); + break; + } + default: { + Slog.w(TAG, "Ignoring unknown response 0x" + Integer.toHexString(message)); + } + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java index 7804ebf1583d..864fe0f5edc1 100644 --- a/services/companion/java/com/android/server/companion/virtual/SensorController.java +++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java @@ -22,8 +22,11 @@ import android.companion.virtual.sensor.IVirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; +import android.hardware.SensorDirectChannel; import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SharedMemory; import android.util.ArrayMap; import android.util.Slog; @@ -36,6 +39,7 @@ import java.io.PrintWriter; import java.util.Iterator; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** Controls virtual sensors, including their lifecycle and sensor event dispatch. */ public class SensorController { @@ -47,6 +51,8 @@ public class SensorController { private static final int UNKNOWN_ERROR = (-2147483647 - 1); // INT32_MIN value private static final int BAD_VALUE = -22; + private static AtomicInteger sNextDirectChannelHandle = new AtomicInteger(1); + private final Object mLock; private final int mVirtualDeviceId; @GuardedBy("mLock") @@ -57,8 +63,6 @@ public class SensorController { private final SensorManagerInternal mSensorManagerInternal; private final VirtualDeviceManagerInternal mVdmInternal; - - public SensorController(@NonNull Object lock, int virtualDeviceId, @Nullable IVirtualSensorCallback virtualSensorCallback) { mLock = lock; @@ -97,7 +101,7 @@ public class SensorController { throws SensorCreationException { final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId, config.getType(), config.getName(), - config.getVendor() == null ? "" : config.getVendor(), + config.getVendor() == null ? "" : config.getVendor(), config.getFlags(), mRuntimeSensorCallback); if (handle <= 0) { throw new SensorCreationException("Received an invalid virtual sensor handle."); @@ -212,6 +216,66 @@ public class SensorController { } return OK; } + + @Override + public int onDirectChannelCreated(ParcelFileDescriptor fd) { + if (mCallback == null) { + Slog.e(TAG, "No sensor callback for virtual deviceId " + mVirtualDeviceId); + return BAD_VALUE; + } else if (fd == null) { + Slog.e(TAG, "Received invalid ParcelFileDescriptor"); + return BAD_VALUE; + } + final int channelHandle = sNextDirectChannelHandle.getAndIncrement(); + SharedMemory sharedMemory = SharedMemory.fromFileDescriptor(fd); + try { + mCallback.onDirectChannelCreated(channelHandle, sharedMemory); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to call sensor callback: " + e); + return UNKNOWN_ERROR; + } + return channelHandle; + } + + @Override + public void onDirectChannelDestroyed(int channelHandle) { + if (mCallback == null) { + Slog.e(TAG, "No sensor callback for virtual deviceId " + mVirtualDeviceId); + return; + } + try { + mCallback.onDirectChannelDestroyed(channelHandle); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to call sensor callback: " + e); + } + } + + @Override + public int onDirectChannelConfigured(int channelHandle, int sensorHandle, + @SensorDirectChannel.RateLevel int rateLevel) { + if (mCallback == null) { + Slog.e(TAG, "No runtime sensor callback configured."); + return BAD_VALUE; + } + VirtualSensor sensor = mVdmInternal.getVirtualSensor(mVirtualDeviceId, sensorHandle); + if (sensor == null) { + Slog.e(TAG, "No sensor found for deviceId=" + mVirtualDeviceId + + " and sensor handle=" + sensorHandle); + return BAD_VALUE; + } + try { + mCallback.onDirectChannelConfigured(channelHandle, sensor, rateLevel, sensorHandle); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to call sensor callback: " + e); + return UNKNOWN_ERROR; + } + if (rateLevel == SensorDirectChannel.RATE_STOP) { + return OK; + } else { + // Use the sensor handle as a report token, i.e. a unique identifier of the sensor. + return sensorHandle; + } + } } @VisibleForTesting diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index b4dcf43c2e1a..642eaef80c47 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -51,6 +51,10 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.graphics.PointF; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.hardware.input.VirtualDpadConfig; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualKeyboardConfig; @@ -82,6 +86,7 @@ import android.widget.Toast; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.BlockedAppStreamingActivity; +import com.android.server.LocalServices; import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener; import com.android.server.companion.virtual.audio.VirtualAudioController; @@ -100,6 +105,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private static final String TAG = "VirtualDeviceImpl"; + private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS = + DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC + | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT + | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL + | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH + | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; + /** * Timeout until {@link #launchPendingIntent} stops waiting for an activity to be launched. */ @@ -109,21 +122,29 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final Context mContext; private final AssociationInfo mAssociationInfo; + private final VirtualDeviceManagerService mService; private final PendingTrampolineCallback mPendingTrampolineCallback; private final int mOwnerUid; private final int mDeviceId; + // Thou shall not hold the mVirtualDeviceLock over the mInputController calls. + // Holding the lock can lead to lock inversion with GlobalWindowManagerLock. + // 1. After display is created the window manager calls into VDM during construction + // of display specific context to fetch device id corresponding to the display. + // mVirtualDeviceLock will be held while this is done. + // 2. InputController interactions result in calls to DisplayManager (to set IME, + // possibly more indirect calls), and those attempt to lock GlobalWindowManagerLock which + // creates lock inversion. private final InputController mInputController; private final SensorController mSensorController; private final CameraAccessController mCameraAccessController; private VirtualAudioController mVirtualAudioController; - @VisibleForTesting - final ArraySet<Integer> mVirtualDisplayIds = new ArraySet<>(); - private final OnDeviceCloseListener mOnDeviceCloseListener; private final IBinder mAppToken; private final VirtualDeviceParams mParams; - private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>(); + @GuardedBy("mVirtualDeviceLock") + private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>(); private final IVirtualDeviceActivityListener mActivityListener; private final IVirtualDeviceSoundEffectListener mSoundEffectListener; + private final DisplayManagerGlobal mDisplayManager; @GuardedBy("mVirtualDeviceLock") private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>(); @NonNull @@ -174,21 +195,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub }; } - /** - * A mapping from the virtual display ID to its corresponding - * {@link GenericWindowPolicyController}. - */ - private final SparseArray<GenericWindowPolicyController> mWindowPolicyControllers = - new SparseArray<>(); - VirtualDeviceImpl( Context context, AssociationInfo associationInfo, + VirtualDeviceManagerService service, IBinder token, int ownerUid, int deviceId, CameraAccessController cameraAccessController, - OnDeviceCloseListener onDeviceCloseListener, PendingTrampolineCallback pendingTrampolineCallback, IVirtualDeviceActivityListener activityListener, IVirtualDeviceSoundEffectListener soundEffectListener, @@ -197,40 +211,43 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub this( context, associationInfo, + service, token, ownerUid, deviceId, /* inputController= */ null, /* sensorController= */ null, cameraAccessController, - onDeviceCloseListener, pendingTrampolineCallback, activityListener, soundEffectListener, runningAppsChangedCallback, - params); + params, + DisplayManagerGlobal.getInstance()); } @VisibleForTesting VirtualDeviceImpl( Context context, AssociationInfo associationInfo, + VirtualDeviceManagerService service, IBinder token, int ownerUid, int deviceId, InputController inputController, SensorController sensorController, CameraAccessController cameraAccessController, - OnDeviceCloseListener onDeviceCloseListener, PendingTrampolineCallback pendingTrampolineCallback, IVirtualDeviceActivityListener activityListener, IVirtualDeviceSoundEffectListener soundEffectListener, Consumer<ArraySet<Integer>> runningAppsChangedCallback, - VirtualDeviceParams params) { + VirtualDeviceParams params, + DisplayManagerGlobal displayManager) { super(PermissionEnforcer.fromContext(context)); UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid); mContext = context.createContextAsUser(ownerUserHandle, 0); mAssociationInfo = associationInfo; + mService = service; mPendingTrampolineCallback = pendingTrampolineCallback; mActivityListener = activityListener; mSoundEffectListener = soundEffectListener; @@ -239,6 +256,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mDeviceId = deviceId; mAppToken = token; mParams = params; + mDisplayManager = displayManager; if (inputController == null) { mInputController = new InputController( mVirtualDeviceLock, @@ -259,7 +277,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } mCameraAccessController = cameraAccessController; mCameraAccessController.startObservingIfNeeded(); - mOnDeviceCloseListener = onDeviceCloseListener; try { token.linkToDeath(this, 0); } catch (RemoteException e) { @@ -272,7 +289,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub * device. */ int getBaseVirtualDisplayFlags() { - int flags = 0; + int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; } @@ -331,9 +348,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override // Binder call public void launchPendingIntent(int displayId, PendingIntent pendingIntent, ResultReceiver resultReceiver) { - if (!mVirtualDisplayIds.contains(displayId)) { - throw new SecurityException("Display ID " + displayId - + " not found for this virtual device"); + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplays.contains(displayId)) { + throw new SecurityException("Display ID " + displayId + + " not found for this virtual device"); + } } if (pendingIntent.isActivity()) { try { @@ -383,24 +402,34 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close() { super.close_enforcePermission(); + // Remove about-to-be-closed virtual device from the service before butchering it. + mService.removeVirtualDevice(mDeviceId); + + VirtualDisplayWrapper[] virtualDisplaysToBeReleased; synchronized (mVirtualDeviceLock) { - if (!mPerDisplayWakelocks.isEmpty()) { - mPerDisplayWakelocks.forEach((displayId, wakeLock) -> { - Slog.w(TAG, "VirtualDisplay " + displayId + " owned by UID " + mOwnerUid - + " was not properly released"); - wakeLock.release(); - }); - mPerDisplayWakelocks.clear(); - } if (mVirtualAudioController != null) { mVirtualAudioController.stopListening(); mVirtualAudioController = null; } mLocaleList = null; + virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()]; + for (int i = 0; i < mVirtualDisplays.size(); i++) { + virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i); + } + mVirtualDisplays.clear(); mVirtualSensorList = null; mVirtualSensors.clear(); } - mOnDeviceCloseListener.onClose(mDeviceId); + // Destroy the display outside locked section. + for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) { + mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken()); + // The releaseVirtualDisplay call above won't trigger + // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the + // virtual device from the service - we release the other display-tied resources here + // with the guarantee it will be done exactly once. + releaseOwnedVirtualDisplayResources(virtualDisplayWrapper); + } + mAppToken.unlinkToDeath(this, 0); mCameraAccessController.stopObservingIfNeeded(); @@ -429,11 +458,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mVirtualAudioController; } - @VisibleForTesting - SparseArray<GenericWindowPolicyController> getWindowPolicyControllersForTesting() { - return mWindowPolicyControllers; - } - @Override // Binder call @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void onAudioSessionStarting(int displayId, @@ -441,7 +465,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Nullable IAudioConfigChangedCallback configChangedCallback) { super.onAudioSessionStarting_enforcePermission(); synchronized (mVirtualDeviceLock) { - if (!mVirtualDisplayIds.contains(displayId)) { + if (!mVirtualDisplays.contains(displayId)) { throw new SecurityException( "Cannot start audio session for a display not associated with this virtual " + "device"); @@ -449,7 +473,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub if (mVirtualAudioController == null) { mVirtualAudioController = new VirtualAudioController(mContext); - GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId); + GenericWindowPolicyController gwpc = mVirtualDisplays.get( + displayId).getWindowPolicyController(); mVirtualAudioController.startListening(gwpc, routingCallback, configChangedCallback); } @@ -473,7 +498,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) { super.createVirtualDpad_enforcePermission(); synchronized (mVirtualDeviceLock) { - if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) { + if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( "Cannot create a virtual dpad for a display not associated with " + "this virtual device"); @@ -493,7 +518,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) { super.createVirtualKeyboard_enforcePermission(); synchronized (mVirtualDeviceLock) { - if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) { + if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( "Cannot create a virtual keyboard for a display not associated with " + "this virtual device"); @@ -515,7 +540,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) { super.createVirtualMouse_enforcePermission(); synchronized (mVirtualDeviceLock) { - if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) { + if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( "Cannot create a virtual mouse for a display not associated with this " + "virtual device"); @@ -536,7 +561,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @NonNull IBinder deviceToken) { super.createVirtualTouchscreen_enforcePermission(); synchronized (mVirtualDeviceLock) { - if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) { + if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( "Cannot create a virtual touchscreen for a display not associated with " + "this virtual device"); @@ -566,7 +591,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @NonNull IBinder deviceToken) { super.createVirtualNavigationTouchpad_enforcePermission(); synchronized (mVirtualDeviceLock) { - if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) { + if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( "Cannot create a virtual navigation touchpad for a display not associated " + "with this virtual device"); @@ -704,7 +729,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub try { synchronized (mVirtualDeviceLock) { mDefaultShowPointerIcon = showPointerIcon; - for (int displayId : mVirtualDisplayIds) { + for (int i = 0; i < mVirtualDisplays.size(); i++) { + final int displayId = mVirtualDisplays.keyAt(i); mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId); } } @@ -795,8 +821,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub fout.println(" mParams: " + mParams); fout.println(" mVirtualDisplayIds: "); synchronized (mVirtualDeviceLock) { - for (int id : mVirtualDisplayIds) { - fout.println(" " + id); + for (int i = 0; i < mVirtualDisplays.size(); i++) { + fout.println(" " + mVirtualDisplays.keyAt(i)); } fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon); } @@ -804,61 +830,75 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mSensorController.dump(fout); } - GenericWindowPolicyController createWindowPolicyController( + private GenericWindowPolicyController createWindowPolicyController( @NonNull List<String> displayCategories) { - synchronized (mVirtualDeviceLock) { - final GenericWindowPolicyController gwpc = - new GenericWindowPolicyController(FLAG_SECURE, - SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, - getAllowedUserHandles(), - mParams.getAllowedCrossTaskNavigations(), - mParams.getBlockedCrossTaskNavigations(), - mParams.getAllowedActivities(), - mParams.getBlockedActivities(), - mParams.getDefaultActivityPolicy(), - createListenerAdapter(), - this::onEnteringPipBlocked, - this::onActivityBlocked, - this::onSecureWindowShown, - this::shouldInterceptIntent, - displayCategories, - mParams.getDefaultRecentsPolicy()); - gwpc.registerRunningAppsChangedListener(/* listener= */ this); - return gwpc; - } + final GenericWindowPolicyController gwpc = + new GenericWindowPolicyController(FLAG_SECURE, + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, + getAllowedUserHandles(), + mParams.getAllowedCrossTaskNavigations(), + mParams.getBlockedCrossTaskNavigations(), + mParams.getAllowedActivities(), + mParams.getBlockedActivities(), + mParams.getDefaultActivityPolicy(), + createListenerAdapter(), + this::onEnteringPipBlocked, + this::onActivityBlocked, + this::onSecureWindowShown, + this::shouldInterceptIntent, + displayCategories, + mParams.getDefaultRecentsPolicy()); + gwpc.registerRunningAppsChangedListener(/* listener= */ this); + return gwpc; } - void onVirtualDisplayCreatedLocked(GenericWindowPolicyController gwpc, int displayId) { + int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig, + @NonNull IVirtualDisplayCallback callback, String packageName) { + GenericWindowPolicyController gwpc = createWindowPolicyController( + virtualDisplayConfig.getDisplayCategories()); + DisplayManagerInternal displayManager = LocalServices.getService( + DisplayManagerInternal.class); + int displayId; + displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback, + this, gwpc, packageName); + gwpc.setDisplayId(displayId); + synchronized (mVirtualDeviceLock) { - if (displayId == Display.INVALID_DISPLAY) { - return; - } - if (mVirtualDisplayIds.contains(displayId)) { + if (mVirtualDisplays.contains(displayId)) { + gwpc.unregisterRunningAppsChangedListener(this); throw new IllegalStateException( "Virtual device already has a virtual display with ID " + displayId); } - mVirtualDisplayIds.add(displayId); - gwpc.setDisplayId(displayId); - mWindowPolicyControllers.put(displayId, gwpc); + PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId); + mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock)); + } + final long token = Binder.clearCallingIdentity(); + try { mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId); mInputController.setPointerAcceleration(1f, displayId); mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false, displayId); mInputController.setLocalIme(displayId); + } finally { + Binder.restoreCallingIdentity(token); + } + return displayId; + } - if (mPerDisplayWakelocks.containsKey(displayId)) { - Slog.e(TAG, "Not creating wakelock for displayId " + displayId); - return; - } + private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) { + final long token = Binder.clearCallingIdentity(); + try { PowerManager powerManager = mContext.getSystemService(PowerManager.class); PowerManager.WakeLock wakeLock = powerManager.newWakeLock( PowerManager.SCREEN_BRIGHT_WAKE_LOCK, TAG + ":" + displayId, displayId); - mPerDisplayWakelocks.put(displayId, wakeLock); wakeLock.acquire(); + return wakeLock; + } finally { + Binder.restoreCallingIdentity(token); } } @@ -872,8 +912,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } private void onSecureWindowShown(int displayId, int uid) { - if (!mVirtualDisplayIds.contains(displayId)) { - return; + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplays.contains(displayId)) { + return; + } } // If a virtual display isn't secure, the screen can't be captured. Show a warning toast @@ -888,55 +930,102 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private ArraySet<UserHandle> getAllowedUserHandles() { ArraySet<UserHandle> result = new ArraySet<>(); - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - UserManager userManager = mContext.getSystemService(UserManager.class); - for (UserHandle profile : userManager.getAllProfiles()) { - int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier()); - if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED - || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) { - result.add(profile); - } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) { - if (mParams.getUsersWithMatchingAccounts().contains(profile)) { + final long token = Binder.clearCallingIdentity(); + try { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + UserManager userManager = mContext.getSystemService(UserManager.class); + for (UserHandle profile : userManager.getAllProfiles()) { + int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy( + profile.getIdentifier()); + if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED + || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) { result.add(profile); + } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) { + if (mParams.getUsersWithMatchingAccounts().contains(profile)) { + result.add(profile); + } } } + } finally { + Binder.restoreCallingIdentity(token); } return result; } - void onVirtualDisplayRemovedLocked(int displayId) { + + void onVirtualDisplayRemoved(int displayId) { + /* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released + * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()). + * At this point, the display is already released, but we still need to release the + * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding + * WindowPolicyController. + * + * Note that when the display is destroyed during VirtualDeviceImpl.close() call, + * this callback won't be invoked because the display is removed from + * VirtualDeviceManagerService before any resources are released. + */ + VirtualDisplayWrapper virtualDisplayWrapper; synchronized (mVirtualDeviceLock) { - if (!mVirtualDisplayIds.contains(displayId)) { - throw new IllegalStateException( - "Virtual device doesn't have a virtual display with ID " + displayId); - } - PowerManager.WakeLock wakeLock = mPerDisplayWakelocks.get(displayId); - if (wakeLock != null) { - wakeLock.release(); - mPerDisplayWakelocks.remove(displayId); - } - GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId); - if (gwpc != null) { - gwpc.unregisterRunningAppsChangedListener(/* listener= */ this); - } - mVirtualDisplayIds.remove(displayId); - mWindowPolicyControllers.remove(displayId); + virtualDisplayWrapper = mVirtualDisplays.removeReturnOld(displayId); + } + + if (virtualDisplayWrapper == null) { + throw new IllegalStateException( + "Virtual device doesn't have a virtual display with ID " + displayId); } + + releaseOwnedVirtualDisplayResources(virtualDisplayWrapper); + + } + + /** + * Release resources tied to virtual display owned by this VirtualDevice instance. + * + * Note that this method won't release the virtual display itself. + * + * @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for. + */ + private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) { + virtualDisplayWrapper.getWakeLock().release(); + virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener( + this); } int getOwnerUid() { return mOwnerUid; } + ArraySet<Integer> getDisplayIds() { + synchronized (mVirtualDeviceLock) { + final int size = mVirtualDisplays.size(); + ArraySet<Integer> arraySet = new ArraySet<>(size); + for (int i = 0; i < size; i++) { + arraySet.append(mVirtualDisplays.keyAt(i)); + } + return arraySet; + } + } + + @VisibleForTesting + GenericWindowPolicyController getDisplayWindowPolicyControllerForTest(int displayId) { + VirtualDisplayWrapper virtualDisplayWrapper; + synchronized (mVirtualDeviceLock) { + virtualDisplayWrapper = mVirtualDisplays.get(displayId); + } + return virtualDisplayWrapper != null ? virtualDisplayWrapper.getWindowPolicyController() + : null; + } + /** * Returns true if an app with the given {@code uid} is currently running on this virtual * device. */ boolean isAppRunningOnVirtualDevice(int uid) { - final int size = mWindowPolicyControllers.size(); - for (int i = 0; i < size; i++) { - if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) { - return true; + synchronized (mVirtualDeviceLock) { + for (int i = 0; i < mVirtualDisplays.size(); i++) { + if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) { + return true; + } } } return false; @@ -957,11 +1046,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub Looper looper) { synchronized (mVirtualDeviceLock) { DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); - final int size = mWindowPolicyControllers.size(); - for (int i = 0; i < size; i++) { - if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) { - int displayId = mWindowPolicyControllers.keyAt(i); - Display display = displayManager.getDisplay(displayId); + for (int i = 0; i < mVirtualDisplays.size(); i++) { + if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) { + Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i)); if (display != null && display.isValid()) { Toast.makeText(mContext.createDisplayContext(display), looper, text, duration).show(); @@ -972,7 +1059,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } boolean isDisplayOwnedByVirtualDevice(int displayId) { - return mVirtualDisplayIds.contains(displayId); + synchronized (mVirtualDeviceLock) { + return mVirtualDisplays.contains(displayId); + } } void onEnteringPipBlocked(int uid) { @@ -1016,10 +1105,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } - interface OnDeviceCloseListener { - void onClose(int deviceId); - } - interface PendingTrampolineCallback { /** * Called when the callback should start waiting for the given pending trampoline. @@ -1073,4 +1158,31 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub + ", displayId=" + mDisplayId + "}"; } } + + /** Data class wrapping resources tied to single virtual display. */ + private static final class VirtualDisplayWrapper { + private final IVirtualDisplayCallback mToken; + private final GenericWindowPolicyController mWindowPolicyController; + private final PowerManager.WakeLock mWakeLock; + + VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token, + @NonNull GenericWindowPolicyController windowPolicyController, + @NonNull PowerManager.WakeLock wakeLock) { + mToken = Objects.requireNonNull(token); + mWindowPolicyController = Objects.requireNonNull(windowPolicyController); + mWakeLock = Objects.requireNonNull(wakeLock); + } + + GenericWindowPolicyController getWindowPolicyController() { + return mWindowPolicyController; + } + + PowerManager.WakeLock getWakeLock() { + return mWakeLock; + } + + IVirtualDisplayCallback getToken() { + return mToken; + } + } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 9bb05a6bfe5d..3b1983f55fb6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -37,7 +37,6 @@ import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.sensor.VirtualSensor; import android.content.Context; import android.content.Intent; -import android.hardware.display.DisplayManagerInternal; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; import android.os.Binder; @@ -85,7 +84,7 @@ public class VirtualDeviceManagerService extends SystemService { private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler); private static AtomicInteger sNextUniqueIndex = new AtomicInteger( - VirtualDeviceManager.DEVICE_ID_DEFAULT + 1); + Context.DEVICE_ID_DEFAULT + 1); /** * Mapping from device IDs to virtual devices. @@ -108,26 +107,26 @@ public class VirtualDeviceManagerService extends SystemService { private final ActivityInterceptorCallback mActivityInterceptorCallback = new ActivityInterceptorCallback() { - @Nullable - @Override - public ActivityInterceptResult onInterceptActivityLaunch(@NonNull - ActivityInterceptorInfo info) { - if (info.getCallingPackage() == null) { - return null; - } - PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage()); - if (pt == null) { - return null; - } - pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null); - ActivityOptions options = info.getCheckedOptions(); - if (options == null) { - options = ActivityOptions.makeBasic(); - } - return new ActivityInterceptResult( - info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId)); - } - }; + @Nullable + @Override + public ActivityInterceptResult onInterceptActivityLaunch(@NonNull + ActivityInterceptorInfo info) { + if (info.getCallingPackage() == null) { + return null; + } + PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage()); + if (pt == null) { + return null; + } + pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null); + ActivityOptions options = info.getCheckedOptions(); + if (options == null) { + options = ActivityOptions.makeBasic(); + } + return new ActivityInterceptResult( + info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId)); + } + }; @Override public void onStart() { @@ -146,8 +145,8 @@ public class VirtualDeviceManagerService extends SystemService { CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName(); mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid, getContext().getString( - com.android.internal.R.string.vdm_camera_access_denied, - deviceName), + com.android.internal.R.string.vdm_camera_access_denied, + deviceName), Toast.LENGTH_LONG, Looper.myLooper()); } } @@ -193,34 +192,46 @@ public class VirtualDeviceManagerService extends SystemService { } } - @VisibleForTesting void removeVirtualDevice(int deviceId) { synchronized (mVirtualDeviceManagerLock) { mAppsOnVirtualDevices.remove(deviceId); mVirtualDevices.remove(deviceId); } + + Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED); + i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId); + i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + final long identity = Binder.clearCallingIdentity(); + try { + getContext().sendBroadcastAsUser(i, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(identity); + } } class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub { private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback = new VirtualDeviceImpl.PendingTrampolineCallback() { - @Override - public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) { - PendingTrampoline existing = mPendingTrampolines.put( - pendingTrampoline.mPendingIntent.getCreatorPackage(), - pendingTrampoline); - if (existing != null) { - existing.mResultReceiver.send( - VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null); - } - } + @Override + public void startWaitingForPendingTrampoline( + PendingTrampoline pendingTrampoline) { + PendingTrampoline existing = mPendingTrampolines.put( + pendingTrampoline.mPendingIntent.getCreatorPackage(), + pendingTrampoline); + if (existing != null) { + existing.mResultReceiver.send( + VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null); + } + } - @Override - public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) { - mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage()); - } - }; + @Override + public void stopWaitingForPendingTrampoline( + PendingTrampoline pendingTrampoline) { + mPendingTrampolines.remove( + pendingTrampoline.mPendingIntent.getCreatorPackage()); + } + }; @Override // Binder call public IVirtualDevice createVirtualDevice( @@ -251,8 +262,9 @@ public class VirtualDeviceManagerService extends SystemService { final Consumer<ArraySet<Integer>> runningAppsChangedCallback = runningUids -> notifyRunningAppsChanged(deviceId, runningUids); VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), - associationInfo, token, callingUid, deviceId, cameraAccessController, - this::onDeviceClosed, mPendingTrampolineCallback, activityListener, + associationInfo, VirtualDeviceManagerService.this, token, callingUid, + deviceId, cameraAccessController, + mPendingTrampolineCallback, activityListener, soundEffectListener, runningAppsChangedCallback, params); mVirtualDevices.put(deviceId, virtualDevice); return virtualDevice; @@ -281,26 +293,9 @@ public class VirtualDeviceManagerService extends SystemService { "uid " + callingUid + " is not the owner of the supplied VirtualDevice"); } - GenericWindowPolicyController gwpc; - final long token = Binder.clearCallingIdentity(); - try { - gwpc = virtualDeviceImpl.createWindowPolicyController( - virtualDisplayConfig.getDisplayCategories()); - } finally { - Binder.restoreCallingIdentity(token); - } - - DisplayManagerInternal displayManager = getLocalService( - DisplayManagerInternal.class); - int displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback, - virtualDevice, gwpc, packageName); - final long tokenTwo = Binder.clearCallingIdentity(); - try { - virtualDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, displayId); - } finally { - Binder.restoreCallingIdentity(tokenTwo); - } + int displayId = virtualDeviceImpl.createVirtualDisplay(virtualDisplayConfig, callback, + packageName); mLocalService.onVirtualDisplayCreated(displayId); return displayId; } @@ -332,7 +327,7 @@ public class VirtualDeviceManagerService extends SystemService { @Override // Binder call public int getDeviceIdForDisplayId(int displayId) { if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) { - return VirtualDeviceManager.DEVICE_ID_DEFAULT; + return Context.DEVICE_ID_DEFAULT; } synchronized (mVirtualDeviceManagerLock) { for (int i = 0; i < mVirtualDevices.size(); i++) { @@ -342,7 +337,7 @@ public class VirtualDeviceManagerService extends SystemService { } } } - return VirtualDeviceManager.DEVICE_ID_DEFAULT; + return Context.DEVICE_ID_DEFAULT; } // Binder call @@ -412,19 +407,6 @@ public class VirtualDeviceManagerService extends SystemService { return null; } - private void onDeviceClosed(int deviceId) { - removeVirtualDevice(deviceId); - Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED); - i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId); - i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - final long identity = Binder.clearCallingIdentity(); - try { - getContext().sendBroadcastAsUser(i, UserHandle.ALL); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -512,9 +494,14 @@ public class VirtualDeviceManagerService extends SystemService { @Override public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) { final VirtualDisplayListener[] listeners; + VirtualDeviceImpl virtualDeviceImpl; synchronized (mVirtualDeviceManagerLock) { - ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId); listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]); + virtualDeviceImpl = mVirtualDevices.get( + ((VirtualDeviceImpl) virtualDevice).getDeviceId()); + } + if (virtualDeviceImpl != null) { + virtualDeviceImpl.onVirtualDisplayRemoved(displayId); } mHandler.post(() -> { for (VirtualDisplayListener listener : listeners) { @@ -599,16 +586,11 @@ public class VirtualDeviceManagerService extends SystemService { @Override public @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId) { + VirtualDeviceImpl virtualDevice; synchronized (mVirtualDeviceManagerLock) { - int size = mVirtualDevices.size(); - for (int i = 0; i < size; i++) { - VirtualDeviceImpl device = mVirtualDevices.valueAt(i); - if (device.getDeviceId() == deviceId) { - return new ArraySet<>(device.mVirtualDisplayIds); - } - } + virtualDevice = mVirtualDevices.get(deviceId); } - return new ArraySet<>(); + return virtualDevice == null ? new ArraySet<>() : virtualDevice.getDisplayIds(); } @Override diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 92e322fa2fa6..e9a7f205c519 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -192,20 +192,21 @@ public final class BatteryService extends SystemService { private ArrayDeque<Bundle> mBatteryLevelsEventQueue; private long mLastBatteryLevelChangedSentMs; - private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter( - new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).setDeferUntilActive(true) + private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic() + .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) + .setDeferUntilActive(true) .toBundle(); - private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter( - new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).setDeferUntilActive(true) + /** Used for both connected/disconnected, so match using key */ + private Bundle mPowerOptions = BroadcastOptions.makeBasic() + .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) + .setDeliveryGroupMatchingKey("android", Intent.ACTION_POWER_CONNECTED) + .setDeferUntilActive(true) .toBundle(); - private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter( - new IntentFilter(Intent.ACTION_POWER_CONNECTED)).setDeferUntilActive(true) - .toBundle(); - private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter( - new IntentFilter(Intent.ACTION_BATTERY_OKAY)).setDeferUntilActive(true) - .toBundle(); - private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter( - new IntentFilter(Intent.ACTION_BATTERY_LOW)).setDeferUntilActive(true) + /** Used for both low/okay, so match using key */ + private Bundle mBatteryOptions = BroadcastOptions.makeBasic() + .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) + .setDeliveryGroupMatchingKey("android", Intent.ACTION_BATTERY_OKAY) + .setDeferUntilActive(true) .toBundle(); private MetricsLogger mMetricsLogger; @@ -636,7 +637,7 @@ public final class BatteryService extends SystemService { @Override public void run() { mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null, - mPowerConnectedOptions); + mPowerOptions); } }); } @@ -648,7 +649,7 @@ public final class BatteryService extends SystemService { @Override public void run() { mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null, - mPowerDisconnectedOptions); + mPowerOptions); } }); } @@ -662,7 +663,7 @@ public final class BatteryService extends SystemService { @Override public void run() { mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null, - mBatteryLowOptions); + mBatteryOptions); } }); } else if (mSentLowBatteryBroadcast && @@ -675,7 +676,7 @@ public final class BatteryService extends SystemService { @Override public void run() { mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null, - mBatteryOkayOptions); + mBatteryOptions); } }); } @@ -1210,6 +1211,11 @@ public final class BatteryService extends SystemService { } private final class Led { + // must match: config_notificationsBatteryLowBehavior in config.xml + static final int LOW_BATTERY_BEHAVIOR_DEFAULT = 0; + static final int LOW_BATTERY_BEHAVIOR_SOLID = 1; + static final int LOW_BATTERY_BEHAVIOR_FLASHING = 2; + private final LogicalLight mBatteryLight; private final int mBatteryLowARGB; @@ -1217,6 +1223,7 @@ public final class BatteryService extends SystemService { private final int mBatteryFullARGB; private final int mBatteryLedOn; private final int mBatteryLedOff; + private final int mBatteryLowBehavior; public Led(Context context, LightsManager lights) { mBatteryLight = lights.getLight(LightsManager.LIGHT_ID_BATTERY); @@ -1233,6 +1240,8 @@ public final class BatteryService extends SystemService { com.android.internal.R.integer.config_notificationsBatteryLedOff); mBatteryNearlyFullLevel = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryNearlyFullLevel); + mBatteryLowBehavior = context.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLowBehavior); } /** @@ -1245,13 +1254,26 @@ public final class BatteryService extends SystemService { final int level = mHealthInfo.batteryLevel; final int status = mHealthInfo.batteryStatus; if (level < mLowBatteryWarningLevel) { - if (status == BatteryManager.BATTERY_STATUS_CHARGING) { - // Solid red when battery is charging - mBatteryLight.setColor(mBatteryLowARGB); - } else { - // Flash red when battery is low and not charging - mBatteryLight.setFlashing(mBatteryLowARGB, LogicalLight.LIGHT_FLASH_TIMED, - mBatteryLedOn, mBatteryLedOff); + switch (mBatteryLowBehavior) { + case LOW_BATTERY_BEHAVIOR_SOLID: + // Solid red when low battery + mBatteryLight.setColor(mBatteryLowARGB); + break; + case LOW_BATTERY_BEHAVIOR_FLASHING: + // Flash red when battery is low and not charging + mBatteryLight.setFlashing(mBatteryLowARGB, LogicalLight.LIGHT_FLASH_TIMED, + mBatteryLedOn, mBatteryLedOff); + break; + default: + if (status == BatteryManager.BATTERY_STATUS_CHARGING) { + // Solid red when battery is charging + mBatteryLight.setColor(mBatteryLowARGB); + } else { + // Flash red when battery is low and not charging + mBatteryLight.setFlashing(mBatteryLowARGB, + LogicalLight.LIGHT_FLASH_TIMED, mBatteryLedOn, mBatteryLedOff); + } + break; } } else if (status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL) { diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index 725ea5c1b3dd..19e5cb142cfd 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -79,6 +79,7 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import java.util.zip.GZIPOutputStream; @@ -105,6 +106,10 @@ public final class DropBoxManagerService extends SystemService { // Size beyond which to force-compress newly added entries. private static final long COMPRESS_THRESHOLD_BYTES = 16_384; + // Tags that we should drop by default. + private static final List<String> DISABLED_BY_DEFAULT_TAGS = + List.of("data_app_wtf", "system_app_wtf", "system_server_wtf"); + // TODO: This implementation currently uses one file per entry, which is // inefficient for smallish entries -- consider using a single queue file // per tag (or even globally) instead. @@ -549,8 +554,13 @@ public final class DropBoxManagerService extends SystemService { public boolean isTagEnabled(String tag) { final long token = Binder.clearCallingIdentity(); try { - return !"disabled".equals(Settings.Global.getString( + if (DISABLED_BY_DEFAULT_TAGS.contains(tag)) { + return "enabled".equals(Settings.Global.getString( + mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag)); + } else { + return !"disabled".equals(Settings.Global.getString( mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag)); + } } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index 3f1ad3ae0587..2a46d862b991 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -16,18 +16,12 @@ package com.android.server; -import static android.content.IntentFilter.BLOCK_NULL_ACTION_INTENTS; - -import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; -import android.os.Binder; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.FastImmutableArraySet; @@ -40,7 +34,6 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.util.FastPrintWriter; -import com.android.server.am.ActivityManagerUtils; import com.android.server.pm.Computer; import com.android.server.pm.snapshot.PackageDataSnapshot; @@ -88,7 +81,7 @@ public abstract class IntentResolver<F, R extends Object> { * Returns whether an intent matches the IntentFilter with a pre-resolved type. */ public static boolean intentMatchesFilter( - IntentFilter filter, Intent intent, String resolvedType, boolean blockNullAction) { + IntentFilter filter, Intent intent, String resolvedType) { final boolean debug = localLOGV || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); @@ -102,8 +95,7 @@ public abstract class IntentResolver<F, R extends Object> { } final int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(), - intent.getData(), intent.getCategories(), TAG, /* supportWildcards */ false, - blockNullAction, null, null); + intent.getData(), intent.getCategories(), TAG); if (match >= 0) { if (debug) { @@ -358,32 +350,14 @@ public abstract class IntentResolver<F, R extends Object> { return Collections.unmodifiableSet(mFilters); } - private boolean blockNullAction(Computer computer, Intent intent, - String resolvedType, int callingUid, boolean debug) { - if (intent.getAction() == null) { - final boolean blockNullAction = UserHandle.isCore(callingUid) - || computer.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS, callingUid); - ActivityManagerUtils.logUnsafeIntentEvent( - UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH, - callingUid, intent, resolvedType, blockNullAction); - if (blockNullAction) { - if (debug) Slog.v(TAG, "Skip matching filters: action is null"); - return true; - } - } - return false; - } - public List<R> queryIntentFromList(@NonNull Computer computer, Intent intent, - String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut, - int callingUid, @UserIdInt int userId, long customFlags) { + String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut, int userId, + long customFlags) { ArrayList<R> resultList = new ArrayList<R>(); final boolean debug = localLOGV || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); - if (blockNullAction(computer, intent, resolvedType, callingUid, debug)) return resultList; - FastImmutableArraySet<String> categories = getFastIntentCategories(intent); final String scheme = intent.getScheme(); int N = listCut.size(); @@ -391,26 +365,18 @@ public abstract class IntentResolver<F, R extends Object> { buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType, scheme, listCut.get(i), resultList, userId, customFlags); } - filterResults(computer, intent, resultList); + filterResults(resultList); sortResults(resultList); return resultList; } - public final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent, - String resolvedType, boolean defaultOnly, @UserIdInt int userId) { - return queryIntent(snapshot, intent, resolvedType, defaultOnly, - Binder.getCallingUid(), userId, 0); - } - public List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent, - String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId) { - return queryIntent(snapshot, intent, resolvedType, defaultOnly, callingUid, userId, 0); + String resolvedType, boolean defaultOnly, @UserIdInt int userId) { + return queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, 0); } protected final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent, - String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId, - long customFlags) { - final Computer computer = (Computer) snapshot; + String resolvedType, boolean defaultOnly, @UserIdInt int userId, long customFlags) { String scheme = intent.getScheme(); ArrayList<R> finalList = new ArrayList<R>(); @@ -422,8 +388,6 @@ public abstract class IntentResolver<F, R extends Object> { TAG, "Resolving type=" + resolvedType + " scheme=" + scheme + " defaultOnly=" + defaultOnly + " userId=" + userId + " of " + intent); - if (blockNullAction(computer, intent, resolvedType, callingUid, debug)) return finalList; - F[] firstTypeCut = null; F[] secondTypeCut = null; F[] thirdTypeCut = null; @@ -484,6 +448,7 @@ public abstract class IntentResolver<F, R extends Object> { } FastImmutableArraySet<String> categories = getFastIntentCategories(intent); + Computer computer = (Computer) snapshot; if (firstTypeCut != null) { buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType, scheme, firstTypeCut, finalList, userId, customFlags); @@ -500,7 +465,7 @@ public abstract class IntentResolver<F, R extends Object> { buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType, scheme, schemeCut, finalList, userId, customFlags); } - filterResults(computer, intent, finalList); + filterResults(finalList); sortResults(finalList); if (debug) { @@ -569,8 +534,7 @@ public abstract class IntentResolver<F, R extends Object> { /** * Apply filtering to the results. This happens before the results are sorted. */ - protected void filterResults(@NonNull Computer computer, @NonNull Intent intent, - List<R> results) { + protected void filterResults(List<R> results) { } protected void dumpFilter(PrintWriter out, String prefix, F filter) { @@ -802,11 +766,7 @@ public abstract class IntentResolver<F, R extends Object> { continue; } - match = intentFilter.match(action, resolvedType, scheme, data, categories, TAG, - false /*supportWildcards*/, - false /*blockNullAction: already handled*/, - null /*ignoreActions*/, - null /*extras*/); + match = intentFilter.match(action, resolvedType, scheme, data, categories, TAG); if (match >= 0) { if (debug) Slog.v(TAG, " Filter matched! match=0x" + Integer.toHexString(match) + " hasDefault=" diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index c16314b6a117..225afea3d1b7 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -38,6 +38,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationManager; @@ -195,6 +196,9 @@ public class AccountManagerService private final IAccountAuthenticatorCache mAuthenticatorCache; private static final String PRE_N_DATABASE_NAME = "accounts.db"; private static final Intent ACCOUNTS_CHANGED_INTENT; + private static final Bundle ACCOUNTS_CHANGED_OPTIONS = new BroadcastOptions() + .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) + .toBundle(); private static final int SIGNATURE_CHECK_MISMATCH = 0; private static final int SIGNATURE_CHECK_MATCH = 1; @@ -1075,7 +1079,8 @@ public class AccountManagerService Log.i(TAG, "the accountType= " + (accountType == null ? "" : accountType) + " changed with useCase=" + useCase + " for userId=" + userId + ", sending broadcast of " + ACCOUNTS_CHANGED_INTENT.getAction()); - mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId)); + mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId), + null /* receiverPermission */, ACCOUNTS_CHANGED_OPTIONS); } private void sendAccountRemovedBroadcast( diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 70304c55067e..c1850bda609b 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -7822,7 +7822,7 @@ public final class ActiveServices { final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null ? r.mRecentCallerApplicationInfo.targetSdkVersion : 0; - // TODO(short-service): Log BFSL too. + // TODO(short-service): Log the UID capabilities (for BFSL) too, and also the procstate? FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortInstanceName, @@ -7872,7 +7872,8 @@ public final class ActiveServices { r.mFgsNotificationShown ? 1 : 0, durationMs, r.mStartForegroundCount, - fgsStopReasonToString(fgsStopReason)); + fgsStopReasonToString(fgsStopReason), + r.foregroundServiceType); } private void updateNumForegroundServicesLocked() { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 74f799060b46..a8054deff8db 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1128,19 +1128,6 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - protected void filterResults(@NonNull Computer computer, - @NonNull Intent intent, List<BroadcastFilter> results) { - if (intent.getAction() != null) return; - // When the resolved component is targeting U+, block null action intents - for (int i = results.size() - 1; i >= 0; --i) { - if (computer.isChangeEnabled( - IntentFilter.BLOCK_NULL_ACTION_INTENTS, results.get(i).owningUid)) { - results.remove(i); - } - } - } - - @Override protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) { return input; } @@ -3537,7 +3524,7 @@ public class ActivityManagerService extends IActivityManager.Stub // We'll take the stack crawls of just the top apps using CPU. final int workingStatsNumber = processCpuTracker.countWorkingStats(); - for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) { + for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) { ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); if (lastPids.indexOfKey(stats.pid) >= 0) { if (DEBUG_ANR) { @@ -7015,36 +7002,6 @@ public class ActivityManagerService extends IActivityManager.Stub } /** - * Allows apps to retrieve the MIME type of a URI. - * If an app is in the same user as the ContentProvider, or if it is allowed to interact across - * users, then it does not need permission to access the ContentProvider. - * Either, it needs cross-user uri grants. - * - * CTS tests for this functionality can be run with "runtest cts-appsecurity". - * - * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/ - * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java - * - * @deprecated -- use getProviderMimeTypeAsync. - */ - @Deprecated - @Override - public String getProviderMimeType(Uri uri, int userId) { - return mCpHelper.getProviderMimeType(uri, userId); - } - - /** - * Allows apps to retrieve the MIME type of a URI. - * If an app is in the same user as the ContentProvider, or if it is allowed to interact across - * users, then it does not need permission to access the ContentProvider. - * Either way, it needs cross-user uri grants. - */ - @Override - public void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) { - mCpHelper.getProviderMimeTypeAsync(uri, userId, resultCallback); - } - - /** * Filters calls to getType based on permission. If the caller has required permission, * then it returns the contentProvider#getType. * Else, it returns the contentProvider#getTypeAnonymous, which does not @@ -13978,19 +13935,11 @@ public class ActivityManagerService extends IActivityManager.Stub (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) { continue; } - - final boolean blockNullAction = mPlatformCompat.isChangeEnabledInternal( - IntentFilter.BLOCK_NULL_ACTION_INTENTS, callerApp.info); // If intent has scheme "content", it will need to access // provider that needs to lock mProviderMap in ActivityThread // and also it may need to wait application response, so we // cannot lock ActivityManagerService here. - if (filter.match(intent.getAction(), intent.resolveType(resolver), - intent.getScheme(), intent.getData(), intent.getCategories(), TAG, - false /* supportWildcards */, - blockNullAction, - null /* ignoreActions */, - intent.getExtras()) >= 0) { + if (filter.match(resolver, intent, true, TAG) >= 0) { if (allSticky == null) { allSticky = new ArrayList<Intent>(); } @@ -15009,7 +14958,7 @@ public class ActivityManagerService extends IActivityManager.Stub } List<BroadcastFilter> registeredReceiversForUser = mReceiverResolver.queryIntent(snapshot, intent, - resolvedType, false /*defaultOnly*/, callingUid, users[i]); + resolvedType, false /*defaultOnly*/, users[i]); if (registeredReceivers == null) { registeredReceivers = registeredReceiversForUser; } else if (registeredReceiversForUser != null) { @@ -15018,7 +14967,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } else { registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent, - resolvedType, false /*defaultOnly*/, callingUid, userId); + resolvedType, false /*defaultOnly*/, userId); } } BroadcastQueue.traceEnd(cookie); @@ -16353,6 +16302,7 @@ public class ActivityManagerService extends IActivityManager.Stub // TODO(b/111541062): This method is only used for updating OOM adjustments. We need to update // the logic there and in mBatteryStatsService to make them aware of multiple resumed activities + @Nullable ProcessRecord getTopApp() { final WindowProcessController wpc = mAtmInternal != null ? mAtmInternal.getTopApp() : null; final ProcessRecord r = wpc != null ? (ProcessRecord) wpc.mOwner : null; @@ -18208,8 +18158,9 @@ public class ActivityManagerService extends IActivityManager.Stub bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(), TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, PowerExemptionManager.REASON_LOCALE_CHANGED, ""); - bOptions.setRemoveMatchingFilter( - new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); + bOptions.setDeliveryGroupPolicy( + BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); + bOptions.setDeferUntilActive(true); broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index aa9d4ccc61ab..4c1835eb80f8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -146,6 +146,7 @@ import java.util.Locale; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; @@ -595,10 +596,14 @@ final class ActivityManagerShellCommand extends ShellCommand { return 1; } - String mimeType = intent.getType(); - if (mimeType == null && intent.getData() != null + AtomicReference<String> mimeType = new AtomicReference<>(intent.getType()); + + if (mimeType.get() == null && intent.getData() != null && "content".equals(intent.getData().getScheme())) { - mimeType = mInterface.getProviderMimeType(intent.getData(), mUserId); + mInterface.getMimeTypeFilterAsync(intent.getData(), mUserId, + new RemoteCallback(result -> { + mimeType.set(result.getPairValue()); + })); } do { @@ -611,8 +616,8 @@ final class ActivityManagerShellCommand extends ShellCommand { int userIdForQuery = mInternal.mUserController.handleIncomingUser( Binder.getCallingPid(), Binder.getCallingUid(), mUserId, false, ALLOW_NON_FULL, "ActivityManagerShellCommand", null); - List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0, - userIdForQuery).getList(); + List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType.get(), + 0, userIdForQuery).getList(); if (activities == null || activities.size() <= 0) { getErrPrintWriter().println("Error: Intent does not match any activities: " + intent); @@ -708,12 +713,12 @@ final class ActivityManagerShellCommand extends ShellCommand { } if (mWaitOption) { result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent, - mimeType, null, null, 0, mStartFlags, profilerInfo, + mimeType.get(), null, null, 0, mStartFlags, profilerInfo, options != null ? options.toBundle() : null, mUserId); res = result.result; } else { res = mInternal.startActivityAsUserWithFeature(null, SHELL_PACKAGE_NAME, null, - intent, mimeType, null, null, 0, mStartFlags, profilerInfo, + intent, mimeType.get(), null, null, 0, mStartFlags, profilerInfo, options != null ? options.toBundle() : null, mUserId); } final long endTime = SystemClock.uptimeMillis(); @@ -3531,9 +3536,14 @@ final class ActivityManagerShellCommand extends ShellCommand { if (foregroundActivities) { try { int prcState = mIam.getUidProcessState(uid, "android"); - int topPid = mInternal.getTopApp().getPid(); - if (prcState == ProcessStateEnum.TOP && topPid == pid) { - mPw.println("New foreground process: " + pid); + ProcessRecord topApp = mInternal.getTopApp(); + if (topApp == null) { + mPw.println("No top app found"); + } else { + int topPid = topApp.getPid(); + if (prcState == ProcessStateEnum.TOP && topPid == pid) { + mPw.println("New foreground process: " + pid); + } } mPw.flush(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java index 01466b845a61..9be553c49a35 100644 --- a/services/core/java/com/android/server/am/ActivityManagerUtils.java +++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java @@ -17,13 +17,11 @@ package com.android.server.am; import android.app.ActivityThread; import android.content.ContentResolver; -import android.content.Intent; import android.provider.Settings; import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.FrameworkStatsLog; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -135,25 +133,4 @@ public class ActivityManagerUtils { public static int hashComponentNameForAtom(String shortInstanceName) { return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash(); } - - /** - * Helper method to log an unsafe intent event. - */ - public static void logUnsafeIntentEvent(int event, int callingUid, - Intent intent, String resolvedType, boolean blocked) { - String[] categories = intent.getCategories() == null ? new String[0] - : intent.getCategories().toArray(String[]::new); - String component = intent.getComponent() == null ? null - : intent.getComponent().flattenToString(); - FrameworkStatsLog.write(FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED, - event, - callingUid, - component, - intent.getPackage(), - intent.getAction(), - categories, - resolvedType, - intent.getScheme(), - blocked); - } } diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java index 463a2f84aa6b..16219cd5b2ea 100644 --- a/services/core/java/com/android/server/am/AnrHelper.java +++ b/services/core/java/com/android/server/am/AnrHelper.java @@ -49,7 +49,7 @@ class AnrHelper { * this time, the information might be outdated. So we only the dump the unresponsive process * instead of including other processes to avoid making the system more busy. */ - private static final long EXPIRED_REPORT_TIME_MS = TimeUnit.MINUTES.toMillis(1); + private static final long EXPIRED_REPORT_TIME_MS = TimeUnit.SECONDS.toMillis(10); /** * If the last ANR occurred within this given time, consider it's anomaly. diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index b942f4b96b21..841b61e8e81f 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -597,18 +597,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final int cookie = traceBegin("enqueueBroadcast"); r.applySingletonPolicy(mService); - final IntentFilter removeMatchingFilter = (r.options != null) - ? r.options.getRemoveMatchingFilter() : null; - if (removeMatchingFilter != null) { - final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate(); - forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> { - // We only allow caller to remove broadcasts they enqueued - return (r.callingUid == testRecord.callingUid) - && (r.userId == testRecord.userId) - && removeMatching.test(testRecord.intent); - }, mBroadcastConsumerSkipAndCanceled, true); - } - applyDeliveryGroupPolicy(r); r.enqueueTime = SystemClock.uptimeMillis(); @@ -909,6 +897,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final IApplicationThread thread = app.getOnewayThread(); if (thread != null) { try { + if (r.shareIdentity) { + mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent, + UserHandle.getAppId(app.uid), r.callingUid, true); + } if (receiver instanceof BroadcastFilter) { notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver); thread.scheduleRegisteredReceiver( diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index f721d69958f5..48df1494fbe8 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -969,122 +969,6 @@ public class ContentProviderHelper { } /** - * Allows apps to retrieve the MIME type of a URI. - * If an app is in the same user as the ContentProvider, or if it is allowed to interact across - * users, then it does not need permission to access the ContentProvider. - * Either, it needs cross-user uri grants. - * - * CTS tests for this functionality can be run with "runtest cts-appsecurity". - * - * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/ - * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java - * - * @deprecated -- use getProviderMimeTypeAsync. - */ - @Deprecated - String getProviderMimeType(Uri uri, int userId) { - mService.enforceNotIsolatedCaller("getProviderMimeType"); - final String name = uri.getAuthority(); - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId); - final long ident = canClearIdentity(callingPid, callingUid, safeUserId) - ? Binder.clearCallingIdentity() : 0; - final ContentProviderHolder holder; - try { - holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid, - "*getmimetype*", safeUserId); - } finally { - if (ident != 0) { - Binder.restoreCallingIdentity(ident); - } - } - try { - if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) { - final IBinder providerConnection = holder.connection; - final ComponentName providerName = holder.info.getComponentName(); - // Note: creating a new Runnable instead of using a lambda here since lambdas in - // java provide no guarantee that there will be a new instance returned every call. - // Hence, it's possible that a cached copy is returned and the ANR is executed on - // the incorrect provider. - final Runnable providerNotResponding = new Runnable() { - @Override - public void run() { - Log.w(TAG, "Provider " + providerName + " didn't return from getType()."); - appNotRespondingViaProvider(providerConnection); - } - }; - mService.mHandler.postDelayed(providerNotResponding, 1000); - try { - final String type = holder.provider.getType(uri); - return type; - } finally { - mService.mHandler.removeCallbacks(providerNotResponding); - // We need to clear the identity to call removeContentProviderExternalUnchecked - final long token = Binder.clearCallingIdentity(); - try { - removeContentProviderExternalUnchecked(name, null /* token */, safeUserId); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - } catch (RemoteException e) { - Log.w(TAG, "Content provider dead retrieving " + uri, e); - return null; - } catch (Exception e) { - Log.w(TAG, "Exception while determining type of " + uri, e); - return null; - } - - return null; - } - - /** - * Allows apps to retrieve the MIME type of a URI. - * If an app is in the same user as the ContentProvider, or if it is allowed to interact across - * users, then it does not need permission to access the ContentProvider. - * Either way, it needs cross-user uri grants. - */ - void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) { - mService.enforceNotIsolatedCaller("getProviderMimeTypeAsync"); - final String name = uri.getAuthority(); - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId); - final long ident = canClearIdentity(callingPid, callingUid, safeUserId) - ? Binder.clearCallingIdentity() : 0; - final ContentProviderHolder holder; - try { - holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid, - "*getmimetype*", safeUserId); - } finally { - if (ident != 0) { - Binder.restoreCallingIdentity(ident); - } - } - - try { - if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) { - holder.provider.getTypeAsync(uri, new RemoteCallback(result -> { - final long identity = Binder.clearCallingIdentity(); - try { - removeContentProviderExternalUnchecked(name, null, safeUserId); - } finally { - Binder.restoreCallingIdentity(identity); - } - resultCallback.sendResult(result); - })); - } else { - resultCallback.sendResult(Bundle.EMPTY); - } - } catch (RemoteException e) { - Log.w(TAG, "Content provider dead retrieving " + uri, e); - resultCallback.sendResult(Bundle.EMPTY); - } - } - - /** * Filters calls to getType based on permission. If the caller has required permission, * then it returns the contentProvider#getType. * Else, it returns the contentProvider#getTypeAnonymous, which does not diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 1534ff5651de..50841ae4488c 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -122,9 +122,9 @@ option java_package com.android.server.am 30091 um_user_visibility_changed (userId|1|5),(visible|1) # Foreground service start/stop events. -30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3) -30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3) -30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3) +30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1) +30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1) +30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1) # Intent Sender redirect for UserHandle.USER_CURRENT 30110 am_intent_sender_redirect_user (userId|1|5) diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 0c366268604d..d05301a21a18 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -207,7 +207,8 @@ public class OomAdjuster { return AppProtoEnums.OOM_ADJ_REASON_PROCESS_BEGIN; case OOM_ADJ_REASON_PROCESS_END: return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END; - case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: // TODO(short-service) add value to AppProtoEnums + case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: + return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; default: return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 88c0c7ff052b..0d0e5764b522 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1315,7 +1315,7 @@ public class AudioService extends IAudioService.Stub // persistent data initVolumeGroupStates(); - mSoundDoseHelper.initSafeUsbMediaVolumeIndex(); + mSoundDoseHelper.initSafeMediaVolumeIndex(); // Link VGS on VSS initVolumeStreamStates(); @@ -8358,6 +8358,7 @@ public class AudioService extends IAudioService.Stub synchronized (VolumeStreamState.class) { // apply device specific volumes first int index; + boolean isAbsoluteVolume = false; for (int i = 0; i < mIndexMap.size(); i++) { final int device = mIndexMap.keyAt(i); if (device != AudioSystem.DEVICE_OUT_DEFAULT) { @@ -8366,6 +8367,7 @@ public class AudioService extends IAudioService.Stub } else if (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device) || AudioSystem.isLeAudioDeviceType(device)) { + isAbsoluteVolume = true; index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10); } else if (isFullVolumeDevice(device)) { index = (mIndexMax + 5)/10; @@ -8374,6 +8376,11 @@ public class AudioService extends IAudioService.Stub } else { index = (mIndexMap.valueAt(i) + 5)/10; } + + sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, + SENDMSG_REPLACE, device, isAbsoluteVolume ? 1 : 0, this, + /*delay=*/0); + setStreamVolumeIndex(index, device); } } @@ -8830,7 +8837,7 @@ public class AudioService extends IAudioService.Stub final VolumeStreamState streamState = mStreamStates[update.mStreamType]; if (update.hasVolumeIndex()) { int index = update.getVolumeIndex(); - if (!mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) { + if (mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) { index = mSoundDoseHelper.safeMediaVolumeIndex(update.mDevice); } streamState.setIndex(index, update.mDevice, update.mCaller, @@ -8848,6 +8855,10 @@ public class AudioService extends IAudioService.Stub /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) { synchronized (VolumeStreamState.class) { + sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_REPLACE, + device, (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device) + || AudioSystem.isLeAudioDeviceType(device) ? 1 : 0), + streamState, /*delay=*/0); // Apply volume streamState.applyDeviceVolume_syncVSS(device); diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 4e8e70420955..cf81dbe08182 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -52,10 +52,10 @@ import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; import java.util.Objects; -import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; /** @@ -79,7 +79,6 @@ public class SoundDoseHelper { // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume() // (when user opts out). - // Note: when CSD calculation is enabled the state is set to SAFE_MEDIA_VOLUME_DISABLED private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0; private static final int SAFE_MEDIA_VOLUME_DISABLED = 1; private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed @@ -89,9 +88,12 @@ public class SoundDoseHelper { private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 2; private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 3; private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 4; + /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 5; private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours + private static final int MOMENTARY_EXPOSURE_TIMEOUT_MS = (20 * 3600 * 1000); // 20 hours + // 30s after boot completed private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; @@ -125,23 +127,50 @@ public class SoundDoseHelper { // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property private int mSafeMediaVolumeIndex; - // mSafeUsbMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB + // mSafeMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB // property, divided by 100.0. - private float mSafeUsbMediaVolumeDbfs; - - // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index - // corresponding to a gain of mSafeUsbMediaVolumeDbfs (defaulting to -37dB) in audio - // flinger mixer. - // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost - // amplification when both effects are on with all band gains at maximum. - // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when - // the headset is compliant to EN 60950 with a max loudness of 100dB SPL. - private int mSafeUsbMediaVolumeIndex; - // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, - private final Set<Integer> mSafeMediaVolumeDevices = new HashSet<>( - Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET, - AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET)); + // For now using the same value for CSD supported devices + private float mSafeMediaVolumeDbfs; + + private static class SafeDeviceVolumeInfo { + int mDeviceType; + int mSafeVolumeIndex = -1; + + SafeDeviceVolumeInfo(int deviceType) { + mDeviceType = deviceType; + } + } + /** + * mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced. + * Contains a safe volume index for a given device type. + * Indexes are used for headsets and is the music volume UI index + * corresponding to a gain of mSafeMediaVolumeDbfs (defaulting to -37dB) in audio + * flinger mixer. + * We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost + * amplification when both effects are on with all band gains at maximum. + * This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when + * the headset is compliant to EN 60950 with a max loudness of 100dB SPL. + */ + private final HashMap<Integer, SafeDeviceVolumeInfo> mSafeMediaVolumeDevices = + new HashMap<>() {{ + put(AudioSystem.DEVICE_OUT_WIRED_HEADSET, + new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADSET)); + put(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, + new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE)); + put(AudioSystem.DEVICE_OUT_USB_HEADSET, + new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_USB_HEADSET)); + put(AudioSystem.DEVICE_OUT_BLE_HEADSET, + new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_HEADSET)); + put(AudioSystem.DEVICE_OUT_BLE_BROADCAST, + new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_BROADCAST)); + put(AudioSystem.DEVICE_OUT_HEARING_AID, + new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_HEARING_AID)); + put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES, + new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES)); + put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); + }}; // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled @@ -158,12 +187,16 @@ public class SoundDoseHelper { private final boolean mEnableCsd; - private ISoundDose mSoundDose; - private final Object mCsdStateLock = new Object(); + private final AtomicReference<ISoundDose> mSoundDose = new AtomicReference<>(); + @GuardedBy("mCsdStateLock") private float mCurrentCsd = 0.f; + + @GuardedBy("mCsdStateLock") + private long mLastMomentaryExposureTimeMs = -1; + // dose at which the next dose reached warning occurs @GuardedBy("mCsdStateLock") private float mNextCsdWarning = 1.0f; @@ -179,10 +212,26 @@ public class SoundDoseHelper { private final ISoundDoseCallback.Stub mSoundDoseCallback = new ISoundDoseCallback.Stub() { public void onMomentaryExposure(float currentMel, int deviceId) { + if (!mEnableCsd) { + Log.w(TAG, "onMomentaryExposure: csd not supported, ignoring callback"); + return; + } + Log.w(TAG, "DeviceId " + deviceId + " triggered momentary exposure with value: " + currentMel); mLogger.enqueue(SoundDoseEvent.getMomentaryExposureEvent(currentMel)); - if (mEnableCsd) { + + boolean postWarning = false; + synchronized (mCsdStateLock) { + if (mLastMomentaryExposureTimeMs < 0 + || (System.currentTimeMillis() - mLastMomentaryExposureTimeMs) + >= MOMENTARY_EXPOSURE_TIMEOUT_MS) { + mLastMomentaryExposureTimeMs = System.currentTimeMillis(); + postWarning = true; + } + } + + if (postWarning) { mVolumeController.postDisplayCsdWarning( AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE, getTimeoutMsForWarning(AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE)); @@ -241,12 +290,10 @@ public class SoundDoseHelper { mContext = context; mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default); - if (mEnableCsd) { - mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED; - } else { - mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(), - Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0); - } + initCsd(); + + mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(), + Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED); // The default safe volume index read here will be replaced by the actual value when // the mcc is read by onConfigureSafeMedia() @@ -263,9 +310,14 @@ public class SoundDoseHelper { return 0.f; } - Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized"); + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "Sound dose interface not initialized"); + return 0.f; + } + try { - return mSoundDose.getOutputRs2(); + return soundDose.getOutputRs2(); } catch (RemoteException e) { Log.e(TAG, "Exception while getting the RS2 exposure value", e); return 0.f; @@ -277,9 +329,14 @@ public class SoundDoseHelper { return; } - Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized"); + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "Sound dose interface not initialized"); + return; + } + try { - mSoundDose.setOutputRs2(rs2Value); + soundDose.setOutputRs2(rs2Value); } catch (RemoteException e) { Log.e(TAG, "Exception while setting the RS2 exposure value", e); } @@ -290,9 +347,14 @@ public class SoundDoseHelper { return -1.f; } - Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized"); + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "Sound dose interface not initialized"); + return -1.f; + } + try { - return mSoundDose.getCsd(); + return soundDose.getCsd(); } catch (RemoteException e) { Log.e(TAG, "Exception while getting the CSD value", e); return -1.f; @@ -304,13 +366,18 @@ public class SoundDoseHelper { return; } - Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized"); + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "Sound dose interface not initialized"); + return; + } + try { final SoundDoseRecord record = new SoundDoseRecord(); record.timestamp = System.currentTimeMillis(); record.value = csd; final SoundDoseRecord[] recordArray = new SoundDoseRecord[] { record }; - mSoundDose.resetCsd(csd, recordArray); + soundDose.resetCsd(csd, recordArray); } catch (RemoteException e) { Log.e(TAG, "Exception while setting the CSD value", e); } @@ -321,9 +388,14 @@ public class SoundDoseHelper { return; } - Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized"); + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "Sound dose interface not initialized"); + return; + } + try { - mSoundDose.forceUseFrameworkMel(useFrameworkMel); + soundDose.forceUseFrameworkMel(useFrameworkMel); } catch (RemoteException e) { Log.e(TAG, "Exception while forcing the internal MEL computation", e); } @@ -334,9 +406,14 @@ public class SoundDoseHelper { return; } - Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized"); + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "Sound dose interface not initialized"); + return; + } + try { - mSoundDose.forceComputeCsdOnAllDevices(computeCsdOnAllDevices); + soundDose.forceComputeCsdOnAllDevices(computeCsdOnAllDevices); } catch (RemoteException e) { Log.e(TAG, "Exception while forcing CSD computation on all devices", e); } @@ -347,14 +424,12 @@ public class SoundDoseHelper { } /*package*/ int safeMediaVolumeIndex(int device) { - if (!mSafeMediaVolumeDevices.contains(device)) { + final SafeDeviceVolumeInfo vi = mSafeMediaVolumeDevices.get(device); + if (vi == null) { return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; } - if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) { - return mSafeUsbMediaVolumeIndex; - } else { - return mSafeMediaVolumeIndex; - } + + return vi.mSafeVolumeIndex; } /*package*/ void restoreMusicActiveMs() { @@ -378,20 +453,24 @@ public class SoundDoseHelper { /*package*/ void enforceSafeMediaVolume(String caller) { AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream( AudioSystem.STREAM_MUSIC); - Set<Integer> devices = mSafeMediaVolumeDevices; - for (int device : devices) { - int index = streamState.getIndex(device); - int safeIndex = safeMediaVolumeIndex(device); + for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) { + int index = streamState.getIndex(vi.mDeviceType); + int safeIndex = safeMediaVolumeIndex(vi.mDeviceType); if (index > safeIndex) { - streamState.setIndex(safeIndex, device, caller, true /*hasModifyAudioSettings*/); + streamState.setIndex(safeIndex, vi.mDeviceType, caller, + true /*hasModifyAudioSettings*/); mAudioHandler.sendMessageAtTime( - mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, device, /*arg2=*/0, - streamState), /*delay=*/0); + mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, vi.mDeviceType, + /*arg2=*/0, streamState), /*delay=*/0); } } } + /** + * Returns {@code true} if the safe media actions can be applied for the given stream type, + * volume index and device. + **/ /*package*/ boolean checkSafeMediaVolume(int streamType, int index, int device) { boolean result; synchronized (mSafeMediaVolumeStateLock) { @@ -402,17 +481,16 @@ public class SoundDoseHelper { @GuardedBy("mSafeMediaVolumeStateLock") private boolean checkSafeMediaVolume_l(int streamType, int index, int device) { - return (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_ACTIVE) - || (AudioService.mStreamVolumeAlias[streamType] != AudioSystem.STREAM_MUSIC) - || (!mSafeMediaVolumeDevices.contains(device)) - || (index <= safeMediaVolumeIndex(device)) - || mEnableCsd; + return (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) + && (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) + && (mSafeMediaVolumeDevices.containsKey(device)) + && (index > safeMediaVolumeIndex(device)); } /*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device, int flags) { synchronized (mSafeMediaVolumeStateLock) { - if (!checkSafeMediaVolume_l(streamType, index, device)) { + if (checkSafeMediaVolume_l(streamType, index, device)) { mVolumeController.postDisplaySafeVolumeWarning(flags); mPendingVolumeCommand = new StreamVolumeCommand( streamType, index, flags, device); @@ -443,15 +521,13 @@ public class SoundDoseHelper { /*package*/ void scheduleMusicActiveCheck() { synchronized (mSafeMediaVolumeStateLock) { cancelMusicActiveCheck(); - if (!mEnableCsd) { - mMusicActiveIntent = PendingIntent.getBroadcast(mContext, - REQUEST_CODE_CHECK_MUSIC_ACTIVE, - new Intent(ACTION_CHECK_MUSIC_ACTIVE), - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() - + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent); - } + mMusicActiveIntent = PendingIntent.getBroadcast(mContext, + REQUEST_CODE_CHECK_MUSIC_ACTIVE, + new Intent(ACTION_CHECK_MUSIC_ACTIVE), + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent); } } @@ -459,7 +535,7 @@ public class SoundDoseHelper { synchronized (mSafeMediaVolumeStateLock) { if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) { int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC); - if (mSafeMediaVolumeDevices.contains(device) && isStreamActive) { + if (mSafeMediaVolumeDevices.containsKey(device) && isStreamActive) { scheduleMusicActiveCheck(); int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC, device); @@ -487,27 +563,31 @@ public class SoundDoseHelper { /*package*/ void configureSafeMedia(boolean forced, String caller) { int msg = MSG_CONFIGURE_SAFE_MEDIA; - mAudioHandler.removeMessages(msg); + if (forced) { + // unforced should not cancel forced configure messages + mAudioHandler.removeMessages(msg); + } long time = 0; if (forced) { time = (SystemClock.uptimeMillis() + (SystemProperties.getBoolean( "audio.safemedia.bypass", false) ? 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS)); } + mAudioHandler.sendMessageAtTime( mAudioHandler.obtainMessage(msg, /*arg1=*/forced ? 1 : 0, /*arg2=*/0, caller), time); } - /*package*/ void initSafeUsbMediaVolumeIndex() { - // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it - // relies on audio policy having correct ranges for volume indexes. - mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); + /*package*/ void initSafeMediaVolumeIndex() { + for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) { + vi.mSafeVolumeIndex = getSafeDeviceMediaVolumeIndex(vi.mDeviceType); + } } /*package*/ int getSafeMediaVolumeIndex(int device) { - if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && mSafeMediaVolumeDevices.contains( - device)) { + if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE + && mSafeMediaVolumeDevices.containsKey(device)) { return safeMediaVolumeIndex(device); } else { return -1; @@ -516,7 +596,7 @@ public class SoundDoseHelper { /*package*/ boolean raiseVolumeDisplaySafeMediaVolume(int streamType, int index, int device, int flags) { - if (checkSafeMediaVolume(streamType, index, device)) { + if (!checkSafeMediaVolume(streamType, index, device)) { return false; } @@ -525,7 +605,7 @@ public class SoundDoseHelper { } /*package*/ boolean safeDevicesContains(int device) { - return mSafeMediaVolumeDevices.contains(device); + return mSafeMediaVolumeDevices.containsKey(device); } /*package*/ void invalidatPendingVolumeCommand() { @@ -551,6 +631,15 @@ public class SoundDoseHelper { case MSG_PERSIST_CSD_VALUES: onPersistSoundDoseRecords(); break; + case MSG_CSD_UPDATE_ATTENUATION: + final int device = msg.arg1; + final boolean isAbsoluteVolume = (msg.arg2 == 1); + final AudioService.VolumeStreamState streamState = + (AudioService.VolumeStreamState) msg.obj; + + updateDoseAttenuation(streamState.getIndex(device), device, + streamState.getStreamType(), isAbsoluteVolume); + break; default: Log.e(TAG, "Unexpected msg to handle: " + msg.what); break; @@ -562,8 +651,11 @@ public class SoundDoseHelper { pw.print(" mSafeMediaVolumeState="); pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState)); pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex); - pw.print(" mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex); - pw.print(" mSafeUsbMediaVolumeDbfs="); pw.println(mSafeUsbMediaVolumeDbfs); + for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) { + pw.print(" mSafeMediaVolumeIndex["); pw.print(vi.mDeviceType); + pw.print("]="); pw.println(vi.mSafeVolumeIndex); + } + pw.print(" mSafeMediaVolumeDbfs="); pw.println(mSafeMediaVolumeDbfs); pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs); pw.print(" mMcc="); pw.println(mMcc); pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand); @@ -574,16 +666,18 @@ public class SoundDoseHelper { /*package*/void reset() { Log.d(TAG, "Reset the sound dose helper"); - mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback); + mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); + synchronized (mCsdStateLock) { try { - if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) { + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose != null && soundDose.asBinder().isBinderAlive()) { if (mCurrentCsd != 0.f) { Log.d(TAG, "Resetting the saved sound dose value " + mCurrentCsd); SoundDoseRecord[] records = mDoseRecords.toArray( new SoundDoseRecord[0]); - mSoundDose.resetCsd(mCurrentCsd, records); + soundDose.resetCsd(mCurrentCsd, records); } } } catch (RemoteException e) { @@ -592,34 +686,69 @@ public class SoundDoseHelper { } } + private void updateDoseAttenuation(int newIndex, int device, int streamType, + boolean isAbsoluteVolume) { + if (!mEnableCsd) { + return; + } + + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "Can not apply attenuation. ISoundDose itf is null."); + return; + } + + try { + if (!isAbsoluteVolume) { + // remove any possible previous attenuation + soundDose.updateAttenuation(/* attenuationDB= */0.f, device); + + return; + } + + if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC + && mSafeMediaVolumeDevices.containsKey(device)) { + soundDose.updateAttenuation( + AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC, + (newIndex + 5) / 10, + device), device); + } + } catch (RemoteException e) { + Log.e(TAG, "Could not apply the attenuation for MEL calculation with volume index " + + newIndex, e); + } + } + private void initCsd() { - if (mEnableCsd) { - Log.v(TAG, "Initializing sound dose"); + if (!mEnableCsd) { + return; + } - synchronized (mCsdStateLock) { - if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) { - mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L; - } + Log.v(TAG, "Initializing sound dose"); - float prevCsd = mCurrentCsd; - // Restore persisted values - mCurrentCsd = parseGlobalSettingFloat( - Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE, /* defaultValue= */0.f); - if (mCurrentCsd != prevCsd) { - mNextCsdWarning = parseGlobalSettingFloat( - Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f); - final List<SoundDoseRecord> records = persistedStringToRecordList( - mSettings.getGlobalString(mAudioService.getContentResolver(), - Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS), - mGlobalTimeOffsetInSecs); - if (records != null) { - mDoseRecords.addAll(records); - } - } + synchronized (mCsdStateLock) { + if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) { + mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L; } - reset(); + float prevCsd = mCurrentCsd; + // Restore persisted values + mCurrentCsd = parseGlobalSettingFloat( + Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE, /* defaultValue= */0.f); + if (mCurrentCsd != prevCsd) { + mNextCsdWarning = parseGlobalSettingFloat( + Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f); + final List<SoundDoseRecord> records = persistedStringToRecordList( + mSettings.getGlobalString(mAudioService.getContentResolver(), + Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS), + mGlobalTimeOffsetInSecs); + if (records != null) { + mDoseRecords.addAll(records); + } + } } + + reset(); } private void onConfigureSafeMedia(boolean force, String caller) { @@ -629,7 +758,7 @@ public class SoundDoseHelper { mSafeMediaVolumeIndex = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_index) * 10; - mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); + initSafeMediaVolumeIndex(); boolean safeMediaVolumeEnabled = SystemProperties.getBoolean("audio.safemedia.force", false) @@ -642,7 +771,7 @@ public class SoundDoseHelper { // The persisted state is either "disabled" or "active": this is the state applied // next time we boot and cannot be "inactive" int persistedState; - if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !mEnableCsd) { + if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) { persistedState = SAFE_MEDIA_VOLUME_ACTIVE; // The state can already be "inactive" here if the user has forced it before // the 30 seconds timeout for forced configuration. In this case we don't reset @@ -668,10 +797,6 @@ public class SoundDoseHelper { /*obj=*/null), /*delay=*/0); } } - - if (mEnableCsd) { - initCsd(); - } } private int getTimeoutMsForWarning(@AudioManager.CsdWarning int csdWarning) { @@ -719,25 +844,32 @@ public class SoundDoseHelper { mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget(); } - private int getSafeUsbMediaVolumeIndex() { + private int getSafeDeviceMediaVolumeIndex(int deviceType) { + // legacy implementation uses mSafeMediaVolumeIndex for wired HS/HP + // instead of computing it from the volume curves + if ((deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE + || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) && !mEnableCsd) { + return mSafeMediaVolumeIndex; + } + // determine UI volume index corresponding to the wanted safe gain in dBFS int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]; - mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger( + mSafeMediaVolumeDbfs = mContext.getResources().getInteger( com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f; while (Math.abs(max - min) > 1) { int index = (max + min) / 2; - float gainDB = AudioSystem.getStreamVolumeDB( - AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET); + float gainDB = AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC, index, + deviceType); if (Float.isNaN(gainDB)) { //keep last min in case of read error break; - } else if (gainDB == mSafeUsbMediaVolumeDbfs) { + } else if (gainDB == mSafeMediaVolumeDbfs) { min = index; break; - } else if (gainDB < mSafeUsbMediaVolumeDbfs) { + } else if (gainDB < mSafeMediaVolumeDbfs) { min = index; } else { max = index; diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index dce88da6f111..005ad20a2d48 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -23,6 +23,7 @@ import android.app.ActivityTaskManager; import android.app.TaskStackListener; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.hardware.biometrics.AuthenticateOptions; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; @@ -44,8 +45,8 @@ import java.util.function.Supplier; /** * A class to keep track of the authentication state for a given client. */ -public abstract class AuthenticationClient<T> extends AcquisitionClient<T> - implements AuthenticationConsumer { +public abstract class AuthenticationClient<T, O extends AuthenticateOptions> + extends AcquisitionClient<T> implements AuthenticationConsumer { // New, has not started yet public static final int STATE_NEW = 0; @@ -89,14 +90,15 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, - int targetUserId, long operationId, boolean restricted, @NonNull String owner, - int cookie, boolean requireConfirmation, int sensorId, + long operationId, boolean restricted, @NonNull O options, + int cookie, boolean requireConfirmation, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener, @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication, boolean shouldVibrate, int sensorStrength) { - super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId, - shouldVibrate, biometricLogger, biometricContext); + super(context, lazyDaemon, token, listener, options.getUserId(), + options.getOpPackageName(), cookie, options.getSensorId(), shouldVibrate, + biometricLogger, biometricContext); mIsStrongBiometric = isStrongBiometric; mOperationId = operationId; mRequireConfirmation = requireConfirmation; diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java index 0f1fe68ad1d7..25651fa2887e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java @@ -17,6 +17,7 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.biometrics.SensorPropertiesInternal; import android.util.proto.ProtoOutputStream; @@ -39,7 +40,7 @@ public interface BiometricServiceProvider<T extends SensorPropertiesInternal> { List<T> getSensorProperties(); /** Properties for the given sensor id. */ - @NonNull + @Nullable T getSensorProperties(int sensorId); boolean isHardwareDetected(int sensorId); diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java index 2263e80039bd..a4b0a0eece5b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java @@ -92,7 +92,7 @@ public class BiometricStateCallback<T extends BiometricServiceProvider<P>, final int previousBiometricState = mBiometricState; if (client instanceof AuthenticationClient) { - final AuthenticationClient<?> authClient = (AuthenticationClient<?>) client; + final AuthenticationClient<?, ?> authClient = (AuthenticationClient<?, ?>) client; if (authClient.isKeyguard()) { mBiometricState = STATE_KEYGUARD_AUTH; } else if (authClient.isBiometricPrompt()) { diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java index cdf22aadbd8c..69ad1523118d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java @@ -64,7 +64,6 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>(); private final BiometricUtils<S> mBiometricUtils; private final Map<Integer, Long> mAuthenticatorIds; - private final List<S> mEnrolledList; private final boolean mHasEnrollmentsBeforeStarting; private BaseClientMonitor mCurrentTask; private boolean mFavorHalEnrollments = false; @@ -135,13 +134,12 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, - @NonNull List<S> enrolledList, @NonNull BiometricUtils<S> utils, + @NonNull BiometricUtils<S> utils, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */, userId, owner, 0 /* cookie */, sensorId, logger, biometricContext); mBiometricUtils = utils; mAuthenticatorIds = authenticatorIds; - mEnrolledList = enrolledList; mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty(); } @@ -169,12 +167,16 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide public void start(@NonNull ClientMonitorCallback callback) { super.start(callback); + final List<S> enrolledList = + mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()); + // Start enumeration. Removal will start if necessary, when enumeration is completed. mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(), - getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId(), getLogger(), + getOwnerString(), enrolledList, mBiometricUtils, getSensorId(), getLogger(), getBiometricContext()); - Slog.d(TAG, "Starting enumerate: " + mCurrentTask); + Slog.d(TAG, "Starting enumerate: " + mCurrentTask + " enrolledList size:" + + enrolledList.size()); mCurrentTask.start(mEnumerateCallback); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java index 51829684f3ab..fb64bcc3abc1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java @@ -64,9 +64,10 @@ public final class FaceAuthenticator extends IBiometricAuthenticator.Stub { long operationId, int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication) throws RemoteException { - mFaceService.prepareForAuthentication(mSensorId, requireConfirmation, token, operationId, + mFaceService.prepareForAuthentication(requireConfirmation, token, operationId, sensorReceiver, new FaceAuthenticateOptions.Builder() .setUserId(userId) + .setSensorId(mSensorId) .setOpPackageName(opPackageName) .build(), requestId, cookie, allowBackgroundAuthentication); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 1ee9f53c5774..6d7b2cb40e21 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -246,7 +246,6 @@ public class FaceService extends SystemService { super.authenticate_enforcePermission(); - final int userId = options.getUserId(); final String opPackageName = options.getOpPackageName(); final boolean restricted = false; // Face APIs are private final int statsClient = Utils.isKeyguard(getContext(), opPackageName) @@ -261,9 +260,11 @@ public class FaceService extends SystemService { if (provider == null) { Slog.w(TAG, "Null provider for authenticate"); return -1; + } else { + options.setSensorId(provider.first); } - return provider.second.scheduleAuthenticate(provider.first, token, operationId, + return provider.second.scheduleAuthenticate(token, operationId, 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options, restricted, statsClient, isKeyguard); } @@ -286,28 +287,27 @@ public class FaceService extends SystemService { return -1; } - return provider.second.scheduleFaceDetect(provider.first, token, options.getUserId(), - new ClientMonitorCallbackConverter(receiver), opPackageName, + return provider.second.scheduleFaceDetect(token, + new ClientMonitorCallbackConverter(receiver), options, BiometricsProtoEnums.CLIENT_KEYGUARD); } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call - public void prepareForAuthentication(int sensorId, boolean requireConfirmation, + public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId, IBiometricSensorReceiver sensorReceiver, FaceAuthenticateOptions options, long requestId, int cookie, boolean allowBackgroundAuthentication) { super.prepareForAuthentication_enforcePermission(); - final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(options.getSensorId()); if (provider == null) { Slog.w(TAG, "Null provider for prepareForAuthentication"); return; } - final boolean isKeyguardBypassEnabled = false; // only valid for keyguard clients final boolean restricted = true; // BiometricPrompt is always restricted - provider.scheduleAuthenticate(sensorId, token, operationId, cookie, + provider.scheduleAuthenticate(token, operationId, cookie, new ClientMonitorCallbackConverter(sensorReceiver), options, requestId, restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java index 609c6a77e50a..2cf64b72d01f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java @@ -83,18 +83,19 @@ public interface ServiceProvider extends BiometricServiceProvider<FaceSensorProp void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId); - long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId, - @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, + long scheduleFaceDetect(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter callback, + @NonNull FaceAuthenticateOptions options, int statsClient); void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId); - long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, + long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter callback, @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient, boolean allowBackgroundAuthentication); - void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, + void scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter callback, @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, int statsClient, boolean allowBackgroundAuthentication); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 29dd707a7c1d..976f1cbe1e5c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -28,6 +28,7 @@ import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.face.IFace; +import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceAuthenticationFrame; import android.hardware.face.FaceManager; import android.os.IBinder; @@ -56,7 +57,7 @@ import java.util.function.Supplier; /** * Face-specific authentication client for the {@link IFace} AIDL HAL interface. */ -class FaceAuthenticationClient extends AuthenticationClient<AidlSession> +class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions> implements LockoutConsumer { private static final String TAG = "FaceAuthenticationClient"; @@ -80,16 +81,16 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> FaceAuthenticationClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, - @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, - boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId, + @NonNull ClientMonitorCallbackConverter listener, long operationId, + boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie, + boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, @Authenticators.Types int sensorStrength) { - this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId, - restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext, - isStrongBiometric, usageStats, lockoutCache /* lockoutCache */, - allowBackgroundAuthentication, + this(context, lazyDaemon, token, requestId, listener, operationId, + restricted, options, cookie, requireConfirmation, logger, biometricContext, + isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication, context.getSystemService(SensorPrivacyManager.class), sensorStrength); } @@ -97,15 +98,16 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> FaceAuthenticationClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, - @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, - boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId, + @NonNull ClientMonitorCallbackConverter listener, long operationId, + boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie, + boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, SensorPrivacyManager sensorPrivacyManager, @Authenticators.Types int biometricStrength) { - super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, - owner, cookie, requireConfirmation, sensorId, logger, biometricContext, + super(context, lazyDaemon, token, listener, operationId, restricted, + options, cookie, requireConfirmation, logger, biometricContext, isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */, allowBackgroundAuthentication, false /* shouldVibrate */, biometricStrength); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java index 506b2bc8d9db..e65202dca5cd 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java @@ -22,6 +22,7 @@ import android.content.Context; import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.face.FaceAuthenticateOptions; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -51,11 +52,11 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, - @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int sensorId, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FaceAuthenticateOptions options, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric) { - this(context, lazyDaemon, token, requestId, listener, userId, owner, sensorId, + this(context, lazyDaemon, token, requestId, listener, options, logger, biometricContext, isStrongBiometric, context.getSystemService(SensorPrivacyManager.class)); } @@ -63,11 +64,12 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements @VisibleForTesting FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, - @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int sensorId, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FaceAuthenticateOptions options, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) { - super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, + super(context, lazyDaemon, token, listener, options.getUserId(), + options.getOpPackageName(), 0 /* cookie */, options.getSensorId(), true /* shouldVibrate */, logger, biometricContext); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java index b0b23faa9aa5..f09d192966f1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java @@ -43,10 +43,10 @@ class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> FaceInternalCleanupClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, - @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList, + @NonNull BiometricContext biometricContext, @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, - enrolledList, utils, authenticatorIds); + utils, authenticatorIds); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 41e02691ddf8..1a53fec82d98 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -410,16 +410,17 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } @Override - public long scheduleFaceDetect(int sensorId, @NonNull IBinder token, - int userId, @NonNull ClientMonitorCallbackConverter callback, - @NonNull String opPackageName, int statsClient) { + public long scheduleFaceDetect(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter callback, + @NonNull FaceAuthenticateOptions options, int statsClient) { final long id = mRequestCounter.incrementAndGet(); + final int sensorId = options.getSensorId(); mHandler.post(() -> { final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); final FaceDetectClient client = new FaceDetectClient(mContext, mSensors.get(sensorId).getLazySession(), - token, id, callback, userId, opPackageName, sensorId, + token, id, callback, options, createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), mBiometricContext, isStrongBiometric); scheduleForSensor(sensorId, client, mBiometricStateCallback); @@ -435,18 +436,19 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } @Override - public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, + public void scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter callback, @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { mHandler.post(() -> { final int userId = options.getUserId(); + final int sensorId = options.getSensorId(); final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); final FaceAuthenticationClient client = new FaceAuthenticationClient( mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback, - userId, operationId, restricted, options.getOpPackageName(), cookie, - false /* requireConfirmation */, sensorId, + operationId, restricted, options, cookie, + false /* requireConfirmation */, createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), mBiometricContext, isStrongBiometric, mUsageStats, mSensors.get(sensorId).getLockoutCache(), @@ -470,13 +472,13 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } @Override - public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, + public long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter callback, @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { final long id = mRequestCounter.incrementAndGet(); - scheduleAuthenticate(sensorId, token, operationId, cookie, callback, + scheduleAuthenticate(token, operationId, cookie, callback, options, id, restricted, statsClient, allowBackgroundAuthentication); return id; @@ -595,14 +597,13 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { public void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) { mHandler.post(() -> { - final List<Face> enrolledList = getEnrolledFaces(sensorId, userId); final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, mSensors.get(sensorId).getLazySession(), userId, mContext.getOpPackageName(), sensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN), - mBiometricContext, enrolledList, + mBiometricContext, FaceUtils.getInstance(sensorId), mSensors.get(sensorId).getAuthenticatorIds()); if (favorHalEnrollments) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 7e575bc23f9c..1e33c96d50ad 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -651,9 +651,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } @Override - public long scheduleFaceDetect(int sensorId, @NonNull IBinder token, - int userId, @NonNull ClientMonitorCallbackConverter callback, - @NonNull String opPackageName, int statsClient) { + public long scheduleFaceDetect(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter callback, + @NonNull FaceAuthenticateOptions options, int statsClient) { throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you" + "forget to check the supportsFaceDetection flag?"); } @@ -665,7 +665,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } @Override - public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, + public void scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter receiver, @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { @@ -675,8 +675,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId); final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, - mLazyDaemon, token, requestId, receiver, userId, operationId, restricted, - options.getOpPackageName(), cookie, false /* requireConfirmation */, mSensorId, + mLazyDaemon, token, requestId, receiver, operationId, restricted, + options, cookie, false /* requireConfirmation */, createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), mBiometricContext, isStrongBiometric, mLockoutTracker, mUsageStats, allowBackgroundAuthentication, @@ -686,13 +686,13 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } @Override - public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, + public long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter receiver, @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { final long id = mRequestCounter.incrementAndGet(); - scheduleAuthenticate(sensorId, token, operationId, cookie, receiver, + scheduleAuthenticate(token, operationId, cookie, receiver, options, id, restricted, statsClient, allowBackgroundAuthentication); return id; @@ -818,12 +818,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final List<Face> enrolledList = getEnrolledFaces(mSensorId, userId); final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN), - mBiometricContext, enrolledList, + mBiometricContext, FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback, mBiometricStateCallback)); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 1c1f56ccddd5..8ab88923d01e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -25,6 +25,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; import android.os.IBinder; import android.os.RemoteException; @@ -50,7 +51,8 @@ import java.util.function.Supplier; * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0} * HIDL interface. */ -class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { +class FaceAuthenticationClient + extends AuthenticationClient<IBiometricsFace, FaceAuthenticateOptions> { private static final String TAG = "FaceAuthenticationClient"; @@ -67,17 +69,18 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { FaceAuthenticationClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, long requestId, - @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, - boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId, + @NonNull ClientMonitorCallbackConverter listener, long operationId, + boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie, + boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker, @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication, @Authenticators.Types int sensorStrength) { - super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, - owner, cookie, requireConfirmation, sensorId, logger, biometricContext, + super(context, lazyDaemon, token, listener, operationId, restricted, + options, cookie, requireConfirmation, logger, biometricContext, isStrongBiometric, null /* taskStackListener */, lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */, - sensorStrength); + sensorStrength); setRequestId(requestId); mUsageStats = usageStats; mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java index d21a7501e516..89a17c6b570e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java @@ -42,10 +42,10 @@ class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsF FaceInternalCleanupClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, - @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList, + @NonNull BiometricContext biometricContext, @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, - enrolledList, utils, authenticatorIds); + utils, authenticatorIds); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java index 52d887a75216..d47a57ad6742 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java @@ -23,6 +23,7 @@ import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.SensorPropertiesInternal; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.IFingerprintService; import android.os.IBinder; import android.os.RemoteException; @@ -63,8 +64,13 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub long operationId, int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication) throws RemoteException { - mFingerprintService.prepareForAuthentication(mSensorId, token, operationId, userId, - sensorReceiver, opPackageName, requestId, cookie, allowBackgroundAuthentication); + mFingerprintService.prepareForAuthentication(token, operationId, sensorReceiver, + new FingerprintAuthenticateOptions.Builder() + .setSensorId(mSensorId) + .setUserId(userId) + .setOpPackageName(opPackageName) + .build(), + requestId, cookie, allowBackgroundAuthentication); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index affc496edc70..f6c1375730bb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -119,7 +119,7 @@ public class FingerprintService extends SystemService { @NonNull private final Supplier<String[]> mAidlInstanceNameSupplier; @NonNull - private final Function<String, IFingerprint> mIFingerprintProvider; + private final Function<String, FingerprintProvider> mFingerprintProvider; @NonNull private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal> mBiometricStateCallback; @@ -307,6 +307,8 @@ public class FingerprintService extends SystemService { if (provider == null) { Slog.w(TAG, "Null provider for authenticate"); return -1; + } else { + options.setSensorId(provider.first); } final FingerprintSensorPropertiesInternal sensorProps = @@ -322,8 +324,8 @@ public class FingerprintService extends SystemService { return -1; } } - return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId, - 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName, + return provider.second.scheduleAuthenticate(token, operationId, + 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options, restricted, statsClient, isKeyguard); } @@ -425,40 +427,36 @@ public class FingerprintService extends SystemService { return -1; } - if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, options.getUserId())) { - // If this happens, something in KeyguardUpdateMonitor is wrong. This should only - // ever be invoked when the user is encrypted or lockdown. - Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown"); - return -1; - } - final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for detectFingerprint"); return -1; + } else { + options.setSensorId(provider.first); } - return provider.second.scheduleFingerDetect(provider.first, token, options.getUserId(), - new ClientMonitorCallbackConverter(receiver), opPackageName, + return provider.second.scheduleFingerDetect(token, + new ClientMonitorCallbackConverter(receiver), options, BiometricsProtoEnums.CLIENT_KEYGUARD); } @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC) @Override // Binder call - public void prepareForAuthentication(int sensorId, IBinder token, long operationId, - int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, + public void prepareForAuthentication(IBinder token, long operationId, + IBiometricSensorReceiver sensorReceiver, + @NonNull FingerprintAuthenticateOptions options, long requestId, int cookie, boolean allowBackgroundAuthentication) { super.prepareForAuthentication_enforcePermission(); - final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); + final ServiceProvider provider = mRegistry.getProviderForSensor(options.getSensorId()); if (provider == null) { Slog.w(TAG, "Null provider for prepareForAuthentication"); return; } final boolean restricted = true; // BiometricPrompt is always restricted - provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie, - new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId, + provider.scheduleAuthenticate(token, operationId, cookie, + new ClientMonitorCallbackConverter(sensorReceiver), options, requestId, restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication); } @@ -982,8 +980,7 @@ public class FingerprintService extends SystemService { () -> IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR), - (fqName) -> IFingerprint.Stub.asInterface( - Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)))); + null /* fingerprintProvider */); } @VisibleForTesting @@ -991,16 +988,35 @@ public class FingerprintService extends SystemService { BiometricContext biometricContext, Supplier<IBiometricService> biometricServiceSupplier, Supplier<String[]> aidlInstanceNameSupplier, - Function<String, IFingerprint> fingerprintProvider) { + Function<String, FingerprintProvider> fingerprintProvider) { super(context); mBiometricContext = biometricContext; mAidlInstanceNameSupplier = aidlInstanceNameSupplier; - mIFingerprintProvider = fingerprintProvider; mAppOps = context.getSystemService(AppOpsManager.class); mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher(); mLockoutResetDispatcher = new LockoutResetDispatcher(context); mLockPatternUtils = new LockPatternUtils(context); mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context)); + mFingerprintProvider = fingerprintProvider != null ? fingerprintProvider : + (name) -> { + final String fqName = IFingerprint.DESCRIPTOR + "/" + name; + final IFingerprint fp = IFingerprint.Stub.asInterface( + Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))); + if (fp != null) { + try { + return new FingerprintProvider(getContext(), + mBiometricStateCallback, fp.getSensorProps(), name, + mLockoutResetDispatcher, mGestureAvailabilityDispatcher, + mBiometricContext); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in getSensorProps: " + fqName); + } + } else { + Slog.e(TAG, "Unable to get declared service: " + fqName); + } + + return null; + }; mHandler = new Handler(Looper.getMainLooper()); mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier); mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() { @@ -1044,23 +1060,9 @@ public class FingerprintService extends SystemService { final List<ServiceProvider> providers = new ArrayList<>(); for (String instance : instances) { - final String fqName = IFingerprint.DESCRIPTOR + "/" + instance; - final IFingerprint fp = mIFingerprintProvider.apply(fqName); - - if (fp != null) { - try { - final FingerprintProvider provider = new FingerprintProvider(getContext(), - mBiometricStateCallback, fp.getSensorProps(), instance, - mLockoutResetDispatcher, mGestureAvailabilityDispatcher, - mBiometricContext); - Slog.i(TAG, "Adding AIDL provider: " + fqName); - providers.add(provider); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception in getSensorProps: " + fqName); - } - } else { - Slog.e(TAG, "Unable to get declared service: " + fqName); - } + final FingerprintProvider provider = mFingerprintProvider.apply(instance); + Slog.i(TAG, "Adding AIDL provider: " + instance); + providers.add(provider); } return providers; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index 5b6f14d6c805..004af2c2ad62 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -23,6 +23,7 @@ import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintServiceReceiver; @@ -78,19 +79,21 @@ public interface ServiceProvider extends void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId); - long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId, - @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, + long scheduleFingerDetect(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter callback, + @NonNull FingerprintAuthenticateOptions options, int statsClient); - void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId, + void scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter callback, - @NonNull String opPackageName, long requestId, boolean restricted, int statsClient, + @NonNull FingerprintAuthenticateOptions options, + long requestId, boolean restricted, int statsClient, boolean allowBackgroundAuthentication); - long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId, + long scheduleAuthenticate(@NonNull IBinder token, long operationId, int cookie, @NonNull ClientMonitorCallbackConverter callback, - @NonNull String opPackageName, boolean restricted, int statsClient, - boolean allowBackgroundAuthentication); + @NonNull FingerprintAuthenticateOptions options, + boolean restricted, int statsClient, boolean allowBackgroundAuthentication); void startPreparedClient(int sensorId, int cookie); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index d1a7b1339179..0f81f9f2660e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcqu import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.fingerprint.PointerContext; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlay; @@ -65,7 +66,8 @@ import java.util.function.Supplier; * Fingerprint-specific authentication client supporting the {@link * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. */ -class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> +class FingerprintAuthenticationClient + extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions> implements Udfps, LockoutConsumer, PowerPressHandler { private static final String TAG = "FingerprintAuthenticationClient"; private static final int MESSAGE_AUTH_SUCCESS = 2; @@ -97,13 +99,11 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, - int targetUserId, long operationId, boolean restricted, - @NonNull String owner, + @NonNull FingerprintAuthenticateOptions options, int cookie, boolean requireConfirmation, - int sensorId, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @@ -122,13 +122,11 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> lazyDaemon, token, listener, - targetUserId, operationId, restricted, - owner, + options, cookie, requireConfirmation, - sensorId, biometricLogger, biometricContext, isStrongBiometric, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index f6911ea29837..376d23187fb8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.IUdfpsOverlay; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; @@ -52,13 +53,14 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, - @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int sensorId, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) { - super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, + super(context, lazyDaemon, token, listener, options.getUserId(), + options.getOpPackageName(), 0 /* cookie */, options.getSensorId(), true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java index c315ccf8dea2..ff9127f516af 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java @@ -45,10 +45,9 @@ class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, - @NonNull List<Fingerprint> enrolledList, @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, - enrolledList, utils, authenticatorIds); + utils, authenticatorIds); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 776d33172e68..23b6f84e6954 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -37,6 +37,7 @@ import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.SensorProps; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintServiceReceiver; @@ -422,15 +423,17 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override - public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId, - @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, + public long scheduleFingerDetect(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter callback, + @NonNull FingerprintAuthenticateOptions options, int statsClient) { final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { + final int sensorId = options.getSensorId(); final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); final FingerprintDetectClient client = new FingerprintDetectClient(mContext, - mSensors.get(sensorId).getLazySession(), token, id, callback, userId, - opPackageName, sensorId, + mSensors.get(sensorId).getLazySession(), token, id, callback, + options, createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric); @@ -441,16 +444,19 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override - public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, - int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback, - @NonNull String opPackageName, long requestId, boolean restricted, int statsClient, + public void scheduleAuthenticate(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter callback, + @NonNull FingerprintAuthenticateOptions options, + long requestId, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { mHandler.post(() -> { + final int userId = options.getUserId(); + final int sensorId = options.getSensorId(); final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback, - userId, operationId, restricted, opPackageName, cookie, - false /* requireConfirmation */, sensorId, + operationId, restricted, options, cookie, + false /* requireConfirmation */, createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), mBiometricContext, isStrongBiometric, mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), @@ -485,14 +491,14 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override - public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, - int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback, - @NonNull String opPackageName, boolean restricted, int statsClient, + public long scheduleAuthenticate(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter callback, + @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { final long id = mRequestCounter.incrementAndGet(); - scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback, - opPackageName, id, restricted, statsClient, allowBackgroundAuthentication); + scheduleAuthenticate(token, operationId, cookie, callback, + options, id, restricted, statsClient, allowBackgroundAuthentication); return id; } @@ -556,7 +562,6 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public void scheduleInternalCleanup(int sensorId, int userId, @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) { mHandler.post(() -> { - final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId); final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(mContext, mSensors.get(sensorId).getLazySession(), userId, @@ -564,7 +569,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN), mBiometricContext, - enrolledList, FingerprintUtils.getInstance(sensorId), + FingerprintUtils.getInstance(sensorId), mSensors.get(sensorId).getAuthenticatorIds()); if (favorHalEnrollments) { client.setFavorHalEnrollments(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 4567addc4302..9e6f4e4f13f3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -34,6 +34,7 @@ import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -631,17 +632,17 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override - public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId, - @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName, + public long scheduleFingerDetect(@NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, int statsClient) { final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { - scheduleUpdateActiveUserWithoutHandler(userId); + scheduleUpdateActiveUserWithoutHandler(options.getUserId()); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); final FingerprintDetectClient client = new FingerprintDetectClient(mContext, - mLazyDaemon, token, id, listener, userId, opPackageName, - mSensorProperties.sensorId, + mLazyDaemon, token, id, listener, options, createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric); @@ -652,18 +653,18 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override - public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, - int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener, - @NonNull String opPackageName, long requestId, boolean restricted, int statsClient, + public void scheduleAuthenticate(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, + long requestId, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { mHandler.post(() -> { - scheduleUpdateActiveUserWithoutHandler(userId); + scheduleUpdateActiveUserWithoutHandler(options.getUserId()); final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( - mContext, mLazyDaemon, token, requestId, listener, userId, operationId, - restricted, opPackageName, cookie, false /* requireConfirmation */, - mSensorProperties.sensorId, + mContext, mLazyDaemon, token, requestId, listener, operationId, + restricted, options, cookie, false /* requireConfirmation */, createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), mBiometricContext, isStrongBiometric, mTaskStackListener, mLockoutTracker, @@ -675,14 +676,14 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override - public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, - int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener, - @NonNull String opPackageName, boolean restricted, int statsClient, + public long scheduleAuthenticate(@NonNull IBinder token, long operationId, + int cookie, @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { final long id = mRequestCounter.incrementAndGet(); - scheduleAuthenticate(sensorId, token, operationId, userId, cookie, listener, - opPackageName, id, restricted, statsClient, allowBackgroundAuthentication); + scheduleAuthenticate(token, operationId, cookie, listener, + options, id, restricted, statsClient, allowBackgroundAuthentication); return id; } @@ -741,14 +742,12 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); - final List<Fingerprint> enrolledList = getEnrolledFingerprints( - mSensorProperties.sensorId, userId); final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( mContext, mLazyDaemon, userId, mContext.getOpPackageName(), mSensorProperties.sensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN), - mBiometricContext, enrolledList, + mBiometricContext, FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, callback); }); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 73b1288d00d2..0a47c12bbe73 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -22,6 +22,7 @@ import android.app.trust.TrustManager; import android.content.ContentResolver; import android.content.Context; import android.hardware.biometrics.fingerprint.PointerContext; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; @@ -362,13 +363,16 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage // Store the authClient parameters so it can be rescheduled final IBinder token = client.getToken(); final long operationId = authClient.getOperationId(); - final int user = client.getTargetUserId(); final int cookie = client.getCookie(); final ClientMonitorCallbackConverter listener = client.getListener(); - final String opPackageName = client.getOwnerString(); final boolean restricted = authClient.isRestricted(); final int statsClient = client.getLogger().getStatsClient(); final boolean isKeyguard = authClient.isKeyguard(); + final FingerprintAuthenticateOptions options = + new FingerprintAuthenticateOptions.Builder() + .setUserId(client.getTargetUserId()) + .setOpPackageName(client.getOwnerString()) + .build(); // Don't actually send cancel() to the HAL, since successful auth already finishes // HAL authenticate() lifecycle. Just @@ -376,8 +380,8 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage // Schedule this only after we invoke onClientFinished for the previous client, so that // internal preemption logic is not run. - mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token, - operationId, user, cookie, listener, opPackageName, restricted, statsClient, + mFingerprint21.scheduleAuthenticate(token, + operationId, cookie, listener, options, restricted, statsClient, isKeyguard); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 957005a9223e..d22aef8b3971 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlay; @@ -57,7 +58,8 @@ import java.util.function.Supplier; * {@link android.hardware.biometrics.fingerprint.V2_1} and * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces. */ -class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFingerprint> +class FingerprintAuthenticationClient + extends AuthenticationClient<IBiometricsFingerprint, FingerprintAuthenticateOptions> implements Udfps { private static final String TAG = "Biometrics/FingerprintAuthClient"; @@ -72,9 +74,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi FingerprintAuthenticationClient(@NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, long requestId, - @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, - boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation, - int sensorId, @NonNull BiometricLogger logger, + @NonNull ClientMonitorCallbackConverter listener, long operationId, + boolean restricted, @NonNull FingerprintAuthenticateOptions options, + int cookie, boolean requireConfirmation, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull TaskStackListener taskStackListener, @NonNull LockoutFrameworkImpl lockoutTracker, @@ -84,8 +86,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps, @Authenticators.Types int sensorStrength) { - super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, - owner, cookie, requireConfirmation, sensorId, logger, biometricContext, + super(context, lazyDaemon, token, listener, operationId, restricted, + options, cookie, requireConfirmation, logger, biometricContext, isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */, sensorStrength); setRequestId(requestId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index cfa9fb429fdd..362c820b9e8d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -24,6 +24,7 @@ import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.IUdfpsOverlay; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; @@ -61,12 +62,13 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> public FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, long requestId, - @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, - int sensorId, + @NonNull ClientMonitorCallbackConverter listener, + @NonNull FingerprintAuthenticateOptions options, @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) { - super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, + super(context, lazyDaemon, token, listener, options.getUserId(), + options.getOpPackageName(), 0 /* cookie */, options.getSensorId(), true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); mSensorOverlays = new SensorOverlays(udfpsOverlayController, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java index 5e7cf3578411..8b61f5966c14 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java @@ -45,11 +45,10 @@ class FingerprintInternalCleanupClient @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, - @NonNull List<Fingerprint> enrolledList, @NonNull BiometricUtils<Fingerprint> utils, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, - enrolledList, utils, authenticatorIds); + utils, authenticatorIds); } @Override diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index dd92ffcbe472..fab138bb2931 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -18,9 +18,9 @@ package com.android.server.clipboard; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID; import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID; +import static android.content.Context.DEVICE_ID_DEFAULT; +import static android.content.Context.DEVICE_ID_INVALID; import android.Manifest; import android.annotation.NonNull; diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index ab2c002c2b24..1ce917cb7841 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -3254,6 +3254,8 @@ public class Vpn { } mActiveNetwork = network; + mUnderlyingLinkProperties = null; + mUnderlyingNetworkCapabilities = null; mRetryCount = 0; startOrMigrateIkeSession(network); diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index 6e1640d545fe..22b6a53ab907 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -35,7 +35,6 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; -import android.hardware.display.BrightnessConfiguration; import android.hardware.display.ColorDisplayManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; @@ -126,7 +125,7 @@ public class BrightnessTracker { private static final int MSG_BRIGHTNESS_CHANGED = 1; private static final int MSG_STOP_SENSOR_LISTENER = 2; private static final int MSG_START_SENSOR_LISTENER = 3; - private static final int MSG_BRIGHTNESS_CONFIG_CHANGED = 4; + private static final int MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED = 4; private static final int MSG_SENSOR_CHANGED = 5; private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); @@ -162,7 +161,7 @@ public class BrightnessTracker { private boolean mColorSamplingEnabled; private int mNoFramesToSample; private float mFrameRate; - private BrightnessConfiguration mBrightnessConfiguration; + private boolean mShouldCollectColorSample = false; // End of block of members that should only be accessed on the mBgHandler thread. private @UserIdInt int mCurrentUserId = UserHandle.USER_NULL; @@ -208,9 +207,9 @@ public class BrightnessTracker { /** * Update tracker with new brightness configuration. */ - public void setBrightnessConfiguration(BrightnessConfiguration brightnessConfiguration) { - mBgHandler.obtainMessage(MSG_BRIGHTNESS_CONFIG_CHANGED, - brightnessConfiguration).sendToTarget(); + public void setShouldCollectColorSample(boolean shouldCollectColorSample) { + mBgHandler.obtainMessage(MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED, + shouldCollectColorSample).sendToTarget(); } private void backgroundStart(float initialBrightness) { @@ -320,7 +319,7 @@ public class BrightnessTracker { * Notify the BrightnessTracker that the user has changed the brightness of the display. */ public void notifyBrightnessChanged(float brightness, boolean userInitiated, - float powerBrightnessFactor, boolean isUserSetBrightness, + float powerBrightnessFactor, boolean wasShortTermModelActive, boolean isDefaultBrightnessConfig, String uniqueDisplayId, float[] luxValues, long[] luxTimestamps) { if (DEBUG) { @@ -329,7 +328,7 @@ public class BrightnessTracker { } Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED, userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness, - powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig, + powerBrightnessFactor, wasShortTermModelActive, isDefaultBrightnessConfig, mInjector.currentTimeMillis(), uniqueDisplayId, luxValues, luxTimestamps)); m.sendToTarget(); } @@ -343,7 +342,7 @@ public class BrightnessTracker { } private void handleBrightnessChanged(float brightness, boolean userInitiated, - float powerBrightnessFactor, boolean isUserSetBrightness, + float powerBrightnessFactor, boolean wasShortTermModelActive, boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId, float[] luxValues, long[] luxTimestamps) { BrightnessChangeEvent.Builder builder; @@ -368,7 +367,7 @@ public class BrightnessTracker { builder.setBrightness(brightness); builder.setTimeStamp(timestamp); builder.setPowerBrightnessFactor(powerBrightnessFactor); - builder.setUserBrightnessPoint(isUserSetBrightness); + builder.setUserBrightnessPoint(wasShortTermModelActive); builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig); builder.setUniqueDisplayId(uniqueDisplayId); @@ -827,8 +826,7 @@ public class BrightnessTracker { if (!mInjector.isBrightnessModeAutomatic(mContentResolver) || !mInjector.isInteractive(mContext) || mColorSamplingEnabled - || mBrightnessConfiguration == null - || !mBrightnessConfiguration.shouldCollectColorSamples()) { + || !mShouldCollectColorSample) { return; } @@ -997,7 +995,7 @@ public class BrightnessTracker { BrightnessChangeValues values = (BrightnessChangeValues) msg.obj; boolean userInitiatedChange = (msg.arg1 == 1); handleBrightnessChanged(values.brightness, userInitiatedChange, - values.powerBrightnessFactor, values.isUserSetBrightness, + values.powerBrightnessFactor, values.wasShortTermModelActive, values.isDefaultBrightnessConfig, values.timestamp, values.uniqueDisplayId, values.luxValues, values.luxTimestamps); break; @@ -1009,14 +1007,11 @@ public class BrightnessTracker { stopSensorListener(); disableColorSampling(); break; - case MSG_BRIGHTNESS_CONFIG_CHANGED: - mBrightnessConfiguration = (BrightnessConfiguration) msg.obj; - boolean shouldCollectColorSamples = - mBrightnessConfiguration != null - && mBrightnessConfiguration.shouldCollectColorSamples(); - if (shouldCollectColorSamples && !mColorSamplingEnabled) { + case MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED: + mShouldCollectColorSample = (boolean) msg.obj; + if (mShouldCollectColorSample && !mColorSamplingEnabled) { enableColorSampling(); - } else if (!shouldCollectColorSamples && mColorSamplingEnabled) { + } else if (!mShouldCollectColorSample && mColorSamplingEnabled) { disableColorSampling(); } break; @@ -1031,7 +1026,7 @@ public class BrightnessTracker { private static class BrightnessChangeValues { public final float brightness; public final float powerBrightnessFactor; - public final boolean isUserSetBrightness; + public final boolean wasShortTermModelActive; public final boolean isDefaultBrightnessConfig; public final long timestamp; public final String uniqueDisplayId; @@ -1039,11 +1034,11 @@ public class BrightnessTracker { public final long[] luxTimestamps; BrightnessChangeValues(float brightness, float powerBrightnessFactor, - boolean isUserSetBrightness, boolean isDefaultBrightnessConfig, + boolean wasShortTermModelActive, boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId, float[] luxValues, long[] luxTimestamps) { this.brightness = brightness; this.powerBrightnessFactor = powerBrightnessFactor; - this.isUserSetBrightness = isUserSetBrightness; + this.wasShortTermModelActive = wasShortTermModelActive; this.isDefaultBrightnessConfig = isDefaultBrightnessConfig; this.timestamp = timestamp; this.uniqueDisplayId = uniqueDisplayId; diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 99e709ea3fd8..7b560cecbf21 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -27,6 +27,8 @@ import android.view.DisplayAddress; import android.view.Surface; import android.view.SurfaceControl; +import com.android.server.display.mode.DisplayModeDirector; + import java.io.PrintWriter; /** diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index d9b350189fc4..75f8accde3a5 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -1260,7 +1260,7 @@ public class DisplayDeviceConfig { return mAmbientDarkeningPercentagesIdle; } - SensorData getAmbientLightSensor() { + public SensorData getAmbientLightSensor() { return mAmbientLightSensor; } @@ -2821,7 +2821,7 @@ public class DisplayDeviceConfig { /** * Uniquely identifies a Sensor, with the combination of Type and Name. */ - static class SensorData { + public static class SensorData { public String type; public String name; public float minRefreshRate = 0.0f; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 6eb465e1049e..214c5916a8f8 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -152,6 +152,7 @@ import com.android.server.UiThread; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.DisplayDeviceConfig.SensorData; import com.android.server.display.layout.Layout; +import com.android.server.display.mode.DisplayModeDirector; import com.android.server.display.utils.SensorUtils; import com.android.server.input.InputManagerInternal; import com.android.server.wm.SurfaceAnimationThread; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 9917bfc3aca6..da8e6a636d72 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1538,10 +1538,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // user, or is a temporary adjustment. boolean userInitiatedChange = (Float.isNaN(brightnessState)) && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged); - boolean hadUserBrightnessPoint = false; + boolean wasShortTermModelActive = false; // Configure auto-brightness. if (mAutomaticBrightnessController != null) { - hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints(); + wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints(); mAutomaticBrightnessController.configure(autoBrightnessState, mBrightnessConfiguration, mLastUserSetScreenBrightness, @@ -1555,7 +1555,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED); if (mBrightnessTracker != null) { - mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration); + mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null + && mBrightnessConfiguration.shouldCollectColorSamples()); } boolean updateScreenBrightnessSetting = false; @@ -1823,7 +1824,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call userInitiatedChange = false; } notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange, - hadUserBrightnessPoint); + wasShortTermModelActive); } // We save the brightness info *after* the brightness setting has been changed and @@ -1865,7 +1866,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mTempBrightnessEvent.setRbcStrength(mCdsi != null ? mCdsi.getReduceBrightColorsStrength() : -1); mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor); - mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint); + mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive); // Temporary is what we use during slider interactions. We avoid logging those so that // we don't spam logcat when the slider is being used. boolean tempToTempTransition = @@ -2634,7 +2635,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated, - boolean hadUserDataPoint) { + boolean wasShortTermModelActive) { final float brightnessInNits = convertToNits(brightness); if (mUseAutoBrightness && brightnessInNits >= 0.0f && mAutomaticBrightnessController != null && mBrightnessTracker != null) { @@ -2645,7 +2646,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call ? mPowerRequest.screenLowPowerBrightnessFactor : 1.0f; mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated, - powerFactor, hadUserDataPoint, + powerFactor, wasShortTermModelActive, mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId, mAutomaticBrightnessController.getLastSensorValues(), mAutomaticBrightnessController.getLastSensorTimestamps()); diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index f49419cf789e..a2a53e3f7549 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -1250,10 +1250,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // user, or is a temporary adjustment. boolean userInitiatedChange = (Float.isNaN(brightnessState)) && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged); - boolean hadUserBrightnessPoint = false; + boolean wasShortTermModelActive = false; // Configure auto-brightness. if (mAutomaticBrightnessController != null) { - hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints(); + wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints(); mAutomaticBrightnessController.configure(autoBrightnessState, mBrightnessConfiguration, mDisplayBrightnessController.getLastUserSetScreenBrightness(), @@ -1267,7 +1267,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED); if (mBrightnessTracker != null) { - mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration); + mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null + && mBrightnessConfiguration.shouldCollectColorSamples()); } boolean updateScreenBrightnessSetting = false; @@ -1537,7 +1538,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal userInitiatedChange = false; } notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange, - hadUserBrightnessPoint); + wasShortTermModelActive); } // We save the brightness info *after* the brightness setting has been changed and @@ -1579,7 +1580,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mTempBrightnessEvent.setRbcStrength(mCdsi != null ? mCdsi.getReduceBrightColorsStrength() : -1); mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor); - mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint); + mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive); mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState .getDisplayBrightnessStrategyName()); // Temporary is what we use during slider interactions. We avoid logging those so that @@ -2220,7 +2221,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated, - boolean hadUserDataPoint) { + boolean wasShortTermModelActive) { final float brightnessInNits = convertToNits(brightness); if (mUseAutoBrightness && brightnessInNits >= 0.0f && mAutomaticBrightnessController != null && mBrightnessTracker != null) { @@ -2231,7 +2232,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal ? mPowerRequest.screenLowPowerBrightnessFactor : 1.0f; mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated, - powerFactor, hadUserDataPoint, + powerFactor, wasShortTermModelActive, mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId, mAutomaticBrightnessController.getLastSensorValues(), mAutomaticBrightnessController.getLastSensorTimestamps()); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 8f52c97c1f87..58c50346a38f 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -46,6 +46,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; +import com.android.server.display.mode.DisplayModeDirector; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index fc90db66c1cb..78c597ea6e53 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -33,6 +33,7 @@ import android.view.Surface; import android.view.SurfaceControl; import com.android.server.display.layout.Layout; +import com.android.server.display.mode.DisplayModeDirector; import com.android.server.wm.utils.InsetUtils; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS index 8e3460175158..fcaa95731408 100644 --- a/services/core/java/com/android/server/display/OWNERS +++ b/services/core/java/com/android/server/display/OWNERS @@ -4,5 +4,7 @@ hackbod@google.com ogunwale@google.com santoscordon@google.com flc@google.com +wilczynskip@google.com +brup@google.com per-file ColorDisplayService.java=christyfranks@google.com diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 3e67f0a466d7..2ce7690ecc3f 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -36,6 +36,7 @@ import android.view.SurfaceControl; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.display.mode.DisplayModeDirector; import java.io.PrintWriter; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 31f5ab7dfe98..24d5ca402dd0 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.display; +package com.android.server.display.mode; import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED; import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; @@ -67,6 +67,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; +import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.utils.AmbientFilter; import com.android.server.display.utils.AmbientFilterFactory; import com.android.server.display.utils.SensorUtils; @@ -214,6 +215,9 @@ public class DisplayModeDirector { mUdfpsObserver.observe(); } + /** + * Enables or disables component logging + */ public void setLoggingEnabled(boolean loggingEnabled) { if (mLoggingEnabled == loggingEnabled) { return; @@ -1574,7 +1578,10 @@ public class DisplayModeDirector { } } - final class AppRequestObserver { + /** + * Responsible for keeping track of app requested refresh rates per display + */ + public final class AppRequestObserver { private final SparseArray<Display.Mode> mAppRequestedModeByDisplay; private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay; @@ -1583,6 +1590,9 @@ public class DisplayModeDirector { mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>(); } + /** + * Sets refresh rates from app request + */ public void setAppRequest(int displayId, int modeId, float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) { synchronized (mLock) { @@ -1665,7 +1675,7 @@ public class DisplayModeDirector { return null; } - public void dumpLocked(PrintWriter pw) { + private void dumpLocked(PrintWriter pw) { pw.println(" AppRequestObserver"); pw.println(" mAppRequestedModeByDisplay:"); for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) { @@ -1794,7 +1804,7 @@ public class DisplayModeDirector { */ @VisibleForTesting public class BrightnessObserver implements DisplayManager.DisplayListener { - private final static int LIGHT_SENSOR_RATE_MS = 250; + private static final int LIGHT_SENSOR_RATE_MS = 250; private int[] mLowDisplayBrightnessThresholds; private int[] mLowAmbientBrightnessThresholds; private int[] mHighDisplayBrightnessThresholds; @@ -2019,7 +2029,7 @@ public class DisplayModeDirector { return mLowAmbientBrightnessThresholds; } - public void observe(SensorManager sensorManager) { + private void observe(SensorManager sensorManager) { mSensorManager = sensorManager; mBrightness = getBrightness(Display.DEFAULT_DISPLAY); @@ -2064,11 +2074,11 @@ public class DisplayModeDirector { mDeviceConfigDisplaySettings.startListening(); mInjector.registerDisplayListener(this, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_CHANGED | - DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); + DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); } - public void setLoggingEnabled(boolean loggingEnabled) { + private void setLoggingEnabled(boolean loggingEnabled) { if (mLoggingEnabled == loggingEnabled) { return; } @@ -2076,7 +2086,8 @@ public class DisplayModeDirector { mLightSensorListener.setLoggingEnabled(loggingEnabled); } - public void onRefreshRateSettingChangedLocked(float min, float max) { + @VisibleForTesting + void onRefreshRateSettingChangedLocked(float min, float max) { boolean changeable = (max - min > 1f && max > 60f); if (mRefreshRateChangeable != changeable) { mRefreshRateChangeable = changeable; @@ -2089,14 +2100,14 @@ public class DisplayModeDirector { } } - public void onLowPowerModeEnabledLocked(boolean b) { + private void onLowPowerModeEnabledLocked(boolean b) { if (mLowPowerModeEnabled != b) { mLowPowerModeEnabled = b; updateSensorStatus(); } } - public void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds, + private void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds, int[] ambientThresholds) { if (displayThresholds != null && ambientThresholds != null && displayThresholds.length == ambientThresholds.length) { @@ -2123,7 +2134,7 @@ public class DisplayModeDirector { } } - public void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds, + private void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds, int[] ambientThresholds) { if (displayThresholds != null && ambientThresholds != null && displayThresholds.length == ambientThresholds.length) { @@ -2150,7 +2161,7 @@ public class DisplayModeDirector { } } - public void dumpLocked(PrintWriter pw) { + void dumpLocked(PrintWriter pw) { pw.println(" BrightnessObserver"); pw.println(" mAmbientLux: " + mAmbientLux); pw.println(" mBrightness: " + mBrightness); @@ -2400,7 +2411,7 @@ public class DisplayModeDirector { } @VisibleForTesting - public void setDefaultDisplayState(int state) { + void setDefaultDisplayState(int state) { if (mLoggingEnabled) { Slog.d(TAG, "setDefaultDisplayState: mDefaultDisplayState = " + mDefaultDisplayState + ", state = " + state); @@ -2475,7 +2486,7 @@ public class DisplayModeDirector { } private final class LightSensorEventListener implements SensorEventListener { - final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS; + private static final int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS; private float mLastSensorData; private long mTimestamp; private boolean mLoggingEnabled; @@ -2876,10 +2887,10 @@ public class DisplayModeDirector { } final int hbmMode = info.highBrightnessMode; - final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF && - info.adjustedBrightness > info.highBrightnessTransitionPoint; - if (hbmMode == mHbmMode.get(displayId) && - isHbmActive == mHbmActive.get(displayId)) { + final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF + && info.adjustedBrightness > info.highBrightnessTransitionPoint; + if (hbmMode == mHbmMode.get(displayId) + && isHbmActive == mHbmActive.get(displayId)) { // no change, ignore. return; } @@ -2901,7 +2912,7 @@ public class DisplayModeDirector { Vote vote = null; if (mHbmActive.get(displayId, false)) { final int hbmMode = - mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); + mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) { // Device resource properties take priority over DisplayDeviceConfig if (mRefreshRateInHbmSunlight > 0) { @@ -2909,7 +2920,7 @@ public class DisplayModeDirector { mRefreshRateInHbmSunlight); } else { final List<RefreshRateLimitation> limits = - mDisplayManagerInternal.getRefreshRateLimitations(displayId); + mDisplayManagerInternal.getRefreshRateLimitations(displayId); for (int i = 0; limits != null && i < limits.size(); i++) { final RefreshRateLimitation limitation = limits.get(i); if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) { @@ -2919,8 +2930,8 @@ public class DisplayModeDirector { } } } - } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR && - mRefreshRateInHbmHdr > 0) { + } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR + && mRefreshRateInHbmHdr > 0) { // HBM for HDR vote isn't supported through DisplayDeviceConfig yet, so look for // a vote from Device properties vote = Vote.forPhysicalRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr); @@ -2988,9 +2999,6 @@ public class DisplayModeDirector { } private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener { - public DeviceConfigDisplaySettings() { - } - public void startListening() { mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, BackgroundThread.getExecutor(), this); @@ -3001,8 +3009,8 @@ public class DisplayModeDirector { */ public int[] getLowDisplayBrightnessThresholds() { return getIntArrayProperty( - DisplayManager.DeviceConfig. - KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS); + DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS); } /* @@ -3010,8 +3018,8 @@ public class DisplayModeDirector { */ public int[] getLowAmbientBrightnessThresholds() { return getIntArrayProperty( - DisplayManager.DeviceConfig. - KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS); + DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS); } public int getRefreshRateInLowZone() { diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 3bc4b543750e..3e2efdd7db85 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -158,15 +158,14 @@ final class DreamController { final DreamRecord oldDream = mCurrentDream; mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock); if (oldDream != null) { - if (!oldDream.mWakingGently) { - // We will stop these previous dreams once the new dream is started. - mPreviousDreams.add(oldDream); - } else if (Objects.equals(oldDream.mName, mCurrentDream.mName)) { + if (Objects.equals(oldDream.mName, mCurrentDream.mName)) { // We are attempting to start a dream that is currently waking up gently. // Let's silently stop the old instance here to clear the dream state. // This should happen after the new mCurrentDream is set to avoid announcing // a "dream stopped" state. stopDreamInstance(/* immediately */ true, "restarting same dream", oldDream); + } else { + mPreviousDreams.add(oldDream); } } diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java index da65f27688d8..2efb0beaa567 100644 --- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java +++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java @@ -24,13 +24,14 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.util.Log; import android.view.inputmethod.ImeTracker; import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.util.FrameworkStatsLog; -import com.android.internal.view.IImeTracker; import java.io.PrintWriter; import java.time.Instant; @@ -53,7 +54,7 @@ import java.util.concurrent.atomic.AtomicInteger; @SuppressWarnings("GuardedBy") public final class ImeTrackerService extends IImeTracker.Stub { - static final String TAG = "ImeTrackerService"; + private static final String TAG = ImeTracker.TAG; /** The threshold in milliseconds after which a history entry is considered timed out. */ private static final long TIMEOUT_MS = 10_000; @@ -71,67 +72,71 @@ public final class ImeTrackerService extends IImeTracker.Stub { @NonNull @Override - public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin, - @SoftInputShowHideReason int reason) { - final IBinder binder = new Binder(); - final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW, - ImeTracker.STATUS_RUN, origin, reason); + public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid, + @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { + final var binder = new Binder(); + final var token = new ImeTracker.Token(binder, tag); + final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN, + origin, reason); mHistory.addEntry(binder, entry); // Register a delayed task to handle the case where the new entry times out. mHandler.postDelayed(() -> { synchronized (ImeTrackerService.this) { - mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); + mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); } }, TIMEOUT_MS); - return binder; + return token; } @NonNull @Override - public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin, - @SoftInputShowHideReason int reason) { - final IBinder binder = new Binder(); - final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE, - ImeTracker.STATUS_RUN, origin, reason); + public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid, + @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { + final var binder = new Binder(); + final var token = new ImeTracker.Token(binder, tag); + final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN, + origin, reason); mHistory.addEntry(binder, entry); // Register a delayed task to handle the case where the new entry times out. mHandler.postDelayed(() -> { synchronized (ImeTrackerService.this) { - mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); + mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); } }, TIMEOUT_MS); - return binder; + return token; } @Override - public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { - final History.Entry entry = mHistory.getEntry(statsToken); + public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) { + final var entry = mHistory.getEntry(binder); if (entry == null) return; entry.mPhase = phase; } @Override - public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + public synchronized void onFailed(@NonNull ImeTracker.Token statsToken, + @ImeTracker.Phase int phase) { mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase); } @Override - public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken, + @ImeTracker.Phase int phase) { mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase); } @Override - public synchronized void onShown(@NonNull IBinder statsToken) { + public synchronized void onShown(@NonNull ImeTracker.Token statsToken) { mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); } @Override - public synchronized void onHidden(@NonNull IBinder statsToken) { + public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) { mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); } @@ -141,9 +146,9 @@ public final class ImeTrackerService extends IImeTracker.Stub { * @param statsToken the token corresponding to the current IME request. * @param requestWindowName the name of the window that created the IME request. */ - public synchronized void onImmsUpdate(@NonNull IBinder statsToken, + public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken, @NonNull String requestWindowName) { - final History.Entry entry = mHistory.getEntry(statsToken); + final var entry = mHistory.getEntry(statsToken.getBinder()); if (entry == null) return; entry.mRequestWindowName = requestWindowName; @@ -181,17 +186,17 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** Latest entry sequence number. */ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); - /** Adds a live entry. */ + /** Adds a live entry corresponding to the given IME tracking token's binder. */ @GuardedBy("ImeTrackerService.this") - private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) { - mLiveEntries.put(statsToken, entry); + private void addEntry(@NonNull IBinder binder, @NonNull Entry entry) { + mLiveEntries.put(binder, entry); } - /** Gets the entry corresponding to the given IME tracking token, if it exists. */ + /** Gets the entry corresponding to the given IME tracking token's binder, if it exists. */ @Nullable @GuardedBy("ImeTrackerService.this") - private Entry getEntry(@NonNull IBinder statsToken) { - return mLiveEntries.get(statsToken); + private Entry getEntry(@NonNull IBinder binder) { + return mLiveEntries.get(binder); } /** @@ -204,10 +209,21 @@ public final class ImeTrackerService extends IImeTracker.Stub { * (or {@link ImeTracker#PHASE_NOT_SET} otherwise). */ @GuardedBy("ImeTrackerService.this") - private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status, - @ImeTracker.Phase int phase) { - final Entry entry = mLiveEntries.remove(statsToken); - if (entry == null) return; + private void setFinished(@NonNull ImeTracker.Token statsToken, + @ImeTracker.Status int status, @ImeTracker.Phase int phase) { + final var entry = mLiveEntries.remove(statsToken.getBinder()); + if (entry == null) { + // This will be unconditionally called through the postDelayed above to handle + // potential timeouts, and is thus intentionally dropped to avoid having to manually + // save and remove the registered callback. Only timeout calls are expected. + if (status != ImeTracker.STATUS_TIMEOUT) { + Log.i(TAG, statsToken.getTag() + + ": setFinished on previously finished token at " + + ImeTracker.Debug.phaseToString(phase) + " with " + + ImeTracker.Debug.statusToString(status)); + } + return; + } entry.mDuration = System.currentTimeMillis() - entry.mStartTime; entry.mStatus = status; @@ -216,6 +232,13 @@ public final class ImeTrackerService extends IImeTracker.Stub { entry.mPhase = phase; } + if (status == ImeTracker.STATUS_TIMEOUT) { + // All events other than timeouts are already logged in the client-side ImeTracker. + Log.i(TAG, statsToken.getTag() + ": setFinished at " + + ImeTracker.Debug.phaseToString(entry.mPhase) + " with " + + ImeTracker.Debug.statusToString(status)); + } + // Remove excess entries overflowing capacity (plus one for the new entry). while (mEntries.size() >= CAPACITY) { mEntries.remove(); @@ -232,21 +255,22 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** Dumps the contents of the circular buffer. */ @GuardedBy("ImeTrackerService.this") private void dump(@NonNull PrintWriter pw, @NonNull String prefix) { - final DateTimeFormatter formatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) - .withZone(ZoneId.systemDefault()); + final var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) + .withZone(ZoneId.systemDefault()); pw.print(prefix); - pw.println("ImeTrackerService#History.mLiveEntries:"); + pw.println("ImeTrackerService#History.mLiveEntries: " + + mLiveEntries.size() + " elements"); - for (final Entry entry: mLiveEntries.values()) { + for (final var entry: mLiveEntries.values()) { dumpEntry(entry, pw, prefix, formatter); } pw.print(prefix); - pw.println("ImeTrackerService#History.mEntries:"); + pw.println("ImeTrackerService#History.mEntries: " + + mEntries.size() + " elements"); - for (final Entry entry: mEntries) { + for (final var entry: mEntries) { dumpEntry(entry, pw, prefix, formatter); } } @@ -255,34 +279,22 @@ public final class ImeTrackerService extends IImeTracker.Stub { private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw, @NonNull String prefix, @NonNull DateTimeFormatter formatter) { pw.print(prefix); - pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":"); - - pw.print(prefix); - pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime))); - - pw.print(prefix); - pw.println(" duration=" + entry.mDuration + "ms"); + pw.print(" #" + entry.mSequenceNumber); + pw.print(" " + ImeTracker.Debug.typeToString(entry.mType)); + pw.print(" - " + ImeTracker.Debug.statusToString(entry.mStatus)); + pw.print(" - " + entry.mTag); + pw.println(" (" + entry.mDuration + "ms):"); pw.print(prefix); - pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType)); + pw.print(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime))); + pw.println(" " + ImeTracker.Debug.originToString(entry.mOrigin)); pw.print(prefix); - pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus)); + pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(entry.mReason)); + pw.println(" " + ImeTracker.Debug.phaseToString(entry.mPhase)); pw.print(prefix); - pw.print(" origin=" - + ImeTracker.Debug.originToString(entry.mOrigin)); - - pw.print(prefix); - pw.print(" reason=" - + InputMethodDebug.softInputDisplayReasonToString(entry.mReason)); - - pw.print(prefix); - pw.print(" phase=" - + ImeTracker.Debug.phaseToString(entry.mPhase)); - - pw.print(prefix); - pw.print(" requestWindowName=" + entry.mRequestWindowName); + pw.println(" requestWindowName=" + entry.mRequestWindowName); } /** A history entry. */ @@ -291,6 +303,10 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** The entry's sequence number in the history. */ private final int mSequenceNumber = sSequenceNumber.getAndIncrement(); + /** Logging tag, of the shape "component:random_hexadecimal". */ + @NonNull + private final String mTag; + /** Uid of the client that requested the IME. */ private final int mUid; @@ -323,13 +339,15 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** * Name of the window that created the IME request. * - * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}. + * Note: This is later set through {@link #onImmsUpdate}. */ @NonNull private String mRequestWindowName = "not set"; - private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status, - @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { + private Entry(@NonNull String tag, int uid, @ImeTracker.Type int type, + @ImeTracker.Status int status, @ImeTracker.Origin int origin, + @SoftInputShowHideReason int reason) { + mTag = tag; mUid = uid; mType = type; mStatus = status; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 0ea64abbe548..8c7658e53dcd 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -178,6 +178,14 @@ public abstract class InputMethodManagerInternal { public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId); /** + * Switch the keyboard layout in response to a keyboard shortcut. + * + * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the + * previous subtype. + */ + public abstract void switchKeyboardLayout(int direction); + + /** * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. */ private static final InputMethodManagerInternal NOP = @@ -256,6 +264,10 @@ public abstract class InputMethodManagerInternal { @Override public void maybeFinishStylusHandwriting() { } + + @Override + public void switchKeyboardLayout(int direction) { + } }; /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8ef4e4afae9b..d397a0c5e0bc 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -145,6 +145,7 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AndroidFuture; import com.android.internal.inputmethod.DirectBootAwareness; import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethod; @@ -168,7 +169,6 @@ import com.android.internal.os.TransferPipe; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; -import com.android.internal.view.IImeTracker; import com.android.internal.view.IInputMethodManager; import com.android.server.AccessibilityManagerInternal; import com.android.server.EventLogTags; @@ -532,6 +532,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub /** * The client that is currently bound to an input method. */ + @Nullable private ClientState mCurClient; /** @@ -557,11 +558,26 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub int mCurFocusedWindowSoftInputMode; /** - * The client by which {@link #mCurFocusedWindow} was reported. + * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an + * IME-focusable window gained focus (without necessarily starting an input connection), + * while {@link #mCurClient} only gets updated when we actually start an input connection. + * + * @see #mCurFocusedWindow */ + @Nullable ClientState mCurFocusedWindowClient; /** + * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from + * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs + * from {@link #mCurClient}. + * + * @see #mCurFocusedWindow + */ + @Nullable + EditorInfo mCurFocusedWindowEditorInfo; + + /** * The {@link IRemoteInputConnection} last provided by the current client. */ IRemoteInputConnection mCurInputConnection; @@ -580,6 +596,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub /** * The {@link EditorInfo} last provided by the current client. */ + @Nullable EditorInfo mCurEditorInfo; /** @@ -1393,6 +1410,22 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override + public void onPackageDataCleared(String packageName, int uid) { + boolean changed = false; + for (InputMethodInfo imi : mMethodList) { + if (imi.getPackageName().equals(packageName)) { + mAdditionalSubtypeMap.remove(imi.getId()); + changed = true; + } + } + if (changed) { + AdditionalSubtypeUtils.save( + mAdditionalSubtypeMap, mMethodMap, mSettings.getCurrentUserId()); + mChangedPackages.add(packageName); + } + } + + @Override public void onFinishPackageChanges() { onFinishPackageChangesInternal(); clearPackageChangeState(); @@ -2062,7 +2095,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, - methodList, directBootAwareness); + methodList, directBootAwareness, mSettings.getEnabledInputMethodNames()); settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */); } // filter caller's access to input methods @@ -2265,6 +2298,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (mCurFocusedWindowClient == cs) { mCurFocusedWindowClient = null; + mCurFocusedWindowEditorInfo = null; } } } @@ -3245,7 +3279,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub notifyInputMethodSubtypeChangedLocked(userId, info, null); return; } - if (newSubtype != oldSubtype) { + if (!newSubtype.equals(oldSubtype)) { setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { @@ -3453,10 +3487,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { // Create statsToken is none exists. if (statsToken == null) { - // TODO(b/261565259): to avoid using null, add package name in ClientState - final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null; - final int uid = mCurClient != null ? mCurClient.mUid : -1; - statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid, + statsToken = createStatsTokenForFocusedClient(true /* show */, ImeTracker.ORIGIN_SERVER_START_INPUT, reason); } @@ -3530,17 +3561,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { // Create statsToken is none exists. if (statsToken == null) { - // TODO(b/261565259): to avoid using null, add package name in ClientState - final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null; - final int uid; - if (mCurClient != null) { - uid = mCurClient.mUid; - } else if (mCurFocusedWindowClient != null) { - uid = mCurFocusedWindowClient.mUid; - } else { - uid = -1; - } - statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid, + statsToken = createStatsTokenForFocusedClient(false /* show */, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason); } @@ -3775,6 +3796,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mCurFocusedWindow = windowToken; mCurFocusedWindowSoftInputMode = softInputMode; mCurFocusedWindowClient = cs; + mCurFocusedWindowEditorInfo = editorInfo; mCurPerceptible = true; // We want to start input before showing the IME, but after closing @@ -4044,15 +4066,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!calledWithValidTokenLocked(token)) { return false; } - final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( - onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype); - if (nextSubtype == null) { - return false; - } - setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(), - nextSubtype.mSubtypeId); - return true; + return switchToNextInputMethodLocked(token, onlyCurrentIme); + } + } + + @GuardedBy("ImfLock.class") + private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) { + final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( + onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype); + if (nextSubtype == null) { + return false; } + setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(), + nextSubtype.mSubtypeId); + return true; } @BinderThread @@ -4130,7 +4157,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, - methodList, DirectBootAwareness.AUTO); + methodList, DirectBootAwareness.AUTO, mSettings.getEnabledInputMethodNames()); final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId, false); settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap, @@ -4703,13 +4730,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mWindowManagerInternal.onToggleImeRequested( show, mCurFocusedWindow, requestToken, mCurTokenDisplayId); mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( - mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName, + mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName, mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode, info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName, info.imeSurfaceParentName)); if (statsToken != null) { - mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName); + mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName); } } @@ -5027,7 +5054,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub static void queryInputMethodServicesInternal(Context context, @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap, ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, - @DirectBootAwareness int directBootAwareness) { + @DirectBootAwareness int directBootAwareness, List<String> enabledInputMethodList) { final Context userAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); @@ -5060,6 +5087,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub methodList.ensureCapacity(services.size()); methodMap.ensureCapacity(services.size()); + filterInputMethodServices(additionalSubtypeMap, methodMap, methodList, + enabledInputMethodList, userAwareContext, services); + } + + static void filterInputMethodServices( + ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap, + ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, + List<String> enabledInputMethodList, Context userAwareContext, + List<ResolveInfo> services) { + final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>(); + for (int i = 0; i < services.size(); ++i) { ResolveInfo ri = services.get(i); ServiceInfo si = ri.serviceInfo; @@ -5079,10 +5117,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (imi.isVrOnly()) { continue; // Skip VR-only IME, which isn't supported for now. } - methodList.add(imi); - methodMap.put(imi.getId(), imi); - if (DEBUG) { - Slog.d(TAG, "Found an input method " + imi); + final String packageName = si.packageName; + // only include IMEs which are from the system, enabled, or below the threshold + if (si.applicationInfo.isSystemApp() || enabledInputMethodList.contains(imi.getId()) + || imiPackageCount.getOrDefault(packageName, 0) + < InputMethodInfo.MAX_IMES_PER_PACKAGE) { + imiPackageCount.put(packageName, + 1 + imiPackageCount.getOrDefault(packageName, 0)); + + methodList.add(imi); + methodMap.put(imi.getId(), imi); + if (DEBUG) { + Slog.d(TAG, "Found an input method " + imi); + } + } else if (DEBUG) { + Slog.d(TAG, "Found an input method, but ignored due threshold: " + imi); } } catch (Exception e) { Slog.wtf(TAG, "Unable to load input method " + imeId, e); @@ -5104,7 +5153,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mMyPackageMonitor.clearKnownImePackageNamesLocked(); queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(), - mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO); + mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO, + mSettings.getEnabledInputMethodNames()); // Construct the set of possible IME packages for onPackageChanged() to avoid false // negatives when the package state remains to be the same but only the component state is @@ -5163,7 +5213,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub reenableMinimumNonAuxSystemImes); final int numImes = defaultEnabledIme.size(); for (int i = 0; i < numImes; ++i) { - final InputMethodInfo imi = defaultEnabledIme.get(i); + final InputMethodInfo imi = defaultEnabledIme.get(i); if (DEBUG) { Slog.d(TAG, "--- enable ime = " + imi); } @@ -5463,7 +5513,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList, DirectBootAwareness.AUTO); + methodMap, methodList, DirectBootAwareness.AUTO, + mSettings.getEnabledInputMethodNames()); return methodMap; } @@ -5719,6 +5770,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mHandler.removeMessages(MSG_FINISH_HANDWRITING); mHandler.obtainMessage(MSG_FINISH_HANDWRITING).sendToTarget(); } + + @Override + public void switchKeyboardLayout(int direction) { + synchronized (ImfLock.class) { + if (direction > 0) { + switchToNextInputMethodLocked(null /* token */, true /* onlyCurrentIme */); + } else { + // TODO(b/258853866): Support backwards switching. + } + } + } } @BinderThread @@ -5751,9 +5813,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // We cannot simply distinguish a bad IME that reports an arbitrary package name from // an unfortunate IME whose internal state is already obsolete due to the asynchronous // nature of our system. Let's compare it with our internal record. - if (!TextUtils.equals(mCurEditorInfo.packageName, packageName)) { + final var curPackageName = mCurEditorInfo != null + ? mCurEditorInfo.packageName : null; + if (!TextUtils.equals(curPackageName, packageName)) { Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName=" - + mCurEditorInfo.packageName + " packageName=" + packageName); + + curPackageName + " packageName=" + packageName); return null; } // This user ID can never bee spoofed. @@ -6421,7 +6485,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList, DirectBootAwareness.AUTO); + methodMap, methodList, DirectBootAwareness.AUTO, + mSettings.getEnabledInputMethodNames()); final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId, false); @@ -6514,6 +6579,30 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return mImeTrackerService; } + /** + * Creates an IME request tracking token for the current focused client. + * + * @param show whether this is a show or a hide request. + * @param origin the origin of the IME request. + * @param reason the reason why the IME request was created. + */ + @NonNull + private ImeTracker.Token createStatsTokenForFocusedClient(boolean show, + @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { + final int uid = mCurFocusedWindowClient != null + ? mCurFocusedWindowClient.mUid + : -1; + final var packageName = mCurFocusedWindowEditorInfo != null + ? mCurFocusedWindowEditorInfo.packageName + : "uid(" + uid + ")"; + + if (show) { + return ImeTracker.forLogging().onRequestShow(packageName, uid, origin, reason); + } else { + return ImeTracker.forLogging().onRequestHide(packageName, uid, origin, reason); + } + } + private static final class InputMethodPrivilegedOperationsImpl extends IInputMethodPrivilegedOperations.Stub { private final InputMethodManagerService mImms; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 559eb5341632..17536fcb820e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -438,6 +438,15 @@ final class InputMethodUtils { mSubtypeSplitter); } + List<String> getEnabledInputMethodNames() { + List<String> result = new ArrayList<>(); + for (Pair<String, ArrayList<String>> pair : + getEnabledInputMethodsAndSubtypeListLocked()) { + result.add(pair.first); + } + return result; + } + void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { if (reloadInputMethodStr) { getEnabledInputMethodsStr(); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 4013468481ab..5a832b78487c 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -420,7 +420,7 @@ public class LockSettingsService extends ILockSettings.Stub { try (LockscreenCredential unifiedProfilePassword = generateRandomProfilePassword()) { setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId, /* isLockTiedToParent= */ true); - tieProfileLockToParent(profileUserId, unifiedProfilePassword); + tieProfileLockToParent(profileUserId, parentId, unifiedProfilePassword); mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword); } } @@ -1065,18 +1065,7 @@ public class LockSettingsService extends ILockSettings.Stub { mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsHave"); } - private static final String[] UNPROTECTED_SETTINGS = { - // These three LOCK_PATTERN_* settings have traditionally been readable via the public API - // android.provider.Settings.{System,Secure}.getString() without any permission. - Settings.Secure.LOCK_PATTERN_ENABLED, - Settings.Secure.LOCK_PATTERN_VISIBLE, - Settings.Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED, - }; - private final void checkDatabaseReadPermission(String requestedKey, int userId) { - if (ArrayUtils.contains(UNPROTECTED_SETTINGS, requestedKey)) { - return; - } if (!hasPermission(PERMISSION)) { throw new SecurityException("uid=" + getCallingUid() + " needs permission " + PERMISSION + " to read " + requestedKey + " for user " + userId); @@ -1190,9 +1179,6 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public boolean getBoolean(String key, boolean defaultValue, int userId) { checkDatabaseReadPermission(key, userId); - if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) { - return getCredentialTypeInternal(userId) == CREDENTIAL_TYPE_PATTERN; - } return mStorage.getBoolean(key, defaultValue, userId); } @@ -1887,34 +1873,43 @@ public class LockSettingsService extends ILockSettings.Stub { } @VisibleForTesting /** Note: this method is overridden in unit tests */ - protected void tieProfileLockToParent(int userId, LockscreenCredential password) { - if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId); + protected void tieProfileLockToParent(int profileUserId, int parentUserId, + LockscreenCredential password) { + if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + profileUserId); final byte[] iv; final byte[] ciphertext; + final long parentSid; + try { + parentSid = getGateKeeperService().getSecureUserId(parentUserId); + } catch (RemoteException e) { + throw new IllegalStateException("Failed to talk to GateKeeper service", e); + } + try { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); keyGenerator.init(new SecureRandom()); SecretKey secretKey = keyGenerator.generateKey(); try { mJavaKeyStore.setEntry( - PROFILE_KEY_NAME_ENCRYPT + userId, + PROFILE_KEY_NAME_ENCRYPT + profileUserId, new java.security.KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); mJavaKeyStore.setEntry( - PROFILE_KEY_NAME_DECRYPT + userId, + PROFILE_KEY_NAME_DECRYPT + profileUserId, new java.security.KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) + .setBoundToSpecificSecureUserId(parentSid) .setUserAuthenticationValidityDurationSeconds(30) .build()); // Key imported, obtain a reference to it. SecretKey keyStoreEncryptionKey = (SecretKey) mJavaKeyStore.getKey( - PROFILE_KEY_NAME_ENCRYPT + userId, null); + PROFILE_KEY_NAME_ENCRYPT + profileUserId, null); Cipher cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); @@ -1923,7 +1918,7 @@ public class LockSettingsService extends ILockSettings.Stub { iv = cipher.getIV(); } finally { // The original key can now be discarded. - mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + userId); + mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + profileUserId); } } catch (UnrecoverableKeyException | BadPaddingException | IllegalBlockSizeException | KeyStoreException @@ -1933,7 +1928,7 @@ public class LockSettingsService extends ILockSettings.Stub { if (iv.length != PROFILE_KEY_IV_SIZE) { throw new IllegalArgumentException("Invalid iv length: " + iv.length); } - mStorage.writeChildProfileLock(userId, ArrayUtils.concat(iv, ciphertext)); + mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext)); } private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) { @@ -2045,7 +2040,7 @@ public class LockSettingsService extends ILockSettings.Stub { LockscreenCredential piUserDecryptedPassword = profileUserDecryptedPasswords.get(i); if (piUserId != -1 && piUserDecryptedPassword != null) { if (DEBUG) Slog.v(TAG, "Restore tied profile lock"); - tieProfileLockToParent(piUserId, piUserDecryptedPassword); + tieProfileLockToParent(piUserId, userId, piUserDecryptedPassword); } if (piUserDecryptedPassword != null) { piUserDecryptedPassword.zeroize(); diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index e592a2207095..d070b416c53c 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -780,6 +780,10 @@ class SyntheticPasswordManager { int slot = loadWeaverSlot(protectorId, userId); destroyState(WEAVER_SLOT_NAME, protectorId, userId); if (slot != INVALID_WEAVER_SLOT) { + if (!isWeaverAvailable()) { + Slog.e(TAG, "Cannot erase Weaver slot because Weaver is unavailable"); + return; + } Set<Integer> usedSlots = getUsedWeaverSlots(); if (!usedSlots.contains(slot)) { Slog.i(TAG, "Destroy weaver slot " + slot + " for user " + userId); diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java new file mode 100644 index 000000000000..58fdb57af8fa --- /dev/null +++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java @@ -0,0 +1,585 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO; +import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED; +import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothLeAudio; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.MediaRoute2Info; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; +import android.util.Slog; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Controls bluetooth routes and provides selected route override. + * + * <p>The controller offers similar functionality to {@link LegacyBluetoothRouteController} but does + * not support routes selection logic. Instead, relies on external clients to make a decision + * about currently selected route. + * + * <p>Selected route override should be used by {@link AudioManager} which is aware of Audio + * Policies. + */ +class AudioPoliciesBluetoothRouteController implements BluetoothRouteController { + private static final String TAG = "APBtRouteController"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_"; + private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_"; + + // Maps hardware address to BluetoothRouteInfo + private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>(); + private final List<BluetoothRouteInfo> mActiveRoutes = new ArrayList<>(); + + // Route type -> volume map + private final SparseIntArray mVolumeMap = new SparseIntArray(); + + private final Context mContext; + private final BluetoothAdapter mBluetoothAdapter; + private final BluetoothRoutesUpdatedListener mListener; + private final AudioManager mAudioManager; + private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener(); + + private final AdapterStateChangedReceiver mAdapterStateChangedReceiver = + new AdapterStateChangedReceiver(); + private final DeviceStateChangedReceiver mDeviceStateChangedReceiver = + new DeviceStateChangedReceiver(); + + private BluetoothA2dp mA2dpProfile; + private BluetoothHearingAid mHearingAidProfile; + private BluetoothLeAudio mLeAudioProfile; + + AudioPoliciesBluetoothRouteController(Context context, BluetoothAdapter btAdapter, + BluetoothRoutesUpdatedListener listener) { + mContext = context; + mBluetoothAdapter = btAdapter; + mListener = listener; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + buildBluetoothRoutes(); + } + + /** + * Registers listener to bluetooth status changes as the provided user. + * + * The registered receiver listens to {@link BluetoothA2dp#ACTION_ACTIVE_DEVICE_CHANGED} and + * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED } events for {@link BluetoothProfile#A2DP}, + * {@link BluetoothProfile#HEARING_AID}, and {@link BluetoothProfile#LE_AUDIO} bluetooth profiles. + * + * @param user {@code UserHandle} as which receiver is registered + */ + @Override + public void start(UserHandle user) { + mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP); + mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID); + mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO); + + IntentFilter adapterStateChangedIntentFilter = new IntentFilter(); + + adapterStateChangedIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + mContext.registerReceiverAsUser(mAdapterStateChangedReceiver, user, + adapterStateChangedIntentFilter, null, null); + + IntentFilter deviceStateChangedIntentFilter = new IntentFilter(); + + deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); + deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + deviceStateChangedIntentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); + deviceStateChangedIntentFilter.addAction( + BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); + deviceStateChangedIntentFilter.addAction( + BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); + deviceStateChangedIntentFilter.addAction( + BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); + + mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user, + deviceStateChangedIntentFilter, null, null); + } + + @Override + public void stop() { + mContext.unregisterReceiver(mAdapterStateChangedReceiver); + mContext.unregisterReceiver(mDeviceStateChangedReceiver); + } + + @Override + public boolean selectRoute(String deviceAddress) { + // Temporary no-op. + return false; + } + + /** + * Transfers to a given bluetooth route. + * The dedicated BT device with the route would be activated. + * + * @param routeId the id of the Bluetooth device. {@code null} denotes to clear the use of + * BT routes. + */ + @Override + public void transferTo(@Nullable String routeId) { + if (routeId == null) { + clearActiveDevices(); + return; + } + + BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId); + + if (btRouteInfo == null) { + Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId); + return; + } + + if (mBluetoothAdapter != null) { + mBluetoothAdapter.setActiveDevice(btRouteInfo.mBtDevice, ACTIVE_DEVICE_AUDIO); + } + } + + private BluetoothRouteInfo findBluetoothRouteWithRouteId(String routeId) { + if (routeId == null) { + return null; + } + for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) { + if (TextUtils.equals(btRouteInfo.mRoute.getId(), routeId)) { + return btRouteInfo; + } + } + return null; + } + + /** + * Clears the active device for all known profiles. + */ + private void clearActiveDevices() { + if (mBluetoothAdapter != null) { + mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO); + } + } + + private void buildBluetoothRoutes() { + mBluetoothRoutes.clear(); + Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); + if (bondedDevices != null) { + for (BluetoothDevice device : bondedDevices) { + if (device.isConnected()) { + BluetoothRouteInfo newBtRoute = createBluetoothRoute(device); + if (newBtRoute.mConnectedProfiles.size() > 0) { + mBluetoothRoutes.put(device.getAddress(), newBtRoute); + } + } + } + } + } + + @Nullable + @Override + public MediaRoute2Info getSelectedRoute() { + // For now, active routes can be multiple only when a pair of hearing aid devices is active. + // Let the first active device represent them. + return (mActiveRoutes.isEmpty() ? null : mActiveRoutes.get(0).mRoute); + } + + @NonNull + @Override + public List<MediaRoute2Info> getTransferableRoutes() { + List<MediaRoute2Info> routes = getAllBluetoothRoutes(); + for (BluetoothRouteInfo btRoute : mActiveRoutes) { + routes.remove(btRoute.mRoute); + } + return routes; + } + + @NonNull + @Override + public List<MediaRoute2Info> getAllBluetoothRoutes() { + List<MediaRoute2Info> routes = new ArrayList<>(); + List<String> routeIds = new ArrayList<>(); + + MediaRoute2Info selectedRoute = getSelectedRoute(); + if (selectedRoute != null) { + routes.add(selectedRoute); + routeIds.add(selectedRoute.getId()); + } + + for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) { + // A pair of hearing aid devices or having the same hardware address + if (routeIds.contains(btRoute.mRoute.getId())) { + continue; + } + routes.add(btRoute.mRoute); + routeIds.add(btRoute.mRoute.getId()); + } + return routes; + } + + /** + * Updates the volume for {@link AudioManager#getDevicesForStream(int) devices}. + * + * @return true if devices can be handled by the provider. + */ + @Override + public boolean updateVolumeForDevices(int devices, int volume) { + int routeType; + if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) { + routeType = MediaRoute2Info.TYPE_HEARING_AID; + } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP + | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES + | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) { + routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP; + } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) { + routeType = MediaRoute2Info.TYPE_BLE_HEADSET; + } else { + return false; + } + mVolumeMap.put(routeType, volume); + + boolean shouldNotify = false; + for (BluetoothRouteInfo btRoute : mActiveRoutes) { + if (btRoute.mRoute.getType() != routeType) { + continue; + } + btRoute.mRoute = new MediaRoute2Info.Builder(btRoute.mRoute) + .setVolume(volume) + .build(); + shouldNotify = true; + } + if (shouldNotify) { + notifyBluetoothRoutesUpdated(); + } + return true; + } + + private void notifyBluetoothRoutesUpdated() { + if (mListener != null) { + mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes()); + } + } + + private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) { + BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo(); + newBtRoute.mBtDevice = device; + + String routeId = device.getAddress(); + String deviceName = device.getName(); + if (TextUtils.isEmpty(deviceName)) { + deviceName = mContext.getResources().getText(R.string.unknownName).toString(); + } + int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP; + newBtRoute.mConnectedProfiles = new SparseBooleanArray(); + if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) { + newBtRoute.mConnectedProfiles.put(BluetoothProfile.A2DP, true); + } + if (mHearingAidProfile != null + && mHearingAidProfile.getConnectedDevices().contains(device)) { + newBtRoute.mConnectedProfiles.put(BluetoothProfile.HEARING_AID, true); + // Intentionally assign the same ID for a pair of devices to publish only one of them. + routeId = HEARING_AID_ROUTE_ID_PREFIX + mHearingAidProfile.getHiSyncId(device); + type = MediaRoute2Info.TYPE_HEARING_AID; + } + if (mLeAudioProfile != null + && mLeAudioProfile.getConnectedDevices().contains(device)) { + newBtRoute.mConnectedProfiles.put(BluetoothProfile.LE_AUDIO, true); + routeId = LE_AUDIO_ROUTE_ID_PREFIX + mLeAudioProfile.getGroupId(device); + type = MediaRoute2Info.TYPE_BLE_HEADSET; + } + + // Current volume will be set when connected. + newBtRoute.mRoute = new MediaRoute2Info.Builder(routeId, deviceName) + .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO) + .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK) + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED) + .setDescription(mContext.getResources().getText( + R.string.bluetooth_a2dp_audio_route_name).toString()) + .setType(type) + .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) + .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) + .setAddress(device.getAddress()) + .build(); + return newBtRoute; + } + + private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute, + @MediaRoute2Info.ConnectionState int state) { + if (btRoute == null) { + Slog.w(TAG, "setRouteConnectionState: route shouldn't be null"); + return; + } + if (btRoute.mRoute.getConnectionState() == state) { + return; + } + + MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.mRoute) + .setConnectionState(state); + builder.setType(btRoute.getRouteType()); + + if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) { + builder.setVolume(mVolumeMap.get(btRoute.getRouteType(), 0)); + } + btRoute.mRoute = builder.build(); + } + + private void addActiveRoute(BluetoothRouteInfo btRoute) { + if (btRoute == null) { + Slog.w(TAG, "addActiveRoute: btRoute is null"); + return; + } + if (DEBUG) { + Log.d(TAG, "Adding active route: " + btRoute.mRoute); + } + if (mActiveRoutes.contains(btRoute)) { + Slog.w(TAG, "addActiveRoute: btRoute is already added."); + return; + } + setRouteConnectionState(btRoute, STATE_CONNECTED); + mActiveRoutes.add(btRoute); + } + + private void removeActiveRoute(BluetoothRouteInfo btRoute) { + if (DEBUG) { + Log.d(TAG, "Removing active route: " + btRoute.mRoute); + } + if (mActiveRoutes.remove(btRoute)) { + setRouteConnectionState(btRoute, STATE_DISCONNECTED); + } + } + + private void clearActiveRoutesWithType(int type) { + if (DEBUG) { + Log.d(TAG, "Clearing active routes with type. type=" + type); + } + Iterator<BluetoothRouteInfo> iter = mActiveRoutes.iterator(); + while (iter.hasNext()) { + BluetoothRouteInfo btRoute = iter.next(); + if (btRoute.mRoute.getType() == type) { + iter.remove(); + setRouteConnectionState(btRoute, STATE_DISCONNECTED); + } + } + } + + private void addActiveDevices(BluetoothDevice device) { + // Let the given device be the first active device + BluetoothRouteInfo activeBtRoute = mBluetoothRoutes.get(device.getAddress()); + // This could happen if ACTION_ACTIVE_DEVICE_CHANGED is sent before + // ACTION_CONNECTION_STATE_CHANGED is sent. + if (activeBtRoute == null) { + activeBtRoute = createBluetoothRoute(device); + mBluetoothRoutes.put(device.getAddress(), activeBtRoute); + } + addActiveRoute(activeBtRoute); + + // A bluetooth route with the same route ID should be added. + for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) { + if (TextUtils.equals(btRoute.mRoute.getId(), activeBtRoute.mRoute.getId()) + && !TextUtils.equals(btRoute.mBtDevice.getAddress(), + activeBtRoute.mBtDevice.getAddress())) { + addActiveRoute(btRoute); + } + } + } + + private static class BluetoothRouteInfo { + private BluetoothDevice mBtDevice; + private MediaRoute2Info mRoute; + private SparseBooleanArray mConnectedProfiles; + + @MediaRoute2Info.Type + int getRouteType() { + // Let hearing aid profile have a priority. + if (mConnectedProfiles.get(BluetoothProfile.HEARING_AID, false)) { + return MediaRoute2Info.TYPE_HEARING_AID; + } + + if (mConnectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) { + return MediaRoute2Info.TYPE_BLE_HEADSET; + } + + return MediaRoute2Info.TYPE_BLUETOOTH_A2DP; + } + } + + // These callbacks run on the main thread. + private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener { + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + List<BluetoothDevice> activeDevices; + switch (profile) { + case BluetoothProfile.A2DP: + mA2dpProfile = (BluetoothA2dp) proxy; + // It may contain null. + activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.A2DP); + break; + case BluetoothProfile.HEARING_AID: + mHearingAidProfile = (BluetoothHearingAid) proxy; + activeDevices = mBluetoothAdapter.getActiveDevices( + BluetoothProfile.HEARING_AID); + break; + case BluetoothProfile.LE_AUDIO: + mLeAudioProfile = (BluetoothLeAudio) proxy; + activeDevices = mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO); + break; + default: + return; + } + for (BluetoothDevice device : proxy.getConnectedDevices()) { + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (btRoute == null) { + btRoute = createBluetoothRoute(device); + mBluetoothRoutes.put(device.getAddress(), btRoute); + } + if (activeDevices.contains(device)) { + addActiveRoute(btRoute); + } + } + notifyBluetoothRoutesUpdated(); + } + + @Override + public void onServiceDisconnected(int profile) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dpProfile = null; + break; + case BluetoothProfile.HEARING_AID: + mHearingAidProfile = null; + break; + case BluetoothProfile.LE_AUDIO: + mLeAudioProfile = null; + break; + default: + return; + } + } + } + + private class AdapterStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); + if (state == BluetoothAdapter.STATE_OFF + || state == BluetoothAdapter.STATE_TURNING_OFF) { + mBluetoothRoutes.clear(); + notifyBluetoothRoutesUpdated(); + } else if (state == BluetoothAdapter.STATE_ON) { + buildBluetoothRoutes(); + if (!mBluetoothRoutes.isEmpty()) { + notifyBluetoothRoutesUpdated(); + } + } + } + } + + private class DeviceStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + BluetoothDevice device = intent.getParcelableExtra( + BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class); + + switch (intent.getAction()) { + case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: + clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); + if (device != null) { + addActiveRoute(mBluetoothRoutes.get(device.getAddress())); + } + notifyBluetoothRoutesUpdated(); + break; + case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED: + clearActiveRoutesWithType(MediaRoute2Info.TYPE_HEARING_AID); + if (device != null) { + if (DEBUG) { + Log.d(TAG, "Setting active hearing aid devices. device=" + device); + } + + addActiveDevices(device); + } + notifyBluetoothRoutesUpdated(); + break; + case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED: + clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLE_HEADSET); + if (device != null) { + if (DEBUG) { + Log.d(TAG, "Setting active le audio devices. device=" + device); + } + + addActiveDevices(device); + } + notifyBluetoothRoutesUpdated(); + break; + case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: + handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device); + break; + case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED: + handleConnectionStateChanged(BluetoothProfile.HEARING_AID, intent, device); + break; + case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: + handleConnectionStateChanged(BluetoothProfile.LE_AUDIO, intent, device); + break; + } + } + + private void handleConnectionStateChanged(int profile, Intent intent, + BluetoothDevice device) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress()); + if (state == BluetoothProfile.STATE_CONNECTED) { + if (btRoute == null) { + btRoute = createBluetoothRoute(device); + if (btRoute.mConnectedProfiles.size() > 0) { + mBluetoothRoutes.put(device.getAddress(), btRoute); + notifyBluetoothRoutesUpdated(); + } + } else { + btRoute.mConnectedProfiles.put(profile, true); + } + } else if (state == BluetoothProfile.STATE_DISCONNECTING + || state == BluetoothProfile.STATE_DISCONNECTED) { + if (btRoute != null) { + btRoute.mConnectedProfiles.delete(profile); + if (btRoute.mConnectedProfiles.size() == 0) { + removeActiveRoute(mBluetoothRoutes.remove(device.getAddress())); + notifyBluetoothRoutesUpdated(); + } + } + } + } + } +} diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java index 08691d035f29..d4a118458952 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteController.java +++ b/services/core/java/com/android/server/media/BluetoothRouteController.java @@ -68,6 +68,17 @@ import java.util.Objects; */ void stop(); + + /** + * Selects the route with the given {@code deviceAddress}. + * + * @param deviceAddress The physical address of the device to select. May be null to unselect + * the currently selected device. + * @return Whether the selection succeeds. If the selection fails, the state of the instance + * remains unaltered. + */ + boolean selectRoute(@Nullable String deviceAddress); + /** * Transfers Bluetooth output to the given route. * @@ -145,6 +156,12 @@ import java.util.Objects; } @Override + public boolean selectRoute(String deviceAddress) { + // no op + return false; + } + + @Override public void transferTo(String routeId) { // no op } diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java new file mode 100644 index 000000000000..8bd6416a6ddb --- /dev/null +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; +import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO; +import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK; +import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; +import static android.media.MediaRoute2Info.TYPE_DOCK; +import static android.media.MediaRoute2Info.TYPE_HDMI; +import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; +import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; +import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioRoutesInfo; +import android.media.IAudioRoutesObserver; +import android.media.IAudioService; +import android.media.MediaRoute2Info; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; + +/** + * Controls device routes. + * + * <p>A device route is a system wired route, for example, built-in speaker, wired + * headsets and headphones, dock, hdmi, or usb devices. + * + * <p>Thread safe. + * + * @see SystemMediaRoute2Provider + */ +/* package */ final class DeviceRouteController { + + private static final String TAG = "WiredRoutesController"; + + private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE"; + + @NonNull + private final Context mContext; + @NonNull + private final AudioManager mAudioManager; + @NonNull + private final IAudioService mAudioService; + + @NonNull + private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; + @NonNull + private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver(); + + private int mDeviceVolume; + private MediaRoute2Info mDeviceRoute; + + /* package */ static DeviceRouteController createInstance(@NonNull Context context, + @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { + AudioManager audioManager = context.getSystemService(AudioManager.class); + IAudioService audioService = IAudioService.Stub.asInterface( + ServiceManager.getService(Context.AUDIO_SERVICE)); + + return new DeviceRouteController(context, + audioManager, + audioService, + onDeviceRouteChangedListener); + } + + @VisibleForTesting + /* package */ DeviceRouteController(@NonNull Context context, + @NonNull AudioManager audioManager, + @NonNull IAudioService audioService, + @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { + Objects.requireNonNull(context); + Objects.requireNonNull(audioManager); + Objects.requireNonNull(audioService); + Objects.requireNonNull(onDeviceRouteChangedListener); + + mContext = context; + mOnDeviceRouteChangedListener = onDeviceRouteChangedListener; + + mAudioManager = audioManager; + mAudioService = audioService; + + AudioRoutesInfo newAudioRoutes = null; + try { + newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver); + } catch (RemoteException e) { + Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e); + } + + mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes); + } + + @NonNull + /* package */ synchronized MediaRoute2Info getDeviceRoute() { + return mDeviceRoute; + } + + /* package */ synchronized boolean updateVolume(int volume) { + if (mDeviceVolume == volume) { + return false; + } + + mDeviceVolume = volume; + mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute) + .setVolume(volume) + .build(); + + return true; + } + + private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) { + int name = R.string.default_audio_route_name; + int type = TYPE_BUILTIN_SPEAKER; + + if (newRoutes != null) { + if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) { + type = TYPE_WIRED_HEADPHONES; + name = com.android.internal.R.string.default_audio_route_name_headphones; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) { + type = TYPE_WIRED_HEADSET; + name = com.android.internal.R.string.default_audio_route_name_headphones; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) { + type = TYPE_DOCK; + name = com.android.internal.R.string.default_audio_route_name_dock_speakers; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) { + type = TYPE_HDMI; + name = com.android.internal.R.string.default_audio_route_name_external_device; + } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) { + type = TYPE_USB_DEVICE; + name = com.android.internal.R.string.default_audio_route_name_usb; + } + } + + synchronized (this) { + return new MediaRoute2Info.Builder( + DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString()) + .setVolumeHandling(mAudioManager.isVolumeFixed() + ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED + : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) + .setVolume(mDeviceVolume) + .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) + .setType(type) + .addFeature(FEATURE_LIVE_AUDIO) + .addFeature(FEATURE_LIVE_VIDEO) + .addFeature(FEATURE_LOCAL_PLAYBACK) + .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED) + .build(); + } + } + + private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) { + mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute); + } + + /* package */ interface OnDeviceRouteChangedListener { + void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute); + } + + private class AudioRoutesObserver extends IAudioRoutesObserver.Stub { + + @Override + public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) { + MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes); + synchronized (DeviceRouteController.this) { + mDeviceRoute = deviceRoute; + } + notifyDeviceRouteUpdate(deviceRoute); + } + } + +} diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java index 7979d2a3fb3b..e31a7fc5d5ff 100644 --- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java @@ -52,7 +52,7 @@ import java.util.Map; import java.util.Set; class LegacyBluetoothRouteController implements BluetoothRouteController { - private static final String TAG = "BTRouteProvider"; + private static final String TAG = "LBtRouteProvider"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_"; @@ -132,6 +132,12 @@ class LegacyBluetoothRouteController implements BluetoothRouteController { mContext.unregisterReceiver(mDeviceStateChangedReceiver); } + @Override + public boolean selectRoute(String deviceAddress) { + // No-op as the class decides if a route is selected based on Bluetooth events. + return false; + } + /** * Transfers to a given bluetooth route. * The dedicated BT device with the route would be activated. diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 76ff19fcb322..638e81a5df36 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -16,25 +16,12 @@ package com.android.server.media; -import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; -import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO; -import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK; -import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; -import static android.media.MediaRoute2Info.TYPE_DOCK; -import static android.media.MediaRoute2Info.TYPE_HDMI; -import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; -import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; -import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; - import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; -import android.media.AudioRoutesInfo; -import android.media.IAudioRoutesObserver; -import android.media.IAudioService; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; @@ -43,14 +30,11 @@ import android.media.RoutingSessionInfo; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.Slog; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import java.util.Objects; @@ -68,23 +52,21 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { SystemMediaRoute2Provider.class.getName()); static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE"; - static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE"; static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION"; private final AudioManager mAudioManager; - private final IAudioService mAudioService; private final Handler mHandler; private final Context mContext; private final UserHandle mUser; + + private final DeviceRouteController mDeviceRouteController; private final BluetoothRouteController mBtRouteProvider; private String mSelectedRouteId; // For apps without MODIFYING_AUDIO_ROUTING permission. // This should be the currently selected route. MediaRoute2Info mDefaultRoute; - MediaRoute2Info mDeviceRoute; RoutingSessionInfo mDefaultSessionInfo; - int mDeviceVolume; private final AudioManagerBroadcastReceiver mAudioReceiver = new AudioManagerBroadcastReceiver(); @@ -93,19 +75,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { @GuardedBy("mRequestLock") private volatile SessionCreationRequest mPendingSessionCreationRequest; - final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() { - @Override - public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { - mHandler.post(() -> { - updateDeviceRoute(newRoutes); - notifyProviderState(); - if (updateSessionInfosIfNeeded()) { - notifySessionInfoUpdated(); - } - }); - } - }; - SystemMediaRoute2Provider(Context context, UserHandle user) { super(COMPONENT_NAME); mIsSystemRouteProvider = true; @@ -114,8 +83,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mHandler = new Handler(Looper.getMainLooper()); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - mAudioService = IAudioService.Stub.asInterface( - ServiceManager.getService(Context.AUDIO_SERVICE)); mBtRouteProvider = BluetoothRouteController.createInstance(context, (routes) -> { publishProviderState(); @@ -124,15 +91,18 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } }); - AudioRoutesInfo newAudioRoutes = null; - try { - newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver); - } catch (RemoteException e) { - } + mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> { + mHandler.post(() -> { + publishProviderState(); + if (updateSessionInfosIfNeeded()) { + notifySessionInfoUpdated(); + } + }); + }); - // The methods below should be called after all fields are initialized, as they + // These methods below should be called after all fields are initialized, as they // access the fields inside. - updateDeviceRoute(newAudioRoutes); + updateProviderState(); updateSessionInfosIfNeeded(); } @@ -216,7 +186,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // The currently selected route is the default route. return; } - if (TextUtils.equals(routeId, mDeviceRoute.getId())) { + + MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); + if (TextUtils.equals(routeId, deviceRoute.getId())) { mBtRouteProvider.transferTo(null); } else { mBtRouteProvider.transferTo(routeId); @@ -254,9 +226,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { if (mSessionInfos.isEmpty()) { return null; } + + MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); + RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder( SYSTEM_SESSION_ID, packageName).setSystemSession(true); - builder.addSelectedRoute(mDeviceRoute.getId()); + builder.addSelectedRoute(deviceRoute.getId()); for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) { builder.addTransferableRoute(route.getId()); } @@ -264,47 +239,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } } - private void updateDeviceRoute(AudioRoutesInfo newRoutes) { - int name = R.string.default_audio_route_name; - int type = TYPE_BUILTIN_SPEAKER; - if (newRoutes != null) { - if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) { - type = TYPE_WIRED_HEADPHONES; - name = com.android.internal.R.string.default_audio_route_name_headphones; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) { - type = TYPE_WIRED_HEADSET; - name = com.android.internal.R.string.default_audio_route_name_headphones; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) { - type = TYPE_DOCK; - name = com.android.internal.R.string.default_audio_route_name_dock_speakers; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) { - type = TYPE_HDMI; - name = com.android.internal.R.string.default_audio_route_name_external_device; - } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) { - type = TYPE_USB_DEVICE; - name = com.android.internal.R.string.default_audio_route_name_usb; - } - } - - mDeviceRoute = new MediaRoute2Info.Builder( - DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString()) - .setVolumeHandling(mAudioManager.isVolumeFixed() - ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED - : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) - .setVolume(mDeviceVolume) - .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) - .setType(type) - .addFeature(FEATURE_LIVE_AUDIO) - .addFeature(FEATURE_LIVE_VIDEO) - .addFeature(FEATURE_LOCAL_PLAYBACK) - .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED) - .build(); - updateProviderState(); - } - private void updateProviderState() { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(); - builder.addRoute(mDeviceRoute); + + // We must have a device route in the provider info. + builder.addRoute(mDeviceRouteController.getDeviceRoute()); + for (MediaRoute2Info route : mBtRouteProvider.getAllBluetoothRoutes()) { builder.addRoute(route); } @@ -327,11 +267,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { SYSTEM_SESSION_ID, "" /* clientPackageName */) .setSystemSession(true); - MediaRoute2Info selectedRoute = mDeviceRoute; + MediaRoute2Info deviceRoute = mDeviceRouteController.getDeviceRoute(); + MediaRoute2Info selectedRoute = deviceRoute; MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute(); if (selectedBtRoute != null) { selectedRoute = selectedBtRoute; - builder.addTransferableRoute(mDeviceRoute.getId()); + builder.addTransferableRoute(deviceRoute.getId()); } mSelectedRouteId = selectedRoute.getId(); mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute) @@ -423,12 +364,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { if (mBtRouteProvider.updateVolumeForDevices(devices, volume)) { return; } - if (mDeviceVolume != volume) { - mDeviceVolume = volume; - mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute) - .setVolume(volume) - .build(); - } + + mDeviceRouteController.updateVolume(volume); + publishProviderState(); } diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index faa06f70d411..9bca9f0192b2 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -404,7 +404,7 @@ public final class BackgroundDexOptService { "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"), () -> { TimingsTraceAndSlog tr = - new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER); + new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_DALVIK); tr.traceBegin("jobExecution"); boolean completed = false; boolean fatalError = false; @@ -494,6 +494,8 @@ public final class BackgroundDexOptService { @GuardedBy("mLock") private void waitForDexOptThreadToFinishLocked() { TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER); + // This tracing section doesn't have any correspondence in ART Service - it never waits for + // cancellation to finish. tr.traceBegin("waitForDexOptThreadToFinishLocked"); try { // Wait but check in regular internal to see if the thread is still alive. diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index fac77486cd19..c232b3698bc0 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -678,8 +678,4 @@ public interface Computer extends PackageDataSnapshot { @NonNull Collection<SharedUserSetting> getAllSharedUsers(); - - boolean isChangeEnabled(long changeId, int uid); - - boolean isChangeEnabled(long changeId, ApplicationInfo info); } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 21f661b4f21f..5984360a534c 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -125,7 +125,6 @@ import com.android.internal.util.CollectionUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlSerializer; -import com.android.server.compat.PlatformCompat; import com.android.server.pm.Installer.LegacyDexoptDisabledException; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.PackageDexUsage; @@ -575,7 +574,8 @@ public class ComputerEngine implements Computer { list = new ArrayList<>(1); list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - this, list, false, intent, resolvedType, filterCallingUid); + mInjector.getCompatibility(), mComponentResolver, + list, false, intent, resolvedType, filterCallingUid); } } } else { @@ -591,7 +591,7 @@ public class ComputerEngine implements Computer { String callingPkgName = getInstantAppPackageName(filterCallingUid); boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId); lockedResult.result = maybeAddInstantAppInstaller( - lockedResult.result, intent, resolvedType, flags, filterCallingUid, + lockedResult.result, intent, resolvedType, flags, userId, resolveForStart, isRequesterInstantApp); } if (lockedResult.sortResult) { @@ -604,7 +604,8 @@ public class ComputerEngine implements Computer { if (originalIntent != null) { // We also have to ensure all components match the original intent PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - this, list, false, originalIntent, resolvedType, filterCallingUid); + mInjector.getCompatibility(), mComponentResolver, + list, false, originalIntent, resolvedType, filterCallingUid); } return skipPostResolution ? list : applyPostResolutionFilter( @@ -686,7 +687,8 @@ public class ComputerEngine implements Computer { list = new ArrayList<>(1); list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - this, list, false, intent, resolvedType, callingUid); + mInjector.getCompatibility(), mComponentResolver, + list, false, intent, resolvedType, callingUid); } } } else { @@ -697,7 +699,8 @@ public class ComputerEngine implements Computer { if (originalIntent != null) { // We also have to ensure all components match the original intent PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - this, list, false, originalIntent, resolvedType, callingUid); + mInjector.getCompatibility(), mComponentResolver, + list, false, originalIntent, resolvedType, callingUid); } return list; @@ -710,7 +713,7 @@ public class ComputerEngine implements Computer { String pkgName = intent.getPackage(); if (pkgName == null) { final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent, - resolvedType, flags, callingUid, userId); + resolvedType, flags, userId); if (resolveInfos == null) { return Collections.emptyList(); } @@ -720,7 +723,7 @@ public class ComputerEngine implements Computer { final AndroidPackage pkg = mPackages.get(pkgName); if (pkg != null) { final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent, - resolvedType, flags, pkg.getServices(), callingUid, + resolvedType, flags, pkg.getServices(), userId); if (resolveInfos == null) { return Collections.emptyList(); @@ -750,7 +753,7 @@ public class ComputerEngine implements Computer { {@link PackageManager.SKIP_CURRENT_PROFILE} set. */ result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this, - intent, resolvedType, flags, filterCallingUid, userId), userId)); + intent, resolvedType, flags, userId), userId)); } addInstant = isInstantAppResolutionAllowed(intent, result, userId, false /*skipPackageCheck*/, flags); @@ -774,7 +777,7 @@ public class ComputerEngine implements Computer { || !shouldFilterApplication(setting, filterCallingUid, userId))) { result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this, intent, resolvedType, flags, setting.getAndroidPackage().getActivities(), - filterCallingUid, userId), userId)); + userId), userId)); } if (result == null || result.size() == 0) { // the caller wants to resolve for a particular package; however, there @@ -1105,7 +1108,7 @@ public class ComputerEngine implements Computer { return null; } List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(this, intent, - resolvedType, flags, Binder.getCallingUid(), parentUserId); + resolvedType, flags, parentUserId); if (resultTargetUser == null || resultTargetUser.isEmpty()) { return null; @@ -1343,7 +1346,7 @@ public class ComputerEngine implements Computer { private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, - int callingUid, int userId, boolean resolveForStart, boolean isRequesterInstantApp) { + int userId, boolean resolveForStart, boolean isRequesterInstantApp) { // first, check to see if we've got an instant app already installed final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0; ResolveInfo localInstantApp = null; @@ -1356,7 +1359,6 @@ public class ComputerEngine implements Computer { | PackageManager.GET_RESOLVED_FILTER | PackageManager.MATCH_INSTANT | PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY, - callingUid, userId); for (int i = instantApps.size() - 1; i >= 0; --i) { final ResolveInfo info = instantApps.get(i); @@ -3691,13 +3693,10 @@ public class ComputerEngine implements Computer { ps, callingUid, component, TYPE_ACTIVITY, userId, true /* filterUninstall */)) { return false; } - final boolean callerBlocksNullAction = isChangeEnabled( - IntentFilter.BLOCK_NULL_ACTION_INTENTS, callingUid); for (int i=0; i< a.getIntents().size(); i++) { if (a.getIntents().get(i).getIntentFilter() .match(intent.getAction(), resolvedType, intent.getScheme(), - intent.getData(), intent.getCategories(), TAG, - /*supportWildcards*/ false, callerBlocksNullAction, null, null) >= 0) { + intent.getData(), intent.getCategories(), TAG) >= 0) { return true; } } @@ -5795,37 +5794,4 @@ public class ComputerEngine implements Computer { public UserInfo[] getUserInfos() { return mInjector.getUserManagerInternal().getUserInfos(); } - - @Override - public boolean isChangeEnabled(long changeId, int uid) { - final PlatformCompat compat = mInjector.getCompatibility(); - int appId = UserHandle.getAppId(uid); - SettingBase s = mSettings.getSettingBase(appId); - if (s instanceof PackageSetting) { - var ps = (PackageSetting) s; - if (ps.getAndroidPackage() == null) return false; - var info = new ApplicationInfo(); - info.packageName = ps.getPackageName(); - info.targetSdkVersion = ps.getAndroidPackage().getTargetSdkVersion(); - return compat.isChangeEnabledInternal(changeId, info); - } else if (s instanceof SharedUserSetting) { - var ss = (SharedUserSetting) s; - List<AndroidPackage> packages = ss.getPackages(); - for (int i = 0; i < packages.size(); ++i) { - var pkg = packages.get(i); - var info = new ApplicationInfo(); - info.packageName = pkg.getPackageName(); - info.targetSdkVersion = pkg.getTargetSdkVersion(); - if (compat.isChangeEnabledInternal(changeId, info)) { - return true; - } - } - } - return false; - } - - @Override - public boolean isChangeEnabled(long changeId, ApplicationInfo info) { - return mInjector.getCompatibility().isChangeEnabledInternal(changeId, info); - } } diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java b/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java index ee8f5602fad2..90d89c606686 100644 --- a/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java +++ b/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java @@ -23,7 +23,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; -import android.os.Binder; import android.util.SparseBooleanArray; import com.android.internal.app.IntentForwarderActivity; @@ -279,7 +278,7 @@ public final class DefaultCrossProfileResolver extends CrossProfileResolver { } List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(computer, intent, - resolvedType, flags, Binder.getCallingUid(), targetUserId); + resolvedType, flags, targetUserId); if (CollectionUtils.isEmpty(resultTargetUser)) { return null; } diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index 0fd81ac0bc41..a9d4115b4b79 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -16,7 +16,7 @@ package com.android.server.pm; -import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; +import static android.os.Trace.TRACE_TAG_DALVIK; import static com.android.server.LocalManagerRegistry.ManagerNotFoundException; import static com.android.server.pm.ApexManager.ActiveApexInfo; @@ -470,11 +470,11 @@ public final class DexOptHelper { @DexOptResult private int performDexOptTraced(DexoptOptions options) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + Trace.traceBegin(TRACE_TAG_DALVIK, "dexopt"); try { return performDexOptInternal(options); } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + Trace.traceEnd(TRACE_TAG_DALVIK); } } @@ -605,7 +605,7 @@ public final class DexOptHelper { throw new IllegalArgumentException("Can't dexopt APEX package: " + packageName); } - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + Trace.traceBegin(TRACE_TAG_DALVIK, "dexopt"); // Whoever is calling forceDexOpt wants a compiled package. // Don't use profiles since that may cause compilation to be skipped. @@ -615,7 +615,7 @@ public final class DexOptHelper { @DexOptResult int res = performDexOptInternalWithDependenciesLI(pkg, packageState, options); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + Trace.traceEnd(TRACE_TAG_DALVIK); if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) { throw new IllegalStateException("Failed to dexopt: " + res); } diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java index e53e756b29bd..392389009398 100644 --- a/services/core/java/com/android/server/pm/NoFilteringResolver.java +++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java @@ -102,7 +102,7 @@ public class NoFilteringResolver extends CrossProfileResolver { boolean hasNonNegativePriorityResult, Function<String, PackageStateInternal> pkgSettingFunction) { List<ResolveInfo> resolveInfos = mComponentResolver.queryActivities(computer, - intent, resolvedType, flags, Binder.getCallingUid(), targetUserId); + intent, resolvedType, flags, targetUserId); List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>(); if (resolveInfos != null) { diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index f33813759e4f..0a90e7a30db6 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -386,7 +386,7 @@ public class PackageDexOptimizer { options.getCompilationReason()); // OTAPreopt doesn't have stats so don't report in that case. if (packageStats != null) { - Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics"); + Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "dex2oat-metrics"); try { long sessionId = sRandom.nextLong(); ArtStatsLogUtils.writeStatsLog( @@ -403,7 +403,7 @@ public class PackageDexOptimizer { dexCodeIsa, path); } finally { - Trace.traceEnd(Trace.TRACE_TAG_PACKAGE_MANAGER); + Trace.traceEnd(Trace.TRACE_TAG_DALVIK); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a42e78b69709..de5f0c4d523f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6675,24 +6675,16 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Deprecated public void legacyDumpProfiles(String packageName, boolean dumpClassesAndMethods) throws LegacyDexoptDisabledException { - /* Only the shell, root, or the app user should be able to dump profiles. */ - final int callingUid = Binder.getCallingUid(); final Computer snapshot = snapshotComputer(); - final String[] callerPackageNames = snapshot.getPackagesForUid(callingUid); - if (!PackageManagerServiceUtils.isRootOrShell(callingUid) - && !ArrayUtils.contains(callerPackageNames, packageName)) { - throw new SecurityException("dumpProfiles"); - } - AndroidPackage pkg = snapshot.getPackage(packageName); if (pkg == null) { throw new IllegalArgumentException("Unknown package: " + packageName); } synchronized (mInstallLock) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dump profiles"); + Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "dump profiles"); mArtManagerService.dumpProfiles(pkg, dumpClassesAndMethods); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + Trace.traceEnd(Trace.TRACE_TAG_DALVIK); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 9036d4c0615b..928ffa718c6f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -16,7 +16,6 @@ package com.android.server.pm; -import static android.content.IntentFilter.BLOCK_NULL_ACTION_INTENTS; 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; @@ -95,6 +94,7 @@ import com.android.server.EventLogTags; import com.android.server.IntentResolver; import com.android.server.LocalManagerRegistry; import com.android.server.Watchdog; +import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; @@ -1166,8 +1166,10 @@ public class PackageManagerServiceUtils { return (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } + // Static to give access to ComputeEngine public static void applyEnforceIntentFilterMatching( - Computer computer, List<ResolveInfo> resolveInfos, boolean isReceiver, + PlatformCompat compat, ComponentResolverApi resolver, + List<ResolveInfo> resolveInfos, boolean isReceiver, Intent intent, String resolvedType, int filterCallingUid) { if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return; @@ -1175,11 +1177,6 @@ public class PackageManagerServiceUtils { ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM) : null; - final boolean callerBlocksNullAction = computer.isChangeEnabled( - BLOCK_NULL_ACTION_INTENTS, filterCallingUid); - - final ComponentResolverApi resolver = computer.getComponentResolver(); - for (int i = resolveInfos.size() - 1; i >= 0; --i) { final ComponentInfo info = resolveInfos.get(i).getComponentInfo(); @@ -1190,15 +1187,11 @@ public class PackageManagerServiceUtils { } // Only enforce filter matching if target app's target SDK >= T - if (!computer.isChangeEnabled( + if (!compat.isChangeEnabledInternal( ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo)) { continue; } - // Block null action intent if either source or target app's target SDK >= U - final boolean blockNullAction = callerBlocksNullAction - || computer.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS, info.applicationInfo); - final ParsedMainComponent comp; if (info instanceof ActivityInfo) { if (isReceiver) { @@ -1220,8 +1213,7 @@ public class PackageManagerServiceUtils { boolean match = false; for (int j = 0, size = comp.getIntents().size(); j < size; ++j) { IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter(); - if (IntentResolver.intentMatchesFilter( - intentFilter, intent, resolvedType, blockNullAction)) { + if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) { match = true; break; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 93b0dcba0afe..586e1123fdc4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -391,6 +391,11 @@ class PackageManagerShellCommand extends ShellCommand { private int runLegacyDexoptCommand(@NonNull String cmd) throws RemoteException, LegacyDexoptDisabledException { Installer.checkLegacyDexoptDisabled(); + + if (!PackageManagerServiceUtils.isRootOrShell(Binder.getCallingUid())) { + throw new SecurityException("Dexopt shell commands need root or shell access"); + } + switch (cmd) { case "compile": return runCompile(); @@ -4410,10 +4415,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" " + PackageManagerServiceCompilerMapping.REASON_STRINGS[i]); } pw.println(" --reset: restore package to its post-install state"); - pw.println(" --check-prof (true | false): look at profiles when doing dexopt?"); + pw.println(" --check-prof (true | false): ignored - this is always true"); pw.println(" --secondary-dex: compile app secondary dex files"); pw.println(" --split SPLIT: compile only the given split name"); - pw.println(" --compile-layouts: compile layout resources for faster inflation"); pw.println(""); pw.println(" force-dex-opt PACKAGE"); pw.println(" Force immediate execution of dex opt for the given PACKAGE."); diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index 8bca4a9fc779..a13c568f87a6 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -466,14 +466,15 @@ final class ResolveIntentHelper { list = new ArrayList<>(1); list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - computer, list, true, intent, resolvedType, filterCallingUid); + mPlatformCompat, componentResolver, list, true, intent, + resolvedType, filterCallingUid); } } } else { String pkgName = intent.getPackage(); if (pkgName == null) { - final List<ResolveInfo> result = componentResolver.queryReceivers( - computer, intent, resolvedType, flags, filterCallingUid, userId); + final List<ResolveInfo> result = componentResolver + .queryReceivers(computer, intent, resolvedType, flags, userId); if (result != null) { list = result; } @@ -481,7 +482,7 @@ final class ResolveIntentHelper { final AndroidPackage pkg = computer.getPackage(pkgName); if (pkg != null) { final List<ResolveInfo> result = componentResolver.queryReceivers(computer, - intent, resolvedType, flags, pkg.getReceivers(), filterCallingUid, userId); + intent, resolvedType, flags, pkg.getReceivers(), userId); if (result != null) { list = result; } @@ -491,7 +492,8 @@ final class ResolveIntentHelper { if (originalIntent != null) { // We also have to ensure all components match the original intent PackageManagerServiceUtils.applyEnforceIntentFilterMatching( - computer, list, true, originalIntent, resolvedType, filterCallingUid); + mPlatformCompat, componentResolver, + list, true, originalIntent, resolvedType, filterCallingUid); } return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid, @@ -575,7 +577,7 @@ final class ResolveIntentHelper { String pkgName = intent.getPackage(); if (pkgName == null) { final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer, - intent, resolvedType, flags, callingUid, userId); + intent, resolvedType, flags, userId); if (resolveInfos == null) { return Collections.emptyList(); } @@ -585,7 +587,7 @@ final class ResolveIntentHelper { final AndroidPackage pkg = computer.getPackage(pkgName); if (pkg != null) { final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer, - intent, resolvedType, flags, pkg.getProviders(), callingUid, userId); + intent, resolvedType, flags, pkg.getProviders(), userId); if (resolveInfos == null) { return Collections.emptyList(); } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index c6411ff94b45..d04ce5f3e7f1 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -119,10 +119,9 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.security.FileIntegrity; import com.android.server.uri.UriGrantsManagerInternal; -import libcore.io.IoUtils; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -207,6 +206,10 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; + @VisibleForTesting + static final String FILENAME_USER_PACKAGES_RESERVE_COPY = + FILENAME_USER_PACKAGES + ".reservecopy"; + static final String DIRECTORY_BITMAPS = "bitmaps"; private static final String TAG_ROOT = "root"; @@ -1055,6 +1058,11 @@ public class ShortcutService extends IShortcutService.Stub { return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); } + @VisibleForTesting + final File getReserveCopyUserFile(@UserIdInt int userId) { + return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES_RESERVE_COPY); + } + @GuardedBy("mLock") private void saveUserLocked(@UserIdInt int userId) { final File path = getUserFile(userId); @@ -1062,6 +1070,9 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, "Saving to " + path); } + final File reservePath = getReserveCopyUserFile(userId); + reservePath.delete(); + path.getParentFile().mkdirs(); final AtomicFile file = new AtomicFile(path); FileOutputStream os = null; @@ -1079,6 +1090,23 @@ public class ShortcutService extends IShortcutService.Stub { file.failWrite(os); } + // Store the reserve copy of the file. + try (FileInputStream in = new FileInputStream(path); + FileOutputStream out = new FileOutputStream(reservePath)) { + FileUtils.copy(in, out); + FileUtils.sync(out); + } catch (IOException e) { + Slog.e(TAG, "Failed to write reserve copy: " + path, e); + } + + // Protect both primary and reserve copy with fs-verity. + try { + FileIntegrity.setUpFsVerity(path); + FileIntegrity.setUpFsVerity(reservePath); + } catch (IOException e) { + Slog.e(TAG, "Failed to verity-protect", e); + } + getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger); } @@ -1117,26 +1145,25 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Loading from " + path); } - final AtomicFile file = new AtomicFile(path); - final FileInputStream in; - try { - in = file.openRead(); + try (FileInputStream in = new AtomicFile(path).openRead()) { + return loadUserInternal(userId, in, /* forBackup= */ false); } catch (FileNotFoundException e) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Not found " + path); } - return null; - } - try { - final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false); - return ret; - } catch (IOException | XmlPullParserException | InvalidFileFormatException e) { - Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); - return null; - } finally { - IoUtils.closeQuietly(in); + } catch (Exception e) { + final File reservePath = getReserveCopyUserFile(userId); + Slog.e(TAG, "Reading from reserve copy: " + reservePath, e); + try (FileInputStream in = new AtomicFile(reservePath).openRead()) { + return loadUserInternal(userId, in, /* forBackup= */ false); + } catch (Exception exceptionReadingReserveFile) { + Slog.e(TAG, "Failed to read reserve copy: " + reservePath, + exceptionReadingReserveFile); + } + Slog.e(TAG, "Failed to read file " + path, e); } + return null; } private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, @@ -4323,14 +4350,8 @@ public class ShortcutService extends IShortcutService.Stub { @NonNull ComponentName activity, @UserIdInt int userId) { final long start = getStatStartTime(); try { - final ActivityInfo ai; - try { - ai = mContext.getPackageManager().getActivityInfoAsUser(activity, - PackageManager.ComponentInfoFlags.of(PACKAGE_MATCH_FLAGS), userId); - } catch (NameNotFoundException e) { - return false; - } - return ai.enabled && ai.exported; + return queryActivities(new Intent(), activity.getPackageName(), activity, userId) + .size() > 0; } finally { logDurationStat(Stats.IS_ACTIVITY_ENABLED, start); } diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index 07d36b311aac..18eebe45a759 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.Intent; @@ -39,6 +40,7 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; @@ -63,6 +65,9 @@ import java.util.Set; import java.util.function.Predicate; public final class SuspendPackageHelper { + + private static final String SYSTEM_EXEMPT_FROM_SUSPENSION = "system_exempt_from_suspension"; + // TODO(b/198166813): remove PMS dependency private final PackageManagerService mPm; private final PackageManagerServiceInjector mInjector; @@ -502,6 +507,10 @@ public final class SuspendPackageHelper { final String requiredPermissionControllerPackage = getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER, userId); + final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class); + final boolean isSystemExemptFlagEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, + SYSTEM_EXEMPT_FROM_SUSPENSION, /* defaultValue= */ true); for (int i = 0; i < packageNames.length; i++) { canSuspend[i] = false; final String packageName = packageNames[i]; @@ -558,6 +567,7 @@ public final class SuspendPackageHelper { PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName); AndroidPackage pkg = packageState == null ? null : packageState.getPkg(); if (pkg != null) { + final int uid = UserHandle.getUid(userId, packageState.getAppId()); // Cannot suspend SDK libs as they are controlled by SDK manager. if (pkg.isSdkLibrary()) { Slog.w(TAG, "Cannot suspend package: " + packageName @@ -574,6 +584,13 @@ public final class SuspendPackageHelper { + pkg.getStaticSharedLibraryName()); continue; } + if (isSystemExemptFlagEnabled && appOpsManager.checkOpNoThrow( + AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName) + == AppOpsManager.MODE_ALLOWED) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": has OP_SYSTEM_EXEMPT_FROM_SUSPENSION set"); + continue; + } } if (PLATFORM_PACKAGE_NAME.equals(packageName)) { Slog.w(TAG, "Cannot suspend the platform package: " + packageName); diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java index 977fab16e29b..fac681aaf1c4 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java @@ -807,10 +807,10 @@ public class ComponentResolver extends ComponentResolverLocked implements } } - private abstract static class MimeGroupsAwareIntentResolver<F extends ParsedComponent> - extends IntentResolver<Pair<F, ParsedIntentInfo>, ResolveInfo> { - private final ArrayMap<String, Pair<F, ParsedIntentInfo>[]> mMimeGroupToFilter = - new ArrayMap<>(); + private abstract static class MimeGroupsAwareIntentResolver<F extends Pair<? + extends ParsedComponent, ParsedIntentInfo>, R> + extends IntentResolver<F, R> { + private final ArrayMap<String, F[]> mMimeGroupToFilter = new ArrayMap<>(); private boolean mIsUpdatingMimeGroup = false; @NonNull @@ -822,7 +822,7 @@ public class ComponentResolver extends ComponentResolverLocked implements } // Copy constructor used in creating snapshots - MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F> orig, + MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F, R> orig, @NonNull UserManagerService userManager) { mUserManager = userManager; copyFrom(orig); @@ -831,7 +831,7 @@ public class ComponentResolver extends ComponentResolverLocked implements } @Override - public void addFilter(@Nullable PackageDataSnapshot snapshot, Pair<F, ParsedIntentInfo> f) { + public void addFilter(@Nullable PackageDataSnapshot snapshot, F f) { IntentFilter intentFilter = getIntentFilter(f); // We assume Computer is available for this class and all subclasses. Because this class // uses subclass method override to handle logic, the Computer parameter must be in the @@ -846,7 +846,7 @@ public class ComponentResolver extends ComponentResolverLocked implements } @Override - protected void removeFilterInternal(Pair<F, ParsedIntentInfo> f) { + protected void removeFilterInternal(F f) { IntentFilter intentFilter = getIntentFilter(f); if (!mIsUpdatingMimeGroup) { unregister_intent_filter(f, intentFilter.mimeGroupsIterator(), mMimeGroupToFilter, @@ -857,86 +857,6 @@ public class ComponentResolver extends ComponentResolverLocked implements intentFilter.clearDynamicDataTypes(); } - @Override - public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent, - String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId) { - if (!mUserManager.exists(userId)) return null; - long flags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0); - return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, callingUid, - userId, flags); - } - - List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent, - String resolvedType, long flags, int callingUid, int userId) { - if (!mUserManager.exists(userId)) return null; - return super.queryIntent(computer, intent, resolvedType, - (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, callingUid, userId, flags); - } - - List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent, - String resolvedType, long flags, List<F> packageComponents, - int callingUid, int userId) { - if (!mUserManager.exists(userId)) { - return null; - } - if (packageComponents == null) { - return Collections.emptyList(); - } - final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0; - final int componentsSize = packageComponents.size(); - ArrayList<Pair<F, ParsedIntentInfo>[]> listCut = new ArrayList<>(componentsSize); - - List<ParsedIntentInfo> intentFilters; - for (int i = 0; i < componentsSize; ++i) { - F component = packageComponents.get(i); - intentFilters = component.getIntents(); - if (!intentFilters.isEmpty()) { - Pair<F, ParsedIntentInfo>[] array = newArray(intentFilters.size()); - for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) { - array[arrayIndex] = Pair.create(component, intentFilters.get(arrayIndex)); - } - listCut.add(array); - } - } - return super.queryIntentFromList(computer, intent, resolvedType, - defaultOnly, listCut, callingUid, userId, flags); - } - - @Override - protected boolean isPackageForFilter(String packageName, Pair<F, ParsedIntentInfo> info) { - return packageName.equals(info.first.getPackageName()); - } - - @Override - protected void sortResults(List<ResolveInfo> results) { - results.sort(RESOLVE_PRIORITY_SORTER); - } - - @Override - protected void filterResults(@NonNull Computer computer, @NonNull Intent intent, - List<ResolveInfo> results) { - if (intent.getAction() != null) return; - // When the resolved component is targeting U+, block null action intents - for (int i = results.size() - 1; i >= 0; --i) { - if (computer.isChangeEnabled(IntentFilter.BLOCK_NULL_ACTION_INTENTS, - results.get(i).getComponentInfo().applicationInfo)) { - results.remove(i); - } - } - } - - @Override - protected Pair<F, ParsedIntentInfo>[] newArray(int size) { - //noinspection unchecked - return (Pair<F, ParsedIntentInfo>[]) new Pair<?, ?>[size]; - } - - @Override - protected IntentFilter getIntentFilter( - @NonNull Pair<F, ParsedIntentInfo> input) { - return input.second.getIntentFilter(); - } - /** * Updates MIME group by applying changes to all IntentFilters * that contain the group and repopulating m*ToFilter maps accordingly @@ -947,12 +867,12 @@ public class ComponentResolver extends ComponentResolverLocked implements */ public boolean updateMimeGroup(@NonNull Computer computer, String packageName, String mimeGroup) { - Pair<F, ParsedIntentInfo>[] filters = mMimeGroupToFilter.get(mimeGroup); + F[] filters = mMimeGroupToFilter.get(mimeGroup); int n = filters != null ? filters.length : 0; mIsUpdatingMimeGroup = true; boolean hasChanges = false; - Pair<F, ParsedIntentInfo> filter; + F filter; for (int i = 0; i < n && (filter = filters[i]) != null; i++) { if (isPackageForFilter(packageName, filter)) { hasChanges |= updateFilter(computer, filter); @@ -962,7 +882,7 @@ public class ComponentResolver extends ComponentResolverLocked implements return hasChanges; } - private boolean updateFilter(@NonNull Computer computer, Pair<F, ParsedIntentInfo> f) { + private boolean updateFilter(@NonNull Computer computer, F f) { IntentFilter filter = getIntentFilter(f); List<String> oldTypes = filter.dataTypes(); removeFilter(f); @@ -987,7 +907,7 @@ public class ComponentResolver extends ComponentResolverLocked implements return first.equals(second); } - private void applyMimeGroups(@NonNull Computer computer, Pair<F, ParsedIntentInfo> f) { + private void applyMimeGroups(@NonNull Computer computer, F f) { IntentFilter filter = getIntentFilter(f); for (int i = filter.countMimeGroups() - 1; i >= 0; i--) { @@ -1011,8 +931,8 @@ public class ComponentResolver extends ComponentResolverLocked implements } @Override - protected boolean isFilterStopped(@NonNull Computer computer, - Pair<F, ParsedIntentInfo> filter, @UserIdInt int userId) { + protected boolean isFilterStopped(@NonNull Computer computer, F filter, + @UserIdInt int userId) { if (!mUserManager.exists(userId)) { return true; } @@ -1028,7 +948,7 @@ public class ComponentResolver extends ComponentResolverLocked implements } public static class ActivityIntentResolver - extends MimeGroupsAwareIntentResolver<ParsedActivity> { + extends MimeGroupsAwareIntentResolver<Pair<ParsedActivity, ParsedIntentInfo>, ResolveInfo> { @NonNull private UserNeedsBadgingCache mUserNeedsBadging; @@ -1049,6 +969,53 @@ public class ComponentResolver extends ComponentResolverLocked implements mUserNeedsBadging = userNeedsBadgingCache; } + @Override + public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent, + String resolvedType, boolean defaultOnly, @UserIdInt int userId) { + if (!mUserManager.exists(userId)) return null; + long flags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0); + return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags); + } + + List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent, + String resolvedType, long flags, int userId) { + if (!mUserManager.exists(userId)) { + return null; + } + return super.queryIntent(computer, intent, resolvedType, + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags); + } + + List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent, + String resolvedType, long flags, List<ParsedActivity> packageActivities, + int userId) { + if (!mUserManager.exists(userId)) { + return null; + } + if (packageActivities == null) { + return Collections.emptyList(); + } + final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0; + final int activitiesSize = packageActivities.size(); + ArrayList<Pair<ParsedActivity, ParsedIntentInfo>[]> listCut = + new ArrayList<>(activitiesSize); + + List<ParsedIntentInfo> intentFilters; + for (int i = 0; i < activitiesSize; ++i) { + ParsedActivity activity = packageActivities.get(i); + intentFilters = activity.getIntents(); + if (!intentFilters.isEmpty()) { + Pair<ParsedActivity, ParsedIntentInfo>[] array = newArray(intentFilters.size()); + for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) { + array[arrayIndex] = Pair.create(activity, intentFilters.get(arrayIndex)); + } + listCut.add(array); + } + } + return super.queryIntentFromList(computer, intent, resolvedType, + defaultOnly, listCut, userId, flags); + } + protected void addActivity(@NonNull Computer computer, ParsedActivity a, String type, List<Pair<ParsedActivity, ParsedIntentInfo>> newIntents) { mActivities.put(a.getComponentName(), a); @@ -1105,6 +1072,18 @@ public class ComponentResolver extends ComponentResolverLocked implements return true; } + @Override + protected Pair<ParsedActivity, ParsedIntentInfo>[] newArray(int size) { + //noinspection unchecked + return (Pair<ParsedActivity, ParsedIntentInfo>[]) new Pair<?, ?>[size]; + } + + @Override + protected boolean isPackageForFilter(String packageName, + Pair<ParsedActivity, ParsedIntentInfo> info) { + return packageName.equals(info.first.getPackageName()); + } + private void log(String reason, ParsedIntentInfo info, int match, int userId) { Slog.w(TAG, reason @@ -1220,6 +1199,11 @@ public class ComponentResolver extends ComponentResolverLocked implements } @Override + protected void sortResults(List<ResolveInfo> results) { + results.sort(RESOLVE_PRIORITY_SORTER); + } + + @Override protected void dumpFilter(PrintWriter out, String prefix, Pair<ParsedActivity, ParsedIntentInfo> pair) { ParsedActivity activity = pair.first; @@ -1253,6 +1237,12 @@ public class ComponentResolver extends ComponentResolverLocked implements out.println(); } + @Override + protected IntentFilter getIntentFilter( + @NonNull Pair<ParsedActivity, ParsedIntentInfo> input) { + return input.second.getIntentFilter(); + } + protected List<ParsedActivity> getResolveList(AndroidPackage pkg) { return pkg.getActivities(); } @@ -1288,7 +1278,7 @@ public class ComponentResolver extends ComponentResolverLocked implements } public static final class ProviderIntentResolver - extends MimeGroupsAwareIntentResolver<ParsedProvider> { + extends MimeGroupsAwareIntentResolver<Pair<ParsedProvider, ParsedIntentInfo>, ResolveInfo> { // Default constructor ProviderIntentResolver(@NonNull UserManagerService userManager) { super(userManager); @@ -1301,6 +1291,57 @@ public class ComponentResolver extends ComponentResolverLocked implements mProviders.putAll(orig.mProviders); } + @Override + public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent, + String resolvedType, boolean defaultOnly, @UserIdInt int userId) { + if (!mUserManager.exists(userId)) { + return null; + } + long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; + return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags); + } + + @Nullable + List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent, + String resolvedType, long flags, int userId) { + if (!mUserManager.exists(userId)) { + return null; + } + return super.queryIntent(computer, intent, resolvedType, + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags); + } + + @Nullable + List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent, + String resolvedType, long flags, List<ParsedProvider> packageProviders, + int userId) { + if (!mUserManager.exists(userId)) { + return null; + } + if (packageProviders == null) { + return Collections.emptyList(); + } + final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0; + final int providersSize = packageProviders.size(); + ArrayList<Pair<ParsedProvider, ParsedIntentInfo>[]> listCut = + new ArrayList<>(providersSize); + + List<ParsedIntentInfo> intentFilters; + for (int i = 0; i < providersSize; ++i) { + ParsedProvider provider = packageProviders.get(i); + intentFilters = provider.getIntents(); + if (!intentFilters.isEmpty()) { + Pair<ParsedProvider, ParsedIntentInfo>[] array = newArray(intentFilters.size()); + for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) { + array[arrayIndex] = Pair.create(provider, intentFilters.get(arrayIndex)); + } + listCut.add(array); + } + } + return super.queryIntentFromList(computer, intent, resolvedType, + defaultOnly, listCut, userId, flags); + } + void addProvider(@NonNull Computer computer, ParsedProvider p) { if (mProviders.containsKey(p.getComponentName())) { Slog.w(TAG, "Provider " + p.getComponentName() + " already defined; ignoring"); @@ -1361,6 +1402,18 @@ public class ComponentResolver extends ComponentResolverLocked implements } @Override + protected Pair<ParsedProvider, ParsedIntentInfo>[] newArray(int size) { + //noinspection unchecked + return (Pair<ParsedProvider, ParsedIntentInfo>[]) new Pair<?, ?>[size]; + } + + @Override + protected boolean isPackageForFilter(String packageName, + Pair<ParsedProvider, ParsedIntentInfo> info) { + return packageName.equals(info.first.getPackageName()); + } + + @Override protected ResolveInfo newResult(@NonNull Computer computer, Pair<ParsedProvider, ParsedIntentInfo> pair, int match, int userId, long customFlags) { @@ -1426,6 +1479,11 @@ public class ComponentResolver extends ComponentResolverLocked implements } @Override + protected void sortResults(List<ResolveInfo> results) { + results.sort(RESOLVE_PRIORITY_SORTER); + } + + @Override protected void dumpFilter(PrintWriter out, String prefix, Pair<ParsedProvider, ParsedIntentInfo> pair) { ParsedProvider provider = pair.first; @@ -1460,11 +1518,17 @@ public class ComponentResolver extends ComponentResolverLocked implements out.println(); } + @Override + protected IntentFilter getIntentFilter( + @NonNull Pair<ParsedProvider, ParsedIntentInfo> input) { + return input.second.getIntentFilter(); + } + final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>(); } public static final class ServiceIntentResolver - extends MimeGroupsAwareIntentResolver<ParsedService> { + extends MimeGroupsAwareIntentResolver<Pair<ParsedService, ParsedIntentInfo>, ResolveInfo> { // Default constructor ServiceIntentResolver(@NonNull UserManagerService userManager) { super(userManager); @@ -1477,6 +1541,50 @@ public class ComponentResolver extends ComponentResolverLocked implements mServices.putAll(orig.mServices); } + @Override + public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent, + String resolvedType, boolean defaultOnly, @UserIdInt int userId) { + if (!mUserManager.exists(userId)) { + return null; + } + long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; + return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags); + } + + List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent, + String resolvedType, long flags, int userId) { + if (!mUserManager.exists(userId)) return null; + return super.queryIntent(computer, intent, resolvedType, + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags); + } + + List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent, + String resolvedType, long flags, List<ParsedService> packageServices, int userId) { + if (!mUserManager.exists(userId)) return null; + if (packageServices == null) { + return Collections.emptyList(); + } + final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0; + final int servicesSize = packageServices.size(); + ArrayList<Pair<ParsedService, ParsedIntentInfo>[]> listCut = + new ArrayList<>(servicesSize); + + List<ParsedIntentInfo> intentFilters; + for (int i = 0; i < servicesSize; ++i) { + ParsedService service = packageServices.get(i); + intentFilters = service.getIntents(); + if (intentFilters.size() > 0) { + Pair<ParsedService, ParsedIntentInfo>[] array = newArray(intentFilters.size()); + for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) { + array[arrayIndex] = Pair.create(service, intentFilters.get(arrayIndex)); + } + listCut.add(array); + } + } + return super.queryIntentFromList(computer, intent, resolvedType, + defaultOnly, listCut, userId, flags); + } + void addService(@NonNull Computer computer, ParsedService s) { mServices.put(s.getComponentName(), s); if (DEBUG_SHOW_INFO) { @@ -1532,6 +1640,18 @@ public class ComponentResolver extends ComponentResolverLocked implements } @Override + protected Pair<ParsedService, ParsedIntentInfo>[] newArray(int size) { + //noinspection unchecked + return (Pair<ParsedService, ParsedIntentInfo>[]) new Pair<?, ?>[size]; + } + + @Override + protected boolean isPackageForFilter(String packageName, + Pair<ParsedService, ParsedIntentInfo> info) { + return packageName.equals(info.first.getPackageName()); + } + + @Override protected ResolveInfo newResult(@NonNull Computer computer, Pair<ParsedService, ParsedIntentInfo> pair, int match, int userId, long customFlags) { @@ -1590,6 +1710,11 @@ public class ComponentResolver extends ComponentResolverLocked implements } @Override + protected void sortResults(List<ResolveInfo> results) { + results.sort(RESOLVE_PRIORITY_SORTER); + } + + @Override protected void dumpFilter(PrintWriter out, String prefix, Pair<ParsedService, ParsedIntentInfo> pair) { ParsedService service = pair.first; @@ -1627,6 +1752,12 @@ public class ComponentResolver extends ComponentResolverLocked implements out.println(); } + @Override + protected IntentFilter getIntentFilter( + @NonNull Pair<ParsedService, ParsedIntentInfo> input) { + return input.second.getIntentFilter(); + } + // Keys are String (activity class name), values are Activity. final ArrayMap<ComponentName, ParsedService> mServices = new ArrayMap<>(); } @@ -1690,8 +1821,7 @@ public class ComponentResolver extends ComponentResolverLocked implements } @Override - protected void filterResults(@NonNull Computer computer, - @NonNull Intent intent, List<AuxiliaryResolveInfo.AuxiliaryFilter> results) { + protected void filterResults(List<AuxiliaryResolveInfo.AuxiliaryFilter> results) { // only do work if ordering is enabled [most of the time it won't be] if (mOrderResult.size() == 0) { return; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java index 7f886600121c..b8e4c8d2a51f 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java @@ -55,12 +55,12 @@ public interface ComponentResolverApi { @Nullable List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId); + @Nullable String resolvedType, long flags, @UserIdInt int userId); @Nullable List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities, - int callingUid, @UserIdInt int userId); + @UserIdInt int userId); @Nullable ProviderInfo queryProvider(@NonNull Computer computer, @NonNull String authority, long flags, @@ -68,12 +68,12 @@ public interface ComponentResolverApi { @Nullable List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId); + @Nullable String resolvedType, long flags, @UserIdInt int userId); @Nullable List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers, - int callingUid, @UserIdInt int userId); + @UserIdInt int userId); @Nullable List<ProviderInfo> queryProviders(@NonNull Computer computer, @Nullable String processName, @@ -81,21 +81,21 @@ public interface ComponentResolverApi { @Nullable List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId); + @Nullable String resolvedType, long flags, @UserIdInt int userId); @Nullable List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers, - int callingUid, @UserIdInt int userId); + @UserIdInt int userId); @Nullable List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId); + @Nullable String resolvedType, long flags, @UserIdInt int userId); @Nullable List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedService> services, - int callingUid, @UserIdInt int userId); + @UserIdInt int userId); void querySyncProviders(@NonNull Computer computer, @NonNull List<String> outNames, @NonNull List<ProviderInfo> outInfo, boolean safeMode, @UserIdInt int userId); diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java index 689992444601..9115775acd05 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java @@ -126,17 +126,17 @@ public abstract class ComponentResolverBase extends WatchableImpl implements Com @Nullable @Override public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, int userId) { - return mActivities.queryIntent(computer, intent, resolvedType, flags, callingUid, userId); + @Nullable String resolvedType, long flags, int userId) { + return mActivities.queryIntent(computer, intent, resolvedType, flags, userId); } @Nullable @Override public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities, - int callingUid, int userId) { + int userId) { return mActivities.queryIntentForPackage(computer, intent, resolvedType, flags, activities, - callingUid, userId); + userId); } @Nullable @@ -168,17 +168,17 @@ public abstract class ComponentResolverBase extends WatchableImpl implements Com @Nullable @Override public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, int userId) { - return mProviders.queryIntent(computer, intent, resolvedType, flags, callingUid, userId); + @Nullable String resolvedType, long flags, int userId) { + return mProviders.queryIntent(computer, intent, resolvedType, flags, userId); } @Nullable @Override public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers, - int callingUid, @UserIdInt int userId) { + @UserIdInt int userId) { return mProviders.queryIntentForPackage(computer, intent, resolvedType, flags, providers, - callingUid, userId); + userId); } @Nullable @@ -241,33 +241,33 @@ public abstract class ComponentResolverBase extends WatchableImpl implements Com @Nullable @Override public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, int userId) { - return mReceivers.queryIntent(computer, intent, resolvedType, flags, callingUid, userId); + @Nullable String resolvedType, long flags, int userId) { + return mReceivers.queryIntent(computer, intent, resolvedType, flags, userId); } @Nullable @Override public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers, - int callingUid, @UserIdInt int userId) { + @UserIdInt int userId) { return mReceivers.queryIntentForPackage(computer, intent, resolvedType, flags, receivers, - callingUid, userId); + userId); } @Nullable @Override public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) { - return mServices.queryIntent(computer, intent, resolvedType, flags, callingUid, userId); + @Nullable String resolvedType, long flags, @UserIdInt int userId) { + return mServices.queryIntent(computer, intent, resolvedType, flags, userId); } @Nullable @Override public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedService> services, - int callingUid, @UserIdInt int userId) { + @UserIdInt int userId) { return mServices.queryIntentForPackage(computer, intent, resolvedType, flags, services, - callingUid, userId); + userId); } @Override diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java index 5bfb135c7d75..0c84f4c53dfe 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java @@ -92,9 +92,9 @@ public abstract class ComponentResolverLocked extends ComponentResolverBase { @Nullable @Override public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) { + @Nullable String resolvedType, long flags, @UserIdInt int userId) { synchronized (mLock) { - return super.queryActivities(computer, intent, resolvedType, flags, callingUid, userId); + return super.queryActivities(computer, intent, resolvedType, flags, userId); } } @@ -102,10 +102,9 @@ public abstract class ComponentResolverLocked extends ComponentResolverBase { @Override public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities, - int callingUid, @UserIdInt int userId) { + @UserIdInt int userId) { synchronized (mLock) { - return super.queryActivities(computer, intent, resolvedType, flags, activities, - callingUid, userId); + return super.queryActivities(computer, intent, resolvedType, flags, activities, userId); } } @@ -121,9 +120,9 @@ public abstract class ComponentResolverLocked extends ComponentResolverBase { @Nullable @Override public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) { + @Nullable String resolvedType, long flags, @UserIdInt int userId) { synchronized (mLock) { - return super.queryProviders(computer, intent, resolvedType, flags, callingUid, userId); + return super.queryProviders(computer, intent, resolvedType, flags, userId); } } @@ -131,10 +130,9 @@ public abstract class ComponentResolverLocked extends ComponentResolverBase { @Override public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers, - int callingUid, @UserIdInt int userId) { + @UserIdInt int userId) { synchronized (mLock) { - return super.queryProviders(computer, intent, resolvedType, flags, providers, - callingUid, userId); + return super.queryProviders(computer, intent, resolvedType, flags, providers, userId); } } @@ -151,9 +149,9 @@ public abstract class ComponentResolverLocked extends ComponentResolverBase { @Nullable @Override public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) { + @Nullable String resolvedType, long flags, @UserIdInt int userId) { synchronized (mLock) { - return super.queryReceivers(computer, intent, resolvedType, flags, callingUid, userId); + return super.queryReceivers(computer, intent, resolvedType, flags, userId); } } @@ -161,19 +159,18 @@ public abstract class ComponentResolverLocked extends ComponentResolverBase { @Override public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers, - int callingUid, @UserIdInt int userId) { + @UserIdInt int userId) { synchronized (mLock) { - return super.queryReceivers(computer, intent, resolvedType, flags, receivers, - callingUid, userId); + return super.queryReceivers(computer, intent, resolvedType, flags, receivers, userId); } } @Nullable @Override public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent, - @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) { + @Nullable String resolvedType, long flags, @UserIdInt int userId) { synchronized (mLock) { - return super.queryServices(computer, intent, resolvedType, flags, callingUid, userId); + return super.queryServices(computer, intent, resolvedType, flags, userId); } } @@ -181,10 +178,9 @@ public abstract class ComponentResolverLocked extends ComponentResolverBase { @Override public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent, @Nullable String resolvedType, long flags, @NonNull List<ParsedService> services, - int callingUid, @UserIdInt int userId) { + @UserIdInt int userId) { synchronized (mLock) { - return super.queryServices(computer, intent, resolvedType, flags, services, callingUid, - userId); + return super.queryServices(computer, intent, resolvedType, flags, services, userId); } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a24f129cc575..37877cf8264e 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -89,6 +89,7 @@ import static com.android.server.wm.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; @@ -160,6 +161,7 @@ import android.service.dreams.IDreamManager; import android.service.vr.IPersistentVrStateCallbacks; import android.speech.RecognizerIntent; import android.telecom.TelecomManager; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.MathUtils; import android.util.MutableBoolean; @@ -398,7 +400,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { ActivityTaskManagerInternal mActivityTaskManagerInternal; AutofillManagerInternal mAutofillManagerInternal; InputManagerInternal mInputManagerInternal; - InputMethodManagerInternal mInputMethodManagerInternal; DreamManagerInternal mDreamManagerInternal; PowerManagerInternal mPowerManagerInternal; IStatusBarService mStatusBarService; @@ -659,6 +660,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_HANDLE_ALL_APPS = 22; private static final int MSG_LAUNCH_ASSIST = 23; private static final int MSG_RINGER_TOGGLE_CHORD = 24; + private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 25; private class PolicyHandler extends Handler { @Override @@ -729,6 +731,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_SCREENSHOT_CHORD: handleScreenShot(msg.arg1); break; + case MSG_SWITCH_KEYBOARD_LAYOUT: + handleSwitchKeyboardLayout(msg.arg1, msg.arg2); + break; } } } @@ -1025,14 +1030,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: { if (mDismissImeOnBackKeyPressed) { - if (mInputMethodManagerInternal == null) { - mInputMethodManagerInternal = - LocalServices.getService(InputMethodManagerInternal.class); - } - if (mInputMethodManagerInternal != null) { - mInputMethodManagerInternal.hideCurrentInputMethod( + InputMethodManagerInternal.get().hideCurrentInputMethod( SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME); - } } else { shortPressPowerGoHome(); } @@ -2978,15 +2977,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case KeyEvent.KEYCODE_N: if (down && event.isMetaPressed()) { - IStatusBarService service = getStatusBarService(); - if (service != null) { - try { - service.expandNotificationsPanel(); - } catch (RemoteException e) { - // do nothing. - } - return key_consumed; - } + toggleNotificationPanel(); + return key_consumed; } break; case KeyEvent.KEYCODE_S: @@ -3177,7 +3169,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if (down && repeatCount == 0) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; - mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); + sendSwitchKeyboardLayout(event, direction); return key_consumed; } break; @@ -3427,7 +3419,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; - mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); + sendSwitchKeyboardLayout(event, direction); return true; } } @@ -3453,6 +3445,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { return false; } + private void sendSwitchKeyboardLayout(@NonNull KeyEvent event, int direction) { + mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, event.getDeviceId(), + direction).sendToTarget(); + } + + private void handleSwitchKeyboardLayout(int deviceId, int direction) { + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) { + InputMethodManagerInternal.get().switchKeyboardLayout(direction); + } else { + mWindowManagerFuncs.switchKeyboardLayout(deviceId, direction); + } + } + private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent, int policyFlags) { int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags); diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS index 7feb85f929cd..daa02111f71f 100644 --- a/services/core/java/com/android/server/rollback/OWNERS +++ b/services/core/java/com/android/server/rollback/OWNERS @@ -1 +1,3 @@ -olilan@google.com +ancr@google.com +harshitmahajan@google.com +robertogil@google.com diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java index 41c2fbfd3314..6c32ec2e8df8 100644 --- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java +++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java @@ -17,6 +17,8 @@ package com.android.server.sensors; import android.annotation.NonNull; +import android.hardware.SensorDirectChannel; +import android.os.ParcelFileDescriptor; import java.util.concurrent.Executor; @@ -58,7 +60,7 @@ public abstract class SensorManagerInternal { * @return The sensor handle. */ public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name, - @NonNull String vendor, @NonNull RuntimeSensorCallback callback); + @NonNull String vendor, int flags, @NonNull RuntimeSensorCallback callback); /** * Unregisters the sensor with the given handle from the framework. @@ -98,9 +100,31 @@ public abstract class SensorManagerInternal { public interface RuntimeSensorCallback { /** * Invoked when the listeners of the runtime sensor have changed. - * Returns an error code if the invocation was unsuccessful, zero otherwise. + * Returns zero on success, negative error code otherwise. */ int onConfigurationChanged(int handle, boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros); + + /** + * Invoked when a direct sensor channel has been created. + * Wraps the file descriptor in a {@link android.os.SharedMemory} object and passes it to + * the client process. + * Returns a positive identifier of the channel on success, negative error code otherwise. + */ + int onDirectChannelCreated(ParcelFileDescriptor fd); + + /** + * Invoked when a direct sensor channel has been destroyed. + */ + void onDirectChannelDestroyed(int channelHandle); + + /** + * Invoked when a direct sensor channel has been configured for a sensor. + * If the invocation is unsuccessful, a negative error code is returned. + * On success, the return value is zero if the rate level is {@code RATE_STOP}, and a + * positive report token otherwise. + */ + int onDirectChannelConfigured(int channelHandle, int sensorHandle, + @SensorDirectChannel.RateLevel int rateLevel); } } diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java index 979065950dc4..1baa0a6d79a1 100644 --- a/services/core/java/com/android/server/sensors/SensorService.java +++ b/services/core/java/com/android/server/sensors/SensorService.java @@ -56,7 +56,8 @@ public class SensorService extends SystemService { private static native void unregisterProximityActiveListenerNative(long ptr); private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type, - String name, String vendor, SensorManagerInternal.RuntimeSensorCallback callback); + String name, String vendor, int flags, + SensorManagerInternal.RuntimeSensorCallback callback); private static native void unregisterRuntimeSensorNative(long ptr, int handle); private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type, long timestampNanos, float[] values); @@ -95,9 +96,9 @@ public class SensorService extends SystemService { class LocalService extends SensorManagerInternal { @Override public int createRuntimeSensor(int deviceId, int type, @NonNull String name, - @NonNull String vendor, @NonNull RuntimeSensorCallback callback) { + @NonNull String vendor, int flags, @NonNull RuntimeSensorCallback callback) { synchronized (mLock) { - int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor, + int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor, flags, callback); mRuntimeSensorHandles.add(handle); return handle; diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java index ed3248ecc165..de631bb53377 100644 --- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java +++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java @@ -181,24 +181,6 @@ public class NetworkTimeUpdateService extends Binder { } /** - * Clears the cached NTP time. For use during tests to simulate when no NTP time is available. - * - * <p>This operation takes place in the calling thread rather than the service's handler thread. - */ - @RequiresPermission(android.Manifest.permission.SET_TIME) - void clearTimeForTests() { - mContext.enforceCallingPermission( - android.Manifest.permission.SET_TIME, "clear latest network time"); - - final long token = Binder.clearCallingIdentity(); - try { - mNtpTrustedTime.clearCachedTimeResult(); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - /** * Forces the service to refresh the NTP time. * * <p>This operation takes place in the calling thread rather than the service's handler thread. diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java index afc0bdd30f29..cfc95df1ef15 100644 --- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java +++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java @@ -37,11 +37,6 @@ class NetworkTimeUpdateServiceShellCommand extends ShellCommand { private static final String SHELL_COMMAND_SERVICE_NAME = "network_time_update_service"; /** - * A shell command that clears the time signal received from the network. - */ - private static final String SHELL_COMMAND_CLEAR_TIME = "clear_time"; - - /** * A shell command that forces the time signal to be refreshed from the network. */ private static final String SHELL_COMMAND_FORCE_REFRESH = "force_refresh"; @@ -73,8 +68,6 @@ class NetworkTimeUpdateServiceShellCommand extends ShellCommand { } switch (cmd) { - case SHELL_COMMAND_CLEAR_TIME: - return runClearTime(); case SHELL_COMMAND_FORCE_REFRESH: return runForceRefresh(); case SHELL_COMMAND_SET_SERVER_CONFIG: @@ -87,11 +80,6 @@ class NetworkTimeUpdateServiceShellCommand extends ShellCommand { } } - private int runClearTime() { - mNetworkTimeUpdateService.clearTimeForTests(); - return 0; - } - private int runForceRefresh() { boolean success = mNetworkTimeUpdateService.forceRefreshForTests(); getOutPrintWriter().println(success); @@ -147,8 +135,6 @@ class NetworkTimeUpdateServiceShellCommand extends ShellCommand { pw.printf("Network Time Update Service (%s) commands:\n", SHELL_COMMAND_SERVICE_NAME); pw.printf(" help\n"); pw.printf(" Print this help text.\n"); - pw.printf(" %s\n", SHELL_COMMAND_CLEAR_TIME); - pw.printf(" Clears the latest time.\n"); pw.printf(" %s\n", SHELL_COMMAND_FORCE_REFRESH); pw.printf(" Refreshes the latest time. Prints whether it was successful.\n"); pw.printf(" %s\n", SHELL_COMMAND_SET_SERVER_CONFIG); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 0da967a3bc00..22f096b11f18 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -37,6 +37,7 @@ import android.os.ParcelableException; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.os.SystemClock; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.NtpTrustedTime; @@ -53,6 +54,7 @@ import com.android.server.timezonedetector.CurrentUserIdentityInjector; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.net.InetSocketAddress; import java.time.DateTimeException; import java.util.Objects; @@ -377,7 +379,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub * * <p>This operation takes place in the calling thread. */ - void clearNetworkTime() { + void clearLatestNetworkTime() { enforceSuggestNetworkTimePermission(); final long token = Binder.clearCallingIdentity(); @@ -390,12 +392,29 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub @Override public UnixEpochTime latestNetworkTime() { - NetworkTimeSuggestion suggestion = getLatestNetworkSuggestion(); - if (suggestion != null) { - return suggestion.getUnixEpochTime(); + NetworkTimeSuggestion latestNetworkTime; + // TODO(b/222295093): Remove this condition once we can be sure that all uses of + // NtpTrustedTime result in a suggestion being made to the time detector. + // mNtpTrustedTime can be removed once this happens. + if (TimeDetectorNetworkTimeHelper.isInUse()) { + // The new implementation. + latestNetworkTime = mTimeDetectorStrategy.getLatestNetworkSuggestion(); } else { + // The old implementation. + NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult(); + if (ntpResult != null) { + latestNetworkTime = new NetworkTimeSuggestion( + new UnixEpochTime( + ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis()), + ntpResult.getUncertaintyMillis()); + } else { + latestNetworkTime = null; + } + } + if (latestNetworkTime == null) { throw new ParcelableException(new DateTimeException("Missing network time fix")); } + return latestNetworkTime.getUnixEpochTime(); } /** @@ -403,23 +422,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub */ @Nullable NetworkTimeSuggestion getLatestNetworkSuggestion() { - // TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can - // be sure that all uses of NtpTrustedTime results in a suggestion being made to the time - // detector. mNtpTrustedTime can be removed once this happens. - if (TimeDetectorNetworkTimeHelper.isInUse()) { - // The new implementation. - return mTimeDetectorStrategy.getLatestNetworkSuggestion(); - } else { - // The old implementation. - NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult(); - if (ntpResult != null) { - UnixEpochTime unixEpochTime = new UnixEpochTime( - ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis()); - return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis()); - } else { - return null; - } - } + return mTimeDetectorStrategy.getLatestNetworkSuggestion(); } /** @@ -440,6 +443,57 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub mHandler.post(() -> mTimeDetectorStrategy.suggestExternalTime(timeSignal)); } + /** + * Sets the network time for testing {@link SystemClock#currentNetworkTimeClock()}. + * + * <p>This operation takes place in the calling thread. + */ + void setNetworkTimeForSystemClockForTests( + @NonNull UnixEpochTime unixEpochTime, int uncertaintyMillis) { + enforceSuggestNetworkTimePermission(); + + // TODO(b/222295093): Remove this condition once we can be sure that all uses of + // NtpTrustedTime result in a suggestion being made to the time detector. + // mNtpTrustedTime can be removed once this happens. + if (TimeDetectorNetworkTimeHelper.isInUse()) { + NetworkTimeSuggestion suggestion = + new NetworkTimeSuggestion(unixEpochTime, uncertaintyMillis); + suggestion.addDebugInfo("Injected for tests"); + mTimeDetectorStrategy.suggestNetworkTime(suggestion); + } else { + NtpTrustedTime.TimeResult timeResult = new NtpTrustedTime.TimeResult( + unixEpochTime.getUnixEpochTimeMillis(), + unixEpochTime.getElapsedRealtimeMillis(), + uncertaintyMillis, + InetSocketAddress.createUnresolved("time.set.for.tests", 123)); + mNtpTrustedTime.setCachedTimeResult(timeResult); + } + } + + /** + * Clears the network time for testing {@link SystemClock#currentNetworkTimeClock()}. + * + * <p>This operation takes place in the calling thread. + */ + void clearNetworkTimeForSystemClockForTests() { + enforceSuggestNetworkTimePermission(); + + final long token = Binder.clearCallingIdentity(); + try { + // TODO(b/222295093): Remove this condition once we can be sure that all uses of + // NtpTrustedTime result in a suggestion being made to the time detector. + // mNtpTrustedTime can be removed once this happens. + if (TimeDetectorNetworkTimeHelper.isInUse()) { + // Clear the latest network suggestion. Done in all c + mTimeDetectorStrategy.clearLatestNetworkSuggestion(); + } else { + mNtpTrustedTime.clearCachedTimeResult(); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) { diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java index cce570986168..fe0127fc11f2 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java @@ -16,12 +16,14 @@ package com.android.server.timedetector; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_NETWORK_TIME; +import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_NETWORK_TIME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED; +import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_TIME_STATE; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_EXTERNAL_TIME; import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_GNSS_TIME; @@ -73,9 +75,9 @@ class TimeDetectorShellCommand extends ShellCommand { case SHELL_COMMAND_SUGGEST_NETWORK_TIME: return runSuggestNetworkTime(); case SHELL_COMMAND_GET_NETWORK_TIME: - return runGetNetworkTime(); + return runGetLatestNetworkTime(); case SHELL_COMMAND_CLEAR_NETWORK_TIME: - return runClearNetworkTime(); + return runClearLatestNetworkTime(); case SHELL_COMMAND_SUGGEST_GNSS_TIME: return runSuggestGnssTime(); case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME: @@ -86,6 +88,10 @@ class TimeDetectorShellCommand extends ShellCommand { return runSetTimeState(); case SHELL_COMMAND_CONFIRM_TIME: return runConfirmTime(); + case SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME: + return runClearSystemClockNetworkTime(); + case SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME: + return runSetSystemClockNetworkTime(); default: { return handleDefaultCommands(cmd); } @@ -128,15 +134,15 @@ class TimeDetectorShellCommand extends ShellCommand { mInterface::suggestNetworkTime); } - private int runGetNetworkTime() { + private int runGetLatestNetworkTime() { NetworkTimeSuggestion networkTimeSuggestion = mInterface.getLatestNetworkSuggestion(); final PrintWriter pw = getOutPrintWriter(); pw.println(networkTimeSuggestion); return 0; } - private int runClearNetworkTime() { - mInterface.clearNetworkTime(); + private int runClearLatestNetworkTime() { + mInterface.clearLatestNetworkTime(); return 0; } @@ -187,6 +193,20 @@ class TimeDetectorShellCommand extends ShellCommand { return 0; } + private int runClearSystemClockNetworkTime() { + mInterface.clearNetworkTimeForSystemClockForTests(); + return 0; + } + + private int runSetSystemClockNetworkTime() { + NetworkTimeSuggestion networkTimeSuggestion = + NetworkTimeSuggestion.parseCommandLineArg(this); + mInterface.setNetworkTimeForSystemClockForTests( + networkTimeSuggestion.getUnixEpochTime(), + networkTimeSuggestion.getUncertaintyMillis()); + return 0; + } + @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); @@ -218,6 +238,16 @@ class TimeDetectorShellCommand extends ShellCommand { pw.printf(" Prints the network time information held by the detector.\n"); pw.printf(" %s\n", SHELL_COMMAND_CLEAR_NETWORK_TIME); pw.printf(" Clears the network time information held by the detector.\n"); + // TODO(b/222295093) Remove these "system_clock" commands when + // SystemClock.currentNetworkTimeClock() is guaranteed to use the latest network + // suggestion. Then, commands above can be used instead. + pw.printf(" %s <network suggestion opts>\n", + SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME); + pw.printf(" Sets the network time information used for" + + " SystemClock.currentNetworkTimeClock().\n"); + pw.printf(" %s\n", SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME); + pw.printf(" Clears the network time information used for" + + " SystemClock.currentNetworkTimeClock().\n"); pw.println(); ManualTimeSuggestion.printCommandLineOpts(pw); pw.println(); diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java index 91c4a2ff03e0..8a4fc0db0264 100644 --- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java +++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java @@ -42,6 +42,9 @@ class ActivitySecurityModelFeatureFlags { // TODO(b/230590090): Replace with public documentation once ready static final String DOC_LINK = "go/android-asm"; + /** Used to determine which version of the ASM logic was used in logs while we iterate */ + static final int ASM_VERSION = 5; + private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER; private static final String KEY_ASM_PREFIX = "ActivitySecurity__"; private static final String KEY_ASM_RESTRICTIONS_ENABLED = KEY_ASM_PREFIX diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 32dac49102bd..e99046041056 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -60,7 +60,6 @@ import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TAS import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; -import static com.android.server.wm.ActivityRecord.State.FINISHING; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; @@ -75,6 +74,7 @@ import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME; import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; +import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_UID; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_DEFAULT; @@ -83,6 +83,7 @@ import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW; import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK; +import static com.android.server.wm.BackgroundActivityStartController.balCodeToString; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY; import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; @@ -150,6 +151,9 @@ import com.android.server.wm.TaskFragment.EmbeddingCheckResult; import java.io.PrintWriter; import java.text.DateFormat; import java.util.Date; +import java.util.StringJoiner; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -1967,8 +1971,7 @@ class ActivityStarter { // ASM rules have failed. Log why ActivityRecord targetTopActivity = targetTask == null ? null - : targetTask.getActivity(ar -> - !ar.isState(FINISHING) && !ar.isAlwaysOnTop()); + : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop()); int action = newTask || mSourceRecord == null ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK @@ -1999,7 +2002,7 @@ class ActivityStarter { /* action */ action, /* version */ - 4, + ActivitySecurityModelFeatureFlags.ASM_VERSION, /* multi_window - we have our source not in the target task, but both are visible */ targetTask != null && mSourceRecord != null && !targetTask.equals(mSourceRecord.getTask()) && targetTask.isVisible(), @@ -2011,22 +2014,26 @@ class ActivityStarter { .shouldRestrictActivitySwitch(mCallingUid) && shouldBlockActivityStart; + String launchedFromPackageName = r.launchedFromPackage; if (ActivitySecurityModelFeatureFlags.shouldShowToast(mCallingUid)) { + String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK + + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ") + + getApplicationLabel(mService.mContext.getPackageManager(), + launchedFromPackageName); UiThread.getHandler().post(() -> Toast.makeText(mService.mContext, - "Activity start from " + r.launchedFromPackage - + (blockActivityStartAndFeatureEnabled ? " " : " would be ") - + "blocked by " + ActivitySecurityModelFeatureFlags.DOC_LINK, - Toast.LENGTH_SHORT).show()); - } + toastText, Toast.LENGTH_LONG).show()); + logDebugInfoForActivitySecurity("Launch", r, targetTask, targetTopActivity, + blockActivityStartAndFeatureEnabled, /* taskToFront */ taskToFront); + } if (blockActivityStartAndFeatureEnabled) { - Slog.e(TAG, "Abort Launching r: " + r + Slog.e(TAG, "[ASM] Abort Launching r: " + r + " as source: " - + (mSourceRecord != null ? mSourceRecord : r.launchedFromPackage) + + (mSourceRecord != null ? mSourceRecord : launchedFromPackageName) + " is in background. New task: " + newTask + ". Top activity: " + targetTopActivity - + ". BAL Code: " + mBalCode); + + ". BAL Code: " + balCodeToString(mBalCode)); return false; } @@ -2034,6 +2041,71 @@ class ActivityStarter { return true; } + /** Only called when an activity launch may be blocked, which should happen very rarely */ + private void logDebugInfoForActivitySecurity(String action, ActivityRecord r, Task targetTask, + ActivityRecord targetTopActivity, boolean blockActivityStartAndFeatureEnabled, + boolean taskToFront) { + final String prefix = "[ASM] "; + Function<ActivityRecord, String> recordToString = (ar) -> { + if (ar == null) { + return null; + } + return (ar == mSourceRecord ? " [source]=> " + : ar == targetTopActivity ? " [ top ]=> " + : ar == r ? " [target]=> " + : " => ") + + ar + + " :: visible=" + ar.isVisible() + + ", finishing=" + ar.isFinishing() + + ", alwaysOnTop=" + ar.isAlwaysOnTop() + + ", taskFragment=" + ar.getTaskFragment(); + }; + + StringJoiner joiner = new StringJoiner("\n"); + joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------"); + joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled); + joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION); + + boolean targetTaskMatchesSourceTask = targetTask != null + && mSourceRecord != null && mSourceRecord.getTask() == targetTask; + + if (mSourceRecord == null) { + joiner.add(prefix + "Source Package: " + r.launchedFromPackage); + String realCallingPackage = mService.mContext.getPackageManager().getNameForUid( + mRealCallingUid); + joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage); + } else { + joiner.add(prefix + "Source Record: " + recordToString.apply(mSourceRecord)); + if (targetTaskMatchesSourceTask) { + joiner.add(prefix + "Source/Target Task: " + mSourceRecord.getTask()); + joiner.add(prefix + "Source/Target Task Stack: "); + } else { + joiner.add(prefix + "Source Task: " + mSourceRecord.getTask()); + joiner.add(prefix + "Source Task Stack: "); + } + mSourceRecord.getTask().forAllActivities((Consumer<ActivityRecord>) + ar -> joiner.add(prefix + recordToString.apply(ar))); + } + + joiner.add(prefix + "Target Task Top: " + recordToString.apply(targetTopActivity)); + if (!targetTaskMatchesSourceTask) { + joiner.add(prefix + "Target Task: " + targetTask); + if (targetTask != null) { + joiner.add(prefix + "Target Task Stack: "); + targetTask.forAllActivities((Consumer<ActivityRecord>) + ar -> joiner.add(prefix + recordToString.apply(ar))); + } + } + + joiner.add(prefix + "Target Record: " + recordToString.apply(r)); + joiner.add(prefix + "Intent: " + mIntent); + joiner.add(prefix + "TaskToFront: " + taskToFront); + joiner.add(prefix + "BalCode: " + balCodeToString(mBalCode)); + + joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------"); + Slog.i(TAG, joiner.toString()); + } + /** * Returns whether embedding of {@code starting} is allowed. * @@ -2165,8 +2237,8 @@ class ActivityStarter { return; } - Predicate<ActivityRecord> isLaunchingOrLaunched = ar -> !ar.finishing && (ar.isUid( - startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid)); + Predicate<ActivityRecord> isLaunchingOrLaunched = ar -> !ar.finishing + && (ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid)); // Return early if we know for sure we won't need to clear any activities by just checking // the top activity. @@ -2202,7 +2274,10 @@ class ActivityStarter { ? "Top activities cleared by " : "Top activities would be cleared by ") + ActivitySecurityModelFeatureFlags.DOC_LINK, - Toast.LENGTH_SHORT).show()); + Toast.LENGTH_LONG).show()); + + logDebugInfoForActivitySecurity("Clear Top", mStartActivity, targetTask, targetTaskTop, + shouldBlockActivityStart, /* taskToFront */ true); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index ef47b6e5d70a..e463358a3cf5 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -51,7 +51,6 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; -import static com.android.server.wm.ActivityRecord.State.FINISHING; import static com.android.server.wm.ActivityRecord.State.PAUSED; import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS; @@ -103,6 +102,7 @@ import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.ResumeActivityItem; import android.companion.virtual.VirtualDeviceManager; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -1236,7 +1236,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { int getDeviceIdForDisplayId(int displayId) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { - return VirtualDeviceManager.DEVICE_ID_DEFAULT; + return Context.DEVICE_ID_DEFAULT; } if (mVirtualDeviceManager == null) { mVirtualDeviceManager = @@ -1636,50 +1636,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Prevent recursion. return; } - boolean shouldBlockActivitySwitchIfFeatureEnabled = false; - boolean wouldBlockActivitySwitchIgnoringFlags = false; - // We may have already checked that the callingUid has additional clearTask privileges, and - // cleared the calling identify. If so, we infer we do not need further restrictions here. - // TODO(b/263368846) Move to live with the rest of the ASM logic. - if (callingUid != SYSTEM_UID) { - Pair<Boolean, Boolean> pair = doesTopActivityMatchingUidExistForAsm(task, - callingUid, - null); - shouldBlockActivitySwitchIfFeatureEnabled = !pair.first; - wouldBlockActivitySwitchIgnoringFlags = !pair.second; - if (wouldBlockActivitySwitchIgnoringFlags) { - ActivityRecord topActivity = task.getActivity(ar -> - !ar.isState(FINISHING) && !ar.isAlwaysOnTop()); - FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED, - /* caller_uid */ - callingUid, - /* caller_activity_class_name */ - callerActivityClassName, - /* target_task_top_activity_uid */ - topActivity == null ? -1 : topActivity.getUid(), - /* target_task_top_activity_class_name */ - topActivity == null ? null : topActivity.info.name, - /* target_task_is_different */ - false, - /* target_activity_uid */ - -1, - /* target_activity_class_name */ - null, - /* target_intent_action */ - null, - /* target_intent_flags */ - 0, - /* action */ - FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK, - /* version */ - 3, - /* multi_window */ - false, - /* bal_code */ - -1 - ); - } - } task.mTransitionController.requestCloseTransitionIfNeeded(task); task.mInRemoveTask = true; try { @@ -1690,33 +1646,107 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { if (task.isPersistable) { mService.notifyTaskPersisterLocked(null, true); } - if (wouldBlockActivitySwitchIgnoringFlags) { - boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags - .shouldRestrictActivitySwitch(callingUid) - && shouldBlockActivitySwitchIfFeatureEnabled; - if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) { - UiThread.getHandler().post(() -> Toast.makeText(mService.mContext, - (restrictActivitySwitch - ? "Returning home due to " - : "Would return home due to ") - + ActivitySecurityModelFeatureFlags.DOC_LINK, - Toast.LENGTH_SHORT).show()); - } - - // If the activity switch should be restricted, return home rather than the - // previously top task, to prevent users from being confused which app they're - // viewing - if (restrictActivitySwitch) { - Slog.w(TAG, "Return to home as source uid: " + callingUid - + "is not on top of task t: " + task); - task.getTaskDisplayArea().moveHomeActivityToTop("taskRemoved"); - } - } + checkActivitySecurityForTaskClear(callingUid, task, callerActivityClassName); } finally { task.mInRemoveTask = false; } } + // TODO(b/263368846) Move to live with the rest of the ASM logic. + /** + * Returns home if the passed in callingUid is not top of the stack, rather than returning to + * previous task. + */ + private void checkActivitySecurityForTaskClear(int callingUid, Task task, + String callerActivityClassName) { + // We may have already checked that the callingUid has additional clearTask privileges, and + // cleared the calling identify. If so, we infer we do not need further restrictions here. + if (callingUid == SYSTEM_UID) { + return; + } + + TaskDisplayArea displayArea = task.getTaskDisplayArea(); + if (displayArea == null) { + // If there is no associated display area, we can not return home. + return; + } + + Pair<Boolean, Boolean> pair = doesTopActivityMatchingUidExistForAsm(task, callingUid, null); + boolean shouldBlockActivitySwitchIfFeatureEnabled = !pair.first; + boolean wouldBlockActivitySwitchIgnoringFlags = !pair.second; + + if (!wouldBlockActivitySwitchIgnoringFlags) { + return; + } + + ActivityRecord topActivity = task.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop()); + FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED, + /* caller_uid */ + callingUid, + /* caller_activity_class_name */ + callerActivityClassName, + /* target_task_top_activity_uid */ + topActivity == null ? -1 : topActivity.getUid(), + /* target_task_top_activity_class_name */ + topActivity == null ? null : topActivity.info.name, + /* target_task_is_different */ + false, + /* target_activity_uid */ + -1, + /* target_activity_class_name */ + null, + /* target_intent_action */ + null, + /* target_intent_flags */ + 0, + /* action */ + FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK, + /* version */ + ActivitySecurityModelFeatureFlags.ASM_VERSION, + /* multi_window */ + false, + /* bal_code */ + -1 + ); + + boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags + .shouldRestrictActivitySwitch(callingUid) + && shouldBlockActivitySwitchIfFeatureEnabled; + + PackageManager pm = mService.mContext.getPackageManager(); + String callingPackage = pm.getNameForUid(callingUid); + final CharSequence callingLabel; + if (callingPackage == null) { + callingPackage = String.valueOf(callingUid); + callingLabel = callingPackage; + } else { + callingLabel = getApplicationLabel(pm, callingPackage); + } + + if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) { + Toast toast = Toast.makeText(mService.mContext, + (ActivitySecurityModelFeatureFlags.DOC_LINK + + (restrictActivitySwitch + ? "returned home due to " + : "would return home due to ") + + callingLabel), + Toast.LENGTH_LONG); + UiThread.getHandler().post(toast::show); + } + + // If the activity switch should be restricted, return home rather than the + // previously top task, to prevent users from being confused which app they're + // viewing + if (restrictActivitySwitch) { + Slog.w(TAG, "[ASM] Return to home as source: " + callingPackage + + " is not on top of task t: " + task); + displayArea.moveHomeActivityToTop("taskRemoved"); + } else { + Slog.i(TAG, "[ASM] Would return to home as source: " + callingPackage + + " is not on top of task t: " + task); + } + } + /** * For the purpose of ASM, ‘Top UID” for a task is defined as an activity UID * 1. Which is top of the stack in z-order @@ -1743,7 +1773,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Consider the source activity, whether or not it is finishing. Do not consider any other // finishing activity. Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord) - || (!ar.isState(FINISHING) && !ar.isAlwaysOnTop()); + || (!ar.finishing && !ar.isAlwaysOnTop()); // Check top of stack (or the first task fragment for embedding). ActivityRecord topActivity = task.getActivity(topOfStackPredicate); @@ -1777,6 +1807,16 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { return topActivity.allowCrossUidActivitySwitchFromBelow(uid); } + static CharSequence getApplicationLabel(PackageManager pm, String packageName) { + try { + ApplicationInfo launchedFromPackageInfo = pm.getApplicationInfo( + packageName, PackageManager.ApplicationInfoFlags.of(0)); + return pm.getApplicationLabel(launchedFromPackageInfo); + } catch (PackageManager.NameNotFoundException e) { + return packageName; + } + } + void cleanUpRemovedTaskLocked(Task task, boolean killProcess, boolean removeFromRecents) { if (removeFromRecents) { mRecentTasks.remove(task); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 587138ded8cb..8fc379724475 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -114,6 +114,35 @@ public class BackgroundActivityStartController { /** Process belongs to a SDK sandbox */ static final int BAL_ALLOW_SDK_SANDBOX = 10; + static String balCodeToString(@BalCode int balCode) { + switch (balCode) { + case BAL_ALLOW_ALLOWLISTED_COMPONENT: + return "BAL_ALLOW_ALLOWLISTED_COMPONENT"; + case BAL_ALLOW_ALLOWLISTED_UID: + return "BAL_ALLOW_ALLOWLISTED_UID"; + case BAL_ALLOW_DEFAULT: + return "BAL_ALLOW_DEFAULT"; + case BAL_ALLOW_FOREGROUND: + return "BAL_ALLOW_FOREGROUND"; + case BAL_ALLOW_GRACE_PERIOD: + return "BAL_ALLOW_GRACE_PERIOD"; + case BAL_ALLOW_PENDING_INTENT: + return "BAL_ALLOW_PENDING_INTENT"; + case BAL_ALLOW_PERMISSION: + return "BAL_ALLOW_PERMISSION"; + case BAL_ALLOW_SAW_PERMISSION: + return "BAL_ALLOW_SAW_PERMISSION"; + case BAL_ALLOW_SDK_SANDBOX: + return "BAL_ALLOW_SDK_SANDBOX"; + case BAL_ALLOW_VISIBLE_WINDOW: + return "BAL_ALLOW_VISIBLE_WINDOW"; + case BAL_BLOCK: + return "BAL_BLOCK"; + default: + throw new IllegalArgumentException("Unexpected value: " + balCode); + } + } + BackgroundActivityStartController( final ActivityTaskManagerService service, final ActivityTaskSupervisor supervisor) { mService = service; diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 25ce5699ab71..210a7d9538c7 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -344,6 +344,19 @@ class InsetsPolicy { } } + if (!attrs.isFullscreen() || attrs.getFitInsetsTypes() != 0) { + if (state == originalState) { + state = new InsetsState(originalState); + } + // Explicitly exclude floating windows from receiving caption insets. This is because we + // hard code caption insets for windows due to a synchronization issue that leads to + // flickering that bypasses insets frame calculation, which consequently needs us to + // remove caption insets from floating windows. + // TODO(b/254128050): Remove this workaround after we find a way to update window frames + // and caption insets frames simultaneously. + state.removeSource(InsetsState.ITYPE_CAPTION_BAR); + } + final SparseArray<WindowContainerInsetsSourceProvider> providers = mStateController.getSourceProviders(); final int windowType = attrs.type; diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 97e0b1e0de2c..ce9bff8521e6 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -948,7 +948,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { synchronized (mService.mGlobalLock) { WindowState windowState = mService.windowForClientLocked(this, window, false); if (windowState == null) { - Slog.e(TAG_WM, + Slog.i(TAG_WM, "setOnBackInvokedCallback(): No window state for package:" + mPackageName); } else { windowState.setOnBackInvokedCallbackInfo(callbackInfo); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index cc81d526af5e..0a833f4f2dba 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3512,10 +3512,7 @@ class Task extends TaskFragment { info.isKeyguardOccluded = mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY); - info.startingWindowTypeParameter = activity.mStartingData != null - ? activity.mStartingData.mTypeParams - : (StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED - | StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS); + info.startingWindowTypeParameter = activity.mStartingData.mTypeParams; if ((info.startingWindowTypeParameter & StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED) != 0) { final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 8cbd553fbebd..3cec3aa6a205 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1225,7 +1225,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Clear last paused activity if focused root task changed while sleeping, so that the // top activity of current focused task can be resumed. - if (mDisplayContent.isSleeping()) { + if (mDisplayContent.isSleeping() && currentFocusedTask != null) { currentFocusedTask.clearLastPausedActivity(); } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index b13136534de3..3a30e4b0cf1f 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.TaskInfo.cameraCompatControlStateToString; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; @@ -44,7 +43,6 @@ import android.view.Display; import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; -import android.window.IWindowlessStartingSurfaceCallback; import android.window.SplashScreenView; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; @@ -658,10 +656,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { info.splashScreenThemeResId = launchTheme; } info.taskSnapshot = taskSnapshot; - info.appToken = activity.token; // make this happen prior than prepare surface try { - lastOrganizer.addStartingWindow(info); + lastOrganizer.addStartingWindow(info, activity.token); } catch (RemoteException e) { Slog.e(TAG, "Exception sending onTaskStart callback", e); return false; @@ -707,55 +704,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } - /** - * Create a starting surface which attach on a given surface. - * @param activity Target activity, this isn't necessary to be the top activity. - * @param root The root surface which the created surface will attach on. - * @param taskSnapshot Whether to draw snapshot. - * @param callback Called when surface is drawn and attached to the root surface. - * @return The taskId, this is a token and should be used to remove the surface, even if - * the task was removed from hierarchy. - */ - int addWindowlessStartingSurface(Task task, ActivityRecord activity, SurfaceControl root, - TaskSnapshot taskSnapshot, IWindowlessStartingSurfaceCallback callback) { - final Task rootTask = task.getRootTask(); - if (rootTask == null) { - return INVALID_TASK_ID; - } - final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); - if (lastOrganizer == null) { - return INVALID_TASK_ID; - } - final StartingWindowInfo info = task.getStartingWindowInfo(activity); - info.taskInfo.taskDescription = activity.taskDescription; - info.taskSnapshot = taskSnapshot; - info.windowlessStartingSurfaceCallback = callback; - info.rootSurface = root; - try { - lastOrganizer.addStartingWindow(info); - } catch (RemoteException e) { - Slog.e(TAG, "Exception sending addWindowlessStartingSurface ", e); - return INVALID_TASK_ID; - } - return task.mTaskId; - } - - void removeWindowlessStartingSurface(int taskId, boolean immediately) { - final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast(); - if (lastOrganizer == null || taskId == 0) { - return; - } - final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo(); - removalInfo.taskId = taskId; - removalInfo.windowlessSurface = true; - removalInfo.removeImmediately = immediately; - try { - lastOrganizer.removeStartingWindow(removalInfo); - } catch (RemoteException e) { - Slog.e(TAG, "Exception sending removeWindowlessStartingSurface ", e); - } - } - boolean copySplashScreenView(Task task) { final Task rootTask = task.getRootTask(); if (rootTask == null) { diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index e56b6792f491..7c0318d2bee4 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -54,7 +54,6 @@ import android.app.BackgroundStartPrivileges; import android.app.IApplicationThread; import android.app.ProfilerInfo; import android.app.servertransaction.ConfigurationChangeItem; -import android.companion.virtual.VirtualDeviceManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -211,7 +210,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio /** Whether {@link #mLastReportedConfiguration} is deferred by the cached state. */ private volatile boolean mHasCachedConfiguration; - private int mTopActivityDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT; + private int mTopActivityDeviceId = Context.DEVICE_ID_DEFAULT; /** * Registered {@link DisplayArea} as a listener to override config changes. {@code null} if not * registered. @@ -1435,7 +1434,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // TODO(b/263402938): Add tests that capture the deviceId dispatch to the client. mTopActivityDeviceId = deviceId; dispatchConfiguration(config, topActivityDeviceChanged ? mTopActivityDeviceId - : VirtualDeviceManager.DEVICE_ID_INVALID); + : Context.DEVICE_ID_INVALID); } private int getTopActivityDeviceId() { @@ -1520,7 +1519,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) { // By default send invalid deviceId as no-op signal so it's not updated on the client side. - scheduleConfigurationChange(thread, config, VirtualDeviceManager.DEVICE_ID_INVALID); + scheduleConfigurationChange(thread, config, Context.DEVICE_ID_INVALID); } private void scheduleConfigurationChange(IApplicationThread thread, Configuration config, diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index e2bdcdd518d5..2ce86add4e58 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -179,6 +179,7 @@ cc_defaults { "android.hardware.power.stats@1.0", "android.hardware.power.stats-V1-ndk", "android.hardware.thermal@1.0", + "android.hardware.thermal-V1-ndk", "android.hardware.tv.input@1.0", "android.hardware.tv.input-V1-ndk", "android.hardware.vibrator-V2-cpp", diff --git a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp index ed79352bba21..7e0bb68c6168 100644 --- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp +++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp @@ -16,27 +16,28 @@ #define LOG_TAG "HardwarePropertiesManagerService-JNI" -#include <nativehelper/JNIHelp.h> -#include "jni.h" - -#include <math.h> -#include <stdlib.h> - +#include <aidl/android/hardware/thermal/IThermal.h> +#include <android/binder_manager.h> #include <android/hardware/thermal/1.0/IThermal.h> +#include <math.h> +#include <nativehelper/JNIHelp.h> #include <utils/Log.h> #include <utils/String8.h> #include "core_jni_helpers.h" +#include "jni.h" namespace android { +using ::aidl::android::hardware::thermal::CoolingDevice; +using ::aidl::android::hardware::thermal::IThermal; +using ::aidl::android::hardware::thermal::Temperature; +using ::aidl::android::hardware::thermal::TemperatureThreshold; +using ::aidl::android::hardware::thermal::TemperatureType; +using ::aidl::android::hardware::thermal::ThrottlingSeverity; using android::hidl::base::V1_0::IBase; using hardware::hidl_death_recipient; using hardware::hidl_vec; -using hardware::thermal::V1_0::CoolingDevice; -using hardware::thermal::V1_0::CpuUsage; -using hardware::thermal::V1_0::IThermal; -using hardware::thermal::V1_0::Temperature; using hardware::thermal::V1_0::ThermalStatus; using hardware::thermal::V1_0::ThermalStatusCode; template<typename T> @@ -62,20 +63,28 @@ jfloat gUndefinedTemperature; static void getThermalHalLocked(); static std::mutex gThermalHalMutex; -static sp<IThermal> gThermalHal = nullptr; - -// struct ThermalHalDeathRecipient; -struct ThermalHalDeathRecipient : virtual public hidl_death_recipient { - // hidl_death_recipient interface - virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override { - std::lock_guard<std::mutex> lock(gThermalHalMutex); - ALOGE("ThermalHAL just died"); - gThermalHal = nullptr; - getThermalHalLocked(); - } +static sp<hardware::thermal::V1_0::IThermal> gThermalHidlHal = nullptr; +static std::shared_ptr<IThermal> gThermalAidlHal = nullptr; + +struct ThermalHidlHalDeathRecipient : virtual public hidl_death_recipient { + // hidl_death_recipient interface + virtual void serviceDied(uint64_t cookie, const wp<IBase> &who) override { + std::lock_guard<std::mutex> lock(gThermalHalMutex); + ALOGE("Thermal HAL just died"); + gThermalHidlHal = nullptr; + getThermalHalLocked(); + } }; -sp<ThermalHalDeathRecipient> gThermalHalDeathRecipient = nullptr; +static void onThermalAidlBinderDied(void *cookie) { + std::lock_guard<std::mutex> lock(gThermalHalMutex); + ALOGE("Thermal AIDL HAL just died"); + gThermalAidlHal = nullptr; + getThermalHalLocked(); +} + +sp<ThermalHidlHalDeathRecipient> gThermalHidlHalDeathRecipient = nullptr; +ndk::ScopedAIBinder_DeathRecipient gThermalAidlDeathRecipient; // ---------------------------------------------------------------------------- @@ -85,27 +94,49 @@ float finalizeTemperature(float temperature) { // The caller must be holding gThermalHalMutex. static void getThermalHalLocked() { - if (gThermalHal != nullptr) { + if (gThermalAidlHal || gThermalHidlHal) { + return; + } + const std::string thermalInstanceName = std::string(IThermal::descriptor) + "/default"; + if (AServiceManager_isDeclared(thermalInstanceName.c_str())) { + auto binder = AServiceManager_waitForService(thermalInstanceName.c_str()); + auto thermalAidlService = IThermal::fromBinder(ndk::SpAIBinder(binder)); + if (thermalAidlService) { + gThermalAidlHal = thermalAidlService; + if (gThermalAidlDeathRecipient.get() == nullptr) { + gThermalAidlDeathRecipient = ndk::ScopedAIBinder_DeathRecipient( + AIBinder_DeathRecipient_new(onThermalAidlBinderDied)); + } + auto linked = AIBinder_linkToDeath(thermalAidlService->asBinder().get(), + gThermalAidlDeathRecipient.get(), nullptr); + if (linked != STATUS_OK) { + ALOGW("Failed to link to death (AIDL): %d", linked); + gThermalAidlHal = nullptr; + } + } else { + ALOGE("Unable to get Thermal AIDL service"); + } return; } - gThermalHal = IThermal::getService(); + ALOGI("Thermal AIDL service is not declared, trying HIDL"); + gThermalHidlHal = hardware::thermal::V1_0::IThermal::getService(); - if (gThermalHal == nullptr) { + if (gThermalHidlHal == nullptr) { ALOGE("Unable to get Thermal service."); } else { - if (gThermalHalDeathRecipient == nullptr) { - gThermalHalDeathRecipient = new ThermalHalDeathRecipient(); + if (gThermalHidlHalDeathRecipient == nullptr) { + gThermalHidlHalDeathRecipient = new ThermalHidlHalDeathRecipient(); } - hardware::Return<bool> linked = gThermalHal->linkToDeath( - gThermalHalDeathRecipient, 0x451F /* cookie */); + hardware::Return<bool> linked = + gThermalHidlHal->linkToDeath(gThermalHidlHalDeathRecipient, 0x451F /* cookie */); if (!linked.isOk()) { ALOGE("Transaction error in linking to ThermalHAL death: %s", - linked.description().c_str()); - gThermalHal = nullptr; + linked.description().c_str()); + gThermalHidlHal = nullptr; } else if (!linked) { ALOGW("Unable to link to ThermalHal death notifications"); - gThermalHal = nullptr; + gThermalHidlHal = nullptr; } else { ALOGD("Link to death notification successful"); } @@ -117,17 +148,27 @@ static void nativeInit(JNIEnv* env, jobject obj) { getThermalHalLocked(); } -static jfloatArray nativeGetFanSpeeds(JNIEnv *env, jclass /* clazz */) { - std::lock_guard<std::mutex> lock(gThermalHalMutex); - getThermalHalLocked(); - if (gThermalHal == nullptr) { - ALOGE("Couldn't get fan speeds because of HAL error."); +static jfloatArray getFanSpeedsAidl(JNIEnv *env) { + std::vector<CoolingDevice> list; + auto status = gThermalAidlHal->getCoolingDevices(&list); + if (!status.isOk()) { + ALOGE("getFanSpeeds failed status: %s", status.getMessage()); return env->NewFloatArray(0); } + float values[list.size()]; + for (size_t i = 0; i < list.size(); ++i) { + values[i] = list[i].value; + } + jfloatArray fanSpeeds = env->NewFloatArray(list.size()); + env->SetFloatArrayRegion(fanSpeeds, 0, list.size(), values); + return fanSpeeds; +} - hidl_vec<CoolingDevice> list; - Return<void> ret = gThermalHal->getCoolingDevices( - [&list](ThermalStatus status, hidl_vec<CoolingDevice> devices) { +static jfloatArray getFanSpeedsHidl(JNIEnv *env) { + hidl_vec<hardware::thermal::V1_0::CoolingDevice> list; + Return<void> ret = gThermalHidlHal->getCoolingDevices( + [&list](ThermalStatus status, + hidl_vec<hardware::thermal::V1_0::CoolingDevice> devices) { if (status.code == ThermalStatusCode::SUCCESS) { list = std::move(devices); } else { @@ -137,9 +178,9 @@ static jfloatArray nativeGetFanSpeeds(JNIEnv *env, jclass /* clazz */) { }); if (!ret.isOk()) { - ALOGE("getCoolingDevices failed status: %s", ret.description().c_str()); + ALOGE("getFanSpeeds failed status: %s", ret.description().c_str()); + return env->NewFloatArray(0); } - float values[list.size()]; for (size_t i = 0; i < list.size(); ++i) { values[i] = list[i].currentValue; @@ -149,17 +190,79 @@ static jfloatArray nativeGetFanSpeeds(JNIEnv *env, jclass /* clazz */) { return fanSpeeds; } -static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, int type, - int source) { +static jfloatArray nativeGetFanSpeeds(JNIEnv *env, jclass /* clazz */) { std::lock_guard<std::mutex> lock(gThermalHalMutex); getThermalHalLocked(); - if (gThermalHal == nullptr) { - ALOGE("Couldn't get device temperatures because of HAL error."); + if (!gThermalHidlHal && !gThermalAidlHal) { + ALOGE("Couldn't get fan speeds because of HAL error."); return env->NewFloatArray(0); } - hidl_vec<Temperature> list; - Return<void> ret = gThermalHal->getTemperatures( - [&list](ThermalStatus status, hidl_vec<Temperature> temperatures) { + if (gThermalAidlHal) { + return getFanSpeedsAidl(env); + } + return getFanSpeedsHidl(env); +} + +static jfloatArray getDeviceTemperaturesAidl(JNIEnv *env, int type, int source) { + jfloat *values; + size_t length = 0; + if (source == TEMPERATURE_CURRENT) { + std::vector<Temperature> list; + auto status = + gThermalAidlHal->getTemperaturesWithType(static_cast<TemperatureType>(type), &list); + + if (!status.isOk()) { + ALOGE("getDeviceTemperatures failed status: %s", status.getMessage()); + return env->NewFloatArray(0); + } + values = new jfloat[list.size()]; + for (const auto &temp : list) { + if (static_cast<int>(temp.type) == type) { + values[length++] = finalizeTemperature(temp.value); + } + } + } else if (source == TEMPERATURE_THROTTLING_BELOW_VR_MIN) { + values = new jfloat[1]; + values[length++] = gUndefinedTemperature; + } else { + std::vector<TemperatureThreshold> list; + auto status = + gThermalAidlHal->getTemperatureThresholdsWithType(static_cast<TemperatureType>( + type), + &list); + + if (!status.isOk()) { + ALOGE("getDeviceTemperatures failed status: %s", status.getMessage()); + return env->NewFloatArray(0); + } + values = new jfloat[list.size()]; + for (auto &t : list) { + if (static_cast<int>(t.type) == type) { + switch (source) { + case TEMPERATURE_THROTTLING: + values[length++] = + finalizeTemperature(t.hotThrottlingThresholds[static_cast<int>( + ThrottlingSeverity::SEVERE)]); + break; + case TEMPERATURE_SHUTDOWN: + values[length++] = + finalizeTemperature(t.hotThrottlingThresholds[static_cast<int>( + ThrottlingSeverity::SHUTDOWN)]); + break; + } + } + } + } + jfloatArray deviceTemps = env->NewFloatArray(length); + env->SetFloatArrayRegion(deviceTemps, 0, length, values); + return deviceTemps; +} + +static jfloatArray getDeviceTemperaturesHidl(JNIEnv *env, int type, int source) { + hidl_vec<hardware::thermal::V1_0::Temperature> list; + Return<void> ret = gThermalHidlHal->getTemperatures( + [&list](ThermalStatus status, + hidl_vec<hardware::thermal::V1_0::Temperature> temperatures) { if (status.code == ThermalStatusCode::SUCCESS) { list = std::move(temperatures); } else { @@ -170,9 +273,9 @@ static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, if (!ret.isOk()) { ALOGE("getDeviceTemperatures failed status: %s", ret.description().c_str()); + return env->NewFloatArray(0); } - - jfloat values[list.size()]; + float values[list.size()]; size_t length = 0; for (size_t i = 0; i < list.size(); ++i) { if (static_cast<int>(list[i].type) == type) { @@ -197,16 +300,34 @@ static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, return deviceTemps; } +static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, int type, + int source) { + std::lock_guard<std::mutex> lock(gThermalHalMutex); + getThermalHalLocked(); + if (!gThermalHidlHal && !gThermalAidlHal) { + ALOGE("Couldn't get device temperatures because of HAL error."); + return env->NewFloatArray(0); + } + if (gThermalAidlHal) { + return getDeviceTemperaturesAidl(env, type, source); + } + return getDeviceTemperaturesHidl(env, type, source); +} + static jobjectArray nativeGetCpuUsages(JNIEnv *env, jclass /* clazz */) { std::lock_guard<std::mutex> lock(gThermalHalMutex); getThermalHalLocked(); - if (gThermalHal == nullptr || !gCpuUsageInfoClassInfo.initMethod) { + if (gThermalAidlHal) { + ALOGW("getCpuUsages is not supported"); + return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr); + } + if (gThermalHidlHal == nullptr || !gCpuUsageInfoClassInfo.initMethod) { ALOGE("Couldn't get CPU usages because of HAL error."); return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr); } - hidl_vec<CpuUsage> list; - Return<void> ret = gThermalHal->getCpuUsages( - [&list](ThermalStatus status, hidl_vec<CpuUsage> cpuUsages) { + hidl_vec<hardware::thermal::V1_0::CpuUsage> list; + Return<void> ret = gThermalHidlHal->getCpuUsages( + [&list](ThermalStatus status, hidl_vec<hardware::thermal::V1_0::CpuUsage> cpuUsages) { if (status.code == ThermalStatusCode::SUCCESS) { list = std::move(cpuUsages); } else { @@ -217,6 +338,7 @@ static jobjectArray nativeGetCpuUsages(JNIEnv *env, jclass /* clazz */) { if (!ret.isOk()) { ALOGE("getCpuUsages failed status: %s", ret.description().c_str()); + return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr); } jobjectArray cpuUsages = env->NewObjectArray(list.size(), gCpuUsageInfoClassInfo.clazz, diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp index 356e9a95e311..a916b64fc0bd 100644 --- a/services/core/jni/com_android_server_sensor_SensorService.cpp +++ b/services/core/jni/com_android_server_sensor_SensorService.cpp @@ -17,10 +17,13 @@ #define LOG_TAG "NativeSensorService" #include <android-base/properties.h> +#include <android_os_NativeHandle.h> #include <android_runtime/AndroidRuntime.h> #include <core_jni_helpers.h> +#include <cutils/native_handle.h> #include <cutils/properties.h> #include <jni.h> +#include <nativehelper/JNIPlatformHelp.h> #include <sensorservice/SensorService.h> #include <string.h> #include <utils/Log.h> @@ -28,6 +31,8 @@ #include <mutex> +#include "android_util_Binder.h" + #define PROXIMITY_ACTIVE_CLASS \ "com/android/server/sensors/SensorManagerInternal$ProximityActiveListener" @@ -38,7 +43,10 @@ namespace android { static JavaVM* sJvm = nullptr; static jmethodID sMethodIdOnProximityActive; -static jmethodID sMethodIdOnConfigurationChanged; +static jmethodID sMethodIdRuntimeSensorOnConfigurationChanged; +static jmethodID sMethodIdRuntimeSensorOnDirectChannelCreated; +static jmethodID sMethodIdRuntimeSensorOnDirectChannelDestroyed; +static jmethodID sMethodIdRuntimeSensorOnDirectChannelConfigured; class NativeSensorService { public: @@ -47,7 +55,7 @@ public: void registerProximityActiveListener(); void unregisterProximityActiveListener(); jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor, - jobject callback); + jint flags, jobject callback); void unregisterRuntimeSensor(jint handle); jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp, jfloatArray values); @@ -74,6 +82,9 @@ private: status_t onConfigurationChanged(int32_t handle, bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) override; + int onDirectChannelCreated(int fd) override; + void onDirectChannelDestroyed(int channelHandle) override; + int onDirectChannelConfigured(int channelHandle, int sensorHandle, int rateLevel) override; private: jobject mCallback; @@ -108,7 +119,7 @@ void NativeSensorService::unregisterProximityActiveListener() { } jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, - jstring vendor, jobject callback) { + jstring vendor, jint flags, jobject callback) { if (mService == nullptr) { ALOGD("Dropping registerRuntimeSensor, sensor service not available."); return -1; @@ -119,6 +130,11 @@ jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint .vendor = env->GetStringUTFChars(vendor, 0), .version = sizeof(sensor_t), .type = type, +#ifdef __LP64__ + .flags = static_cast<uint64_t>(flags), +#else + .flags = static_cast<uint32_t>(flags), +#endif }; sp<RuntimeSensorCallbackDelegate> callbackDelegate( @@ -234,12 +250,39 @@ NativeSensorService::RuntimeSensorCallbackDelegate::~RuntimeSensorCallbackDelega status_t NativeSensorService::RuntimeSensorCallbackDelegate::onConfigurationChanged( int32_t handle, bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) { auto jniEnv = GetOrAttachJNIEnvironment(sJvm); - return jniEnv->CallIntMethod(mCallback, sMethodIdOnConfigurationChanged, + return jniEnv->CallIntMethod(mCallback, sMethodIdRuntimeSensorOnConfigurationChanged, static_cast<jint>(handle), static_cast<jboolean>(enabled), static_cast<jint>(ns2us(samplingPeriodNs)), static_cast<jint>(ns2us(batchReportLatencyNs))); } +int NativeSensorService::RuntimeSensorCallbackDelegate::onDirectChannelCreated(int fd) { + if (fd <= 0) { + return 0; + } + auto jniEnv = GetOrAttachJNIEnvironment(sJvm); + jobject jfd = jniCreateFileDescriptor(jniEnv, fd); + jobject parcelFileDescriptor = newParcelFileDescriptor(jniEnv, jfd); + return jniEnv->CallIntMethod(mCallback, sMethodIdRuntimeSensorOnDirectChannelCreated, + parcelFileDescriptor); +} + +void NativeSensorService::RuntimeSensorCallbackDelegate::onDirectChannelDestroyed( + int channelHandle) { + auto jniEnv = GetOrAttachJNIEnvironment(sJvm); + return jniEnv->CallVoidMethod(mCallback, sMethodIdRuntimeSensorOnDirectChannelDestroyed, + static_cast<jint>(channelHandle)); +} + +int NativeSensorService::RuntimeSensorCallbackDelegate::onDirectChannelConfigured(int channelHandle, + int sensorHandle, + int rateLevel) { + auto jniEnv = GetOrAttachJNIEnvironment(sJvm); + return jniEnv->CallIntMethod(mCallback, sMethodIdRuntimeSensorOnDirectChannelConfigured, + static_cast<jint>(channelHandle), static_cast<jint>(sensorHandle), + static_cast<jint>(rateLevel)); +} + static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) { NativeSensorService* service = new NativeSensorService(env, listener); return reinterpret_cast<jlong>(service); @@ -256,9 +299,10 @@ static void unregisterProximityActiveListenerNative(JNIEnv* env, jclass, jlong p } static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type, - jstring name, jstring vendor, jobject callback) { + jstring name, jstring vendor, jint flags, + jobject callback) { auto* service = reinterpret_cast<NativeSensorService*>(ptr); - return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback); + return service->registerRuntimeSensor(env, deviceId, type, name, vendor, flags, callback); } static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) { @@ -280,7 +324,7 @@ static const JNINativeMethod methods[] = { {"unregisterProximityActiveListenerNative", "(J)V", reinterpret_cast<void*>(unregisterProximityActiveListenerNative)}, {"registerRuntimeSensorNative", - "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I", + "(JIILjava/lang/String;Ljava/lang/String;IL" RUNTIME_SENSOR_CALLBACK_CLASS ";)I", reinterpret_cast<void*>(registerRuntimeSensorNative)}, {"unregisterRuntimeSensorNative", "(JI)V", reinterpret_cast<void*>(unregisterRuntimeSensorNative)}, @@ -293,8 +337,17 @@ int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) { jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS); sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V"); jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS); - sMethodIdOnConfigurationChanged = + sMethodIdRuntimeSensorOnConfigurationChanged = GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onConfigurationChanged", "(IZII)I"); + sMethodIdRuntimeSensorOnDirectChannelCreated = + GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onDirectChannelCreated", + "(Landroid/os/ParcelFileDescriptor;)I"); + sMethodIdRuntimeSensorOnDirectChannelDestroyed = + GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onDirectChannelDestroyed", "(I)V"); + sMethodIdRuntimeSensorOnDirectChannelConfigured = + GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onDirectChannelConfigured", + "(III)I"); + return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods, NELEM(methods)); } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 8b913db35734..7a4e7dfd56a3 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -17,6 +17,7 @@ package com.android.server.credentials; import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN; +import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR; import static android.content.Context.CREDENTIAL_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -631,7 +632,8 @@ public final class CredentialManagerService } // Send an intent to the UI that we have new enabled providers. - getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent()); + getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(), + LAUNCH_CREDENTIAL_SELECTOR); } @Override diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index 5e167712a8e8..32b14d773b95 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -44,7 +44,6 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest IGetCredentialCallback> implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> { private static final String TAG = "GetRequestSession"; - public GetRequestSession(Context context, int userId, int callingUid, IGetCredentialCallback callback, GetCredentialRequest request, CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal) { @@ -173,6 +172,12 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName) { Log.i(TAG, "in onStatusChanged with status: " + status); + // Auth entry was selected, and it did not have any underlying credentials + if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { + handleEmptyAuthenticationSelection(componentName); + return; + } + // For any other status, we check if all providers are done and then invoke UI if needed if (!isAnyProviderPending()) { // If all provider responses have been received, we can either need the UI, // or we need to respond with error. The only other case is the entry being @@ -186,4 +191,34 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest } } } + + private void handleEmptyAuthenticationSelection(ComponentName componentName) { + // Update auth entry statuses across different provider sessions + mProviders.keySet().forEach(key -> { + ProviderGetSession session = (ProviderGetSession) mProviders.get(key); + if (!session.mComponentName.equals(componentName)) { + session.updateAuthEntriesStatusFromAnotherSession(); + } + }); + + // Invoke UI since it needs to show a snackbar if last auth entry, or a status on each + // auth entries along with other valid entries + getProviderDataAndInitiateUi(); + + // Respond to client if all auth entries are empty and nothing else to show on the UI + if (providerDataContainsEmptyAuthEntriesOnly()) { + respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + "No credentials available"); + } + } + + private boolean providerDataContainsEmptyAuthEntriesOnly() { + for (String key : mProviders.keySet()) { + ProviderGetSession session = (ProviderGetSession) mProviders.get(key); + if (!session.containsEmptyAuthEntriesOnly()) { + return false; + } + } + return true; + } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 12074c7494bf..6498b6afe208 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -241,13 +241,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential if (additionalContentReceived) { Log.i(TAG, "Additional content received - removing authentication entry"); mProviderResponseDataHandler.removeAuthenticationAction(entryKey); + if (!mProviderResponseDataHandler.isEmptyResponse()) { + updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED); + } } else { Log.i(TAG, "Additional content not received"); mProviderResponseDataHandler .updateAuthEntryWithNoCredentialsReceived(entryKey); - } - if (!mProviderResponseDataHandler.isEmptyResponse()) { - updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED); + updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY); } break; case REMOTE_ENTRY_KEY: @@ -414,25 +415,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential /** Returns true if either an exception or a response is found. */ private void onActionEntrySelected(ProviderPendingIntentResponse providerPendingIntentResponse) { - // Action entry is expected to either contain the final GetCredentialResponse, or it is - // also acceptable if it does not contain anything. In the second case, we re-show this - // action on the UI. - if (providerPendingIntentResponse == null) { - Log.i(TAG, "providerPendingIntentResponse is null"); - return; - } - - GetCredentialException exception = maybeGetPendingIntentException( - providerPendingIntentResponse); - if (exception != null) { - invokeCallbackWithError(exception.getType(), exception.getMessage()); - } - GetCredentialResponse response = PendingIntentResultHandler - .extractGetCredentialResponse( - providerPendingIntentResponse.getResultData()); - if (response != null) { - mCallbacks.onFinalResponseReceived(mComponentName, response); - } + Log.i(TAG, "onActionEntrySelected"); + onCredentialEntrySelected(providerPendingIntentResponse); } @@ -449,11 +433,32 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential /** * When an invalid state occurs, e.g. entry mismatch or no response from provider, - * we send back a TYPE_UNKNOWN error as to the developer. + * we send back a TYPE_NO_CREDENTIAL error as to the developer. */ private void invokeCallbackOnInternalInvalidState() { mCallbacks.onFinalErrorReceived(mComponentName, - GetCredentialException.TYPE_UNKNOWN, null); + GetCredentialException.TYPE_NO_CREDENTIAL, null); + } + + /** Update auth entries status based on an auth entry selected from a different session. */ + public void updateAuthEntriesStatusFromAnotherSession() { + // Pass null for entryKey if the auth entry selected belongs to a different session + mProviderResponseDataHandler.updateAuthEntryWithNoCredentialsReceived(/*entryKey=*/null); + } + + /** Returns true if the provider response contains empty auth entries only, false otherwise. **/ + public boolean containsEmptyAuthEntriesOnly() { + // We do not consider action entries here because if actions are the only entries, + // we don't show the UI + return mProviderResponseDataHandler.mUiCredentialEntries.isEmpty() + && mProviderResponseDataHandler.mUiRemoteEntry == null + && mProviderResponseDataHandler.mUiAuthenticationEntries + .values().stream().allMatch( + e -> e.second.getStatus() == AuthenticationEntry + .STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT + || e.second.getStatus() + == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT + ); } private class ProviderResponseDataHandler { @@ -610,7 +615,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential ? null : mUiCredentialEntries.get(entryKey).first; } - public void updateAuthEntryWithNoCredentialsReceived(String entryKey) { + public void updateAuthEntryWithNoCredentialsReceived(@Nullable String entryKey) { + if (entryKey == null) { + // Auth entry from a different provider was selected by the user. + updatePreviousMostRecentAuthEntry(); + return; + } updatePreviousMostRecentAuthEntry(); updateMostRecentAuthEntry(entryKey); } diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index 2f9d57872c3c..ecddcf30f88d 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -66,7 +66,8 @@ public abstract class ProviderSession<T, R> * on the credMan UI. */ public static boolean isUiInvokingStatus(Status status) { - return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED; + return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED + || status == Status.NO_CREDENTIALS_FROM_AUTH_ENTRY; } /** @@ -140,7 +141,7 @@ public abstract class ProviderSession<T, R> PENDING_INTENT_INVOKED, CREDENTIAL_RECEIVED_FROM_SELECTION, SAVE_ENTRIES_RECEIVED, CANCELED, - NO_CREDENTIALS, EMPTY_RESPONSE, COMPLETE + NO_CREDENTIALS, EMPTY_RESPONSE, NO_CREDENTIALS_FROM_AUTH_ENTRY, COMPLETE } /** Converts exception to a provider session status. */ diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index f0d05c5dfd05..f8bbfcfb6a4d 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -216,7 +216,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan * Returns true if at least one provider is ready for UI invocation, and no * provider is pending a response. */ - boolean isUiInvocationNeeded() { + protected boolean isUiInvocationNeeded() { for (ProviderSession session : mProviders.values()) { if (ProviderSession.isUiInvokingStatus(session.getStatus())) { return true; diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp index 05a8b11830f7..07ddda39649a 100644 --- a/services/tests/InputMethodSystemServerTests/Android.bp +++ b/services/tests/InputMethodSystemServerTests/Android.bp @@ -52,6 +52,10 @@ android_test { "android.test.runner", ], + data: [ + ":SimpleTestIme", + ], + certificate: "platform", platform_apis: true, test_suites: ["device-tests"], diff --git a/services/tests/InputMethodSystemServerTests/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/AndroidTest.xml index 92be78060da8..13719343ac49 100644 --- a/services/tests/InputMethodSystemServerTests/AndroidTest.xml +++ b/services/tests/InputMethodSystemServerTests/AndroidTest.xml @@ -21,6 +21,7 @@ <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> <option name="test-file-name" value="FrameworksInputMethodSystemServerTests.apk" /> + <option name="test-file-name" value="SimpleTestIme.apk" /> </target_preparer> <option name="test-tag" value="FrameworksInputMethodSystemServerTests" /> diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java new file mode 100644 index 000000000000..7cbfc52c5cce --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertEquals; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.util.ArrayMap; +import android.view.inputmethod.InputMethod; +import android.view.inputmethod.InputMethodInfo; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class InputMethodManagerServiceRestrictImeAmountTest extends + InputMethodManagerServiceTestBase { + + @Test + public void testFilterInputMethodServices_loadsAllImesBelowThreshold() { + List<ResolveInfo> resolveInfoList = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + resolveInfoList.add( + createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i)); + } + + final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList, + List.of()); + assertEquals(5, methodList.size()); + } + + @Test + public void testFilterInputMethodServices_ignoresImesBeyondThreshold() { + List<ResolveInfo> resolveInfoList = new ArrayList<>(); + for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) { + resolveInfoList.add( + createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i)); + } + + final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList, + List.of()); + assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo( + InputMethodInfo.MAX_IMES_PER_PACKAGE); + } + + @Test + public void testFilterInputMethodServices_loadsSystemImesBeyondThreshold() { + List<ResolveInfo> resolveInfoList = new ArrayList<>(); + for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) { + resolveInfoList.add( + createFakeSystemResolveInfo("com.android.apps.inputmethod.systemime", + "SystemIME" + i)); + } + + final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList, + List.of()); + assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo( + 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE); + } + + @Test + public void testFilterInputMethodServices_ignoresImesBeyondThresholdFromTwoPackages() { + List<ResolveInfo> resolveInfoList = new ArrayList<>(); + for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) { + resolveInfoList.add( + createFakeResolveInfo("com.android.apps.inputmethod.simpleime1", "IME1_" + i)); + } + for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) { + resolveInfoList.add( + createFakeResolveInfo("com.android.apps.inputmethod.simpleime2", "IME2_" + i)); + } + + final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList, + List.of()); + assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo( + 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE); + } + + @Test + public void testFilterInputMethodServices_stillLoadsEnabledImesBeyondThreshold() { + final ResolveInfo enabledIme = createFakeResolveInfo( + "com.android.apps.inputmethod.simpleime_enabled", "EnabledIME"); + + List<ResolveInfo> resolveInfoList = new ArrayList<>(); + for (int i = 0; i < 2 * InputMethodInfo.MAX_IMES_PER_PACKAGE; i++) { + resolveInfoList.add( + createFakeResolveInfo("com.android.apps.inputmethod.simpleime", "IME" + i)); + } + resolveInfoList.add(enabledIme); + + final List<InputMethodInfo> methodList = filterInputMethodServices(resolveInfoList, + List.of(new ComponentName(enabledIme.serviceInfo.packageName, + enabledIme.serviceInfo.name).flattenToShortString())); + + assertWithMessage("Filtered IMEs").that(methodList.size()).isEqualTo( + 1 + InputMethodInfo.MAX_IMES_PER_PACKAGE); + } + + private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList, + List<String> enabledComponents) { + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + InputMethodManagerService.filterInputMethodServices(new ArrayMap<>(), methodMap, methodList, + enabledComponents, mContext, resolveInfoList); + return methodList; + } + + private ResolveInfo createFakeSystemResolveInfo(String packageName, String componentName) { + final ResolveInfo ime = createFakeResolveInfo(packageName, componentName); + ime.serviceInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + return ime; + } + + private ResolveInfo createFakeResolveInfo(String packageName, String componentName) { + final ResolveInfo ime = getResolveInfo("com.android.apps.inputmethod.simpleime"); + if (packageName != null) { + ime.serviceInfo.packageName = packageName; + } + if (componentName != null) { + ime.serviceInfo.name = componentName; + } + return ime; + } + + private ResolveInfo getResolveInfo(String packageName) { + final int flags = PackageManager.GET_META_DATA + | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; + final List<ResolveInfo> ime = mContext.getPackageManager().queryIntentServices( + new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName), + PackageManager.ResolveInfoFlags.of(flags)); + assertWithMessage("Loaded IMEs").that(ime.size()).isGreaterThan(0); + return ime.get(0); + } +} 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 dbdffd05714a..9501b9604fa0 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -127,7 +127,7 @@ public class InputMethodManagerServiceTestBase { mockitoSession() .initMocks(this) .strictness(Strictness.LENIENT) - .mockStatic(LocalServices.class) + .spyStatic(LocalServices.class) .mockStatic(ServiceManager.class) .mockStatic(SystemServerInitThreadPool.class) .startMocking(); @@ -212,6 +212,7 @@ public class InputMethodManagerServiceTestBase { new InputMethodManagerService.Lifecycle(mContext, mInputMethodManagerService); // Public local InputMethodManagerService. + LocalServices.removeServiceForTest(InputMethodManagerInternal.class); lifecycle.onStart(); try { // After this boot phase, services can broadcast Intents. @@ -237,6 +238,7 @@ public class InputMethodManagerServiceTestBase { if (mMockingSession != null) { mMockingSession.finishMocking(); } + LocalServices.removeServiceForTest(InputMethodManagerInternal.class); } protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput) diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java new file mode 100644 index 000000000000..111cabd298f5 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class SwitchKeyboardLayoutTest extends InputMethodManagerServiceTestBase { + @Test + public void testSwitchToNextKeyboardLayout() { + ExtendedMockito.spyOn(mInputMethodManagerService.mSwitchingController); + InputMethodManagerInternal.get().switchKeyboardLayout(1); + verify(mInputMethodManagerService.mSwitchingController) + .getNextInputMethodLocked(eq(true) /* onlyCurrentIme */, any(), any()); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index 95c2ed2f841f..99da415380cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -759,40 +759,6 @@ public final class BroadcastQueueModernImplTest { } /** - * Verify that sending a broadcast that removes any matching pending - * broadcasts is applied as expected. - */ - @Test - public void testRemoveMatchingFilter() { - final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON); - final BroadcastOptions optionsOn = BroadcastOptions.makeBasic(); - optionsOn.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_OFF)); - - final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF); - final BroadcastOptions optionsOff = BroadcastOptions.makeBasic(); - optionsOff.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_ON)); - - // Halt all processing so that we get a consistent view - mHandlerThread.getLooper().getQueue().postSyncBarrier(); - - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn)); - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff)); - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn)); - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff)); - - // While we're here, give our health check some test coverage - mImpl.checkHealthLocked(); - - // Marching through the queue we should only have one SCREEN_OFF - // broadcast, since that's the last state we dispatched - final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, - getUidForPackage(PACKAGE_GREEN)); - queue.makeActiveNextPending(); - assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction()); - assertTrue(queue.isEmpty()); - } - - /** * Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MOST_RECENT works as expected. */ @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 95a588497980..5f82ec1dde02 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -53,6 +53,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.display.LocalDisplayAdapter.BacklightAdapter; +import com.android.server.display.mode.DisplayModeDirector; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index 991d5667206b..8b420a36602c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -234,15 +234,9 @@ public class JobSchedulerServiceTest { createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH)); JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs", createJobInfo(6)); - JobStatus jobDT = createJobStatus("testGetMinJobExecutionGuaranteeMs", - createJobInfo(7) - .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); - JobStatus jobUI = createJobStatus("testGetMinJobExecutionGuaranteeMs", - createJobInfo(8)); // TODO(255371817): add setUserInitiated(true) JobStatus jobUIDT = createJobStatus("testGetMinJobExecutionGuaranteeMs", - // TODO(255371817): add setUserInitiated(true) createJobInfo(9) - .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); + .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); spyOn(ejMax); spyOn(ejHigh); @@ -250,8 +244,6 @@ public class JobSchedulerServiceTest { spyOn(ejHighDowngraded); spyOn(jobHigh); spyOn(jobDef); - spyOn(jobDT); - spyOn(jobUI); spyOn(jobUIDT); when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true); @@ -260,14 +252,11 @@ public class JobSchedulerServiceTest { when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false); when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false); when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false); - when(jobUI.shouldTreatAsUserInitiatedJob()).thenReturn(true); when(jobUIDT.shouldTreatAsUserInitiatedJob()).thenReturn(true); ConnectivityController connectivityController = mService.getConnectivityController(); spyOn(connectivityController); mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS; - mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 15 * MINUTE_IN_MILLIS; - mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS; mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f; mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS; mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS; @@ -284,37 +273,14 @@ public class JobSchedulerServiceTest { mService.getMinJobExecutionGuaranteeMs(jobHigh)); assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS, mService.getMinJobExecutionGuaranteeMs(jobDef)); - grantRunUserInitiatedJobsPermission(false); // Without permission - assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - mService.getMinJobExecutionGuaranteeMs(jobDT)); - grantRunUserInitiatedJobsPermission(true); // With permission - doReturn(ConnectivityController.UNKNOWN_TIME) - .when(connectivityController).getEstimatedTransferTimeMs(any()); - assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - mService.getMinJobExecutionGuaranteeMs(jobDT)); - doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS / 2) - .when(connectivityController).getEstimatedTransferTimeMs(any()); - assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - mService.getMinJobExecutionGuaranteeMs(jobDT)); - doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS * 2) - .when(connectivityController).getEstimatedTransferTimeMs(any()); - assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - mService.getMinJobExecutionGuaranteeMs(jobDT)); - doReturn(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS * 2) - .when(connectivityController).getEstimatedTransferTimeMs(any()); - assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - mService.getMinJobExecutionGuaranteeMs(jobDT)); // UserInitiated grantRunUserInitiatedJobsPermission(false); - // Permission isn't granted, so it should just be treated as a regular data transfer job. - assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS, - mService.getMinJobExecutionGuaranteeMs(jobUIDT)); // Permission isn't granted, so it should just be treated as a regular job. assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS, - mService.getMinJobExecutionGuaranteeMs(jobUI)); + mService.getMinJobExecutionGuaranteeMs(jobUIDT)); grantRunUserInitiatedJobsPermission(true); // With permission - assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS, - mService.getMinJobExecutionGuaranteeMs(jobUI)); + assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS, + mService.getMinJobExecutionGuaranteeMs(jobUIDT)); doReturn(ConnectivityController.UNKNOWN_TIME) .when(connectivityController).getEstimatedTransferTimeMs(any()); assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS, @@ -338,21 +304,10 @@ public class JobSchedulerServiceTest { @Test public void testGetMaxJobExecutionTimeMs() { - JobStatus jobDT = createJobStatus("testGetMaxJobExecutionTimeMs", - createJobInfo(7) - .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); - JobStatus jobUI = createJobStatus("testGetMaxJobExecutionTimeMs", - createJobInfo(9)); // TODO(255371817): add setUserInitiated(true) JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs", - // TODO(255371817): add setUserInitiated(true) createJobInfo(10) - .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); - - spyOn(jobDT); - spyOn(jobUI); + .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); spyOn(jobUIDT); - - when(jobUI.shouldTreatAsUserInitiatedJob()).thenReturn(true); when(jobUIDT.shouldTreatAsUserInitiatedJob()).thenReturn(true); QuotaController quotaController = mService.getQuotaController(); @@ -365,17 +320,9 @@ public class JobSchedulerServiceTest { .when(quotaController).getMaxJobExecutionTimeMsLocked(any()); grantRunUserInitiatedJobsPermission(true); - assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS, - mService.getMaxJobExecutionTimeMs(jobDT)); - assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_LIMIT_MS, - mService.getMaxJobExecutionTimeMs(jobUI)); assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS, mService.getMaxJobExecutionTimeMs(jobUIDT)); grantRunUserInitiatedJobsPermission(false); - assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS, - mService.getMaxJobExecutionTimeMs(jobDT)); - assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, - mService.getMaxJobExecutionTimeMs(jobUI)); assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, mService.getMaxJobExecutionTimeMs(jobUIDT)); } @@ -478,7 +425,8 @@ public class JobSchedulerServiceTest { @Test public void testGetRescheduleJobForFailure_userStopped() { JobStatus uiJob = createJobStatus("testGetRescheduleJobForFailure", - createJobInfo().setUserInitiated(true)); + createJobInfo().setUserInitiated(true) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)); JobStatus uvJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo()); spyOn(uvJob); doReturn(true).when(uvJob).isUserVisibleJob(); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 1e65b380310f..6bc552c7f32e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -619,7 +619,7 @@ public class FlexibilityControllerTest { @Test public void testExceptions_UserInitiated() { JobInfo.Builder jb = createJob(0); - jb.setUserInitiated(true); + jb.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); JobStatus js = createJobStatus("testExceptions_UserInitiated", jb); assertFalse(js.hasFlexibilityConstraint()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index e6bc72f94f05..5dc8ed58994d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -170,6 +170,7 @@ public class JobStatusTest { final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setUserInitiated(true) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); JobStatus job = createJobStatus(jobInfo); assertTrue(job.canRunInBatterySaver()); @@ -216,6 +217,7 @@ public class JobStatusTest { final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setUserInitiated(true) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); JobStatus job = createJobStatus(jobInfo); assertTrue(job.canRunInDoze()); @@ -236,6 +238,7 @@ public class JobStatusTest { // User-initiated jobs are always user-visible unless they've been demoted. jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setUserInitiated(true) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); job = createJobStatus(jobInfo); @@ -507,6 +510,7 @@ public class JobStatusTest { jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setUserInitiated(true) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); job = createJobStatus(jobInfo); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 3f5d1139c9db..06ba5dd6069b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -664,7 +664,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { private fun mockQueryActivities(action: String, vararg activities: ActivityInfo) { whenever(mocks.componentResolver.queryActivities(any(), argThat { intent: Intent? -> intent != null && (action == intent.action) }, - nullable(), anyLong(), anyInt(), anyInt())) { + nullable(), anyLong(), anyInt())) { ArrayList(activities.asList().map { info: ActivityInfo? -> ResolveInfo().apply { activityInfo = info } }) @@ -674,7 +674,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { private fun mockQueryServices(action: String, vararg services: ServiceInfo) { whenever(mocks.componentResolver.queryServices(any(), argThat { intent: Intent? -> intent != null && (action == intent.action) }, - nullable(), anyLong(), anyInt(), anyInt())) { + nullable(), anyLong(), anyInt())) { ArrayList(services.asList().map { info -> ResolveInfo().apply { serviceInfo = info } }) 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 503e579062d4..96eca7171af0 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -289,7 +289,7 @@ public class AccessibilityManagerServiceTest { @SmallTest @Test - public void testRegisterProxyWithoutPermission() throws Exception { + public void testRegisterProxyWithoutA11yPermission() throws Exception { doThrow(SecurityException.class).when(mMockSecurityPolicy) .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); @@ -301,6 +301,18 @@ public class AccessibilityManagerServiceTest { @SmallTest @Test + public void testRegisterProxyWithoutDevicePermission() throws Exception { + doThrow(SecurityException.class).when(mMockSecurityPolicy) + .enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE); + + assertThrows(SecurityException.class, + () -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY)); + verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(), + any(), any(), any()); + } + + @SmallTest + @Test public void testRegisterProxyForDefaultDisplay() throws Exception { assertThrows(IllegalArgumentException.class, () -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.DEFAULT_DISPLAY)); @@ -328,7 +340,7 @@ public class AccessibilityManagerServiceTest { @SmallTest @Test - public void testUnRegisterProxyWithoutPermission() throws Exception { + public void testUnRegisterProxyWithoutA11yPermission() { doThrow(SecurityException.class).when(mMockSecurityPolicy) .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); @@ -339,6 +351,17 @@ public class AccessibilityManagerServiceTest { @SmallTest @Test + public void testUnRegisterProxyWithoutDevicePermission() { + doThrow(SecurityException.class).when(mMockSecurityPolicy) + .enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE); + + assertThrows(SecurityException.class, + () -> mA11yms.unregisterProxyForDisplay(TEST_DISPLAY)); + verify(mProxyManager, never()).unregisterProxy(TEST_DISPLAY); + } + + @SmallTest + @Test public void testOnMagnificationTransitionFailed_capabilitiesIsAll_fallBackToPreviousMode() { final AccessibilityUserState userState = mA11yms.mUserStates.get( mA11yms.getCurrentUserIdLocked()); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 51d3bae7d32c..306ce4dbf707 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -44,6 +44,8 @@ import android.annotation.NonNull; import android.graphics.PointF; import android.os.Handler; import android.os.Message; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.testing.TestableContext; import android.util.DebugUtils; import android.view.InputDevice; @@ -507,6 +509,91 @@ public class FullScreenMagnificationGestureHandlerTest { verify(mWindowMagnificationPromptController).showNotificationIfNeeded(); } + @Test + public void testTransitToPanningState_scaleDifferenceOverThreshold_startDetecting() { + final float scale = 2.0f; + final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState + .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD; + final float persistedScale = (1.0f + threshold) * scale + 1.0f; + mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X, + DEFAULT_Y, /* animate= */ false, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + mFullScreenMagnificationController.persistScale(DISPLAY_0); + mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X, + DEFAULT_Y, /* animate= */ false, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + + mMgh.transitionTo(mMgh.mPanningScalingState); + + assertTrue(mMgh.mPanningScalingState.mDetectingPassPersistedScale); + + mMgh.clearAndTransitionToStateDetecting(); + mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false); + } + + @Test + public void testTransitToPanningState_scaleDifferenceLessThanThreshold_doNotDetect() { + final float scale = 2.0f; + final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState + .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD; + final float persistedScale = (1.0f + threshold) * scale - 0.1f; + mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X, + DEFAULT_Y, /* animate= */ false, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + mFullScreenMagnificationController.persistScale(DISPLAY_0); + mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X, + DEFAULT_Y, /* animate= */ false, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + + mMgh.transitionTo(mMgh.mPanningScalingState); + + assertFalse(mMgh.mPanningScalingState.mDetectingPassPersistedScale); + + mMgh.clearAndTransitionToStateDetecting(); + mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false); + } + + @Test + public void testPanningScaleToPersistedScale_detecting_vibrateAndClear() { + Vibrator vibrator = mock(Vibrator.class); + mContext.addMockSystemService(Vibrator.class, vibrator); + + mMgh.mPanningScalingState.mDetectingPassPersistedScale = true; + + final float persistedScale = + mFullScreenMagnificationController.getPersistedScale(DISPLAY_0); + + mMgh.transitionTo(mMgh.mPanningScalingState); + mMgh.mPanningScalingState.setScaleAndClearIfNeeded(persistedScale, DEFAULT_X, DEFAULT_Y); + + verify(vibrator).vibrate(any(VibrationEffect.class)); + assertFalse(mMgh.mPanningScalingState.mScaling); + + mMgh.clearAndTransitionToStateDetecting(); + mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false); + } + + @Test + public void testPanningScaleOverThreshold_notDetecting_startDetecting() { + final float persistedScale = + mFullScreenMagnificationController.getPersistedScale(DISPLAY_0); + + mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X, + DEFAULT_Y, /* animate= */ false, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + mMgh.transitionTo(mMgh.mPanningScalingState); + + final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState + .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD; + final float scale = (1.0f + threshold) * persistedScale + 1.0f; + mMgh.mPanningScalingState.setScaleAndClearIfNeeded(scale, DEFAULT_X, DEFAULT_Y); + + assertTrue(mMgh.mPanningScalingState.mDetectingPassPersistedScale); + + mMgh.clearAndTransitionToStateDetecting(); + mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false); + } + private void assertActionsInOrder(List<MotionEvent> actualEvents, List<Integer> expectedActions) { assertTrue(actualEvents.size() == expectedActions.size()); diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 1298e7bbd344..24b003c2c011 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -24,6 +24,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.nullable; import static org.mockito.Mockito.times; @@ -37,6 +38,7 @@ import android.accounts.AccountManagerInternal; import android.accounts.CantAddAccountActivity; import android.accounts.IAccountManagerResponse; import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.app.INotificationManager; import android.app.PropertyInvalidatedCache; import android.app.admin.DevicePolicyManager; @@ -171,6 +173,16 @@ public class AccountManagerServiceTest extends AndroidTestCase { setContext(mockContext); mTestInjector = new TestInjector(realTestContext, mockContext, mMockNotificationManager); mAms = new AccountManagerService(mTestInjector); + doAnswer(invocation -> { + final Intent intent = invocation.getArgument(0); + final Bundle options = invocation.getArgument(3); + if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.endsWith(intent.getAction())) { + final BroadcastOptions bOptions = new BroadcastOptions(options); + assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT, + bOptions.getDeliveryGroupPolicy()); + } + return null; + }).when(mMockContext).sendBroadcastAsUser(any(), any(), any(), any()); } @Override @@ -3142,7 +3154,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { mAccountRemovedBroadcasts = 0; ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mMockContext, atLeast(expectedBroadcasts)).sendBroadcastAsUser(captor.capture(), - any(UserHandle.class)); + any(UserHandle.class), any(), any()); for (Intent intent : captor.getAllValues()) { if (AccountManager.ACTION_VISIBLE_ACCOUNTS_CHANGED.equals(intent.getAction())) { mVisibleAccountsChangedBroadcasts++; @@ -3499,7 +3511,19 @@ public class AccountManagerServiceTest extends AndroidTestCase { @Override public void sendBroadcastAsUser(Intent intent, UserHandle user) { - mMockContext.sendBroadcastAsUser(intent, user); + sendBroadcastAsUser(intent, user, null, null); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, + Bundle options) { + mMockContext.sendBroadcastAsUser(intent, user, receiverPermission, options); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + return mMockContext.registerReceiver(receiver, filter, broadcastPermission, scheduler); } @Override diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index b0c3a6e26b7a..e9d82696affc 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -37,9 +37,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.hardware.biometrics.AuthenticateOptions; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricService; +import android.hardware.fingerprint.Fingerprint; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -48,6 +51,7 @@ import android.platform.test.annotations.Presubmit; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; +import android.util.Slog; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -58,6 +62,8 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.nano.BiometricSchedulerProto; import com.android.server.biometrics.nano.BiometricsProto; +import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession; import org.junit.Before; import org.junit.Rule; @@ -67,6 +73,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.function.Supplier; @Presubmit @@ -78,6 +87,9 @@ public class BiometricSchedulerTest { private static final String TAG = "BiometricSchedulerTest"; private static final int TEST_SENSOR_ID = 1; private static final int LOG_NUM_RECENT_OPERATIONS = 2; + private static final Fingerprint TEST_FINGERPRINT = new Fingerprint("" /* name */, + 1 /* fingerId */, TEST_SENSOR_ID); + @Rule public final TestableContext mContext = new TestableContext( InstrumentationRegistry.getContext(), null); @@ -673,6 +685,56 @@ public class BiometricSchedulerTest { } + @Test + public void testTwoInternalCleanupOps_withFirstFavorHalEnrollment() throws Exception { + final String owner = "test.owner"; + final int userId = 1; + final Supplier<Object> daemon = () -> mock(AidlSession.class); + final FingerprintUtils utils = mock(FingerprintUtils.class); + final Map<Integer, Long> authenticatorIds = new HashMap<>(); + final ClientMonitorCallback callback0 = mock(ClientMonitorCallback.class); + final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class); + final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class); + + final TestInternalCleanupClient client1 = new + TestInternalCleanupClient(mContext, daemon, userId, + owner, TEST_SENSOR_ID, mock(BiometricLogger.class), + mBiometricContext, utils, authenticatorIds); + final TestInternalCleanupClient client2 = new + TestInternalCleanupClient(mContext, daemon, userId, + owner, TEST_SENSOR_ID, mock(BiometricLogger.class), + mBiometricContext, utils, authenticatorIds); + + //add initial start client to scheduler, so later clients will be on pending operation queue + final TestHalClientMonitor startClient = new TestHalClientMonitor(mContext, mToken, + daemon); + mScheduler.scheduleClientMonitor(startClient, callback0); + + //add first cleanup client which favors enrollments from HAL + client1.setFavorHalEnrollments(); + mScheduler.scheduleClientMonitor(client1, callback1); + assertEquals(1, mScheduler.mPendingOperations.size()); + + when(utils.getBiometricsForUser(mContext, userId)).thenAnswer(i -> + new ArrayList<>(client1.getFingerprints())); + + //add second cleanup client + mScheduler.scheduleClientMonitor(client2, callback2); + + //finish the start client, so other pending clients are processed + startClient.getCallback().onClientFinished(startClient, true); + + waitForIdle(); + + assertTrue(client1.isAlreadyDone()); + assertTrue(client2.isAlreadyDone()); + assertNull(mScheduler.mCurrentOperation); + verify(utils, never()).removeBiometricForUser(mContext, userId, + TEST_FINGERPRINT.getBiometricId()); + assertEquals(1, client1.getFingerprints().size()); + } + + private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception { return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer)); } @@ -681,7 +743,37 @@ public class BiometricSchedulerTest { TestableLooper.get(this).processAllMessages(); } - private static class TestAuthenticationClient extends AuthenticationClient<Object> { + private static class TestAuthenticateOptions implements AuthenticateOptions { + @Override + public int getUserId() { + return 0; + } + + @Override + public int getSensorId() { + return TEST_SENSOR_ID; + } + + @Override + public int getDisplayState() { + return DISPLAY_STATE_UNKNOWN; + } + + @NonNull + @Override + public String getOpPackageName() { + return "some.test.name"; + } + + @Nullable + @Override + public String getAttributionTag() { + return null; + } + } + + private static class TestAuthenticationClient + extends AuthenticationClient<Object, TestAuthenticateOptions> { boolean mStartedHal = false; boolean mStoppedHal = false; boolean mDestroyed = false; @@ -700,9 +792,10 @@ public class BiometricSchedulerTest { @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int cookie, @NonNull BiometricContext biometricContext) { - super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */, - false /* restricted */, TAG, cookie, false /* requireConfirmation */, - TEST_SENSOR_ID, mock(BiometricLogger.class), biometricContext, + super(context, lazyDaemon, token, listener, 0 /* operationId */, + false /* restricted */, new TestAuthenticateOptions(), cookie, + false /* requireConfirmation */, + mock(BiometricLogger.class), biometricContext, true /* isStrongBiometric */, null /* taskStackListener */, null /* lockoutTracker */, false /* isKeyguard */, true /* shouldVibrate */, @@ -835,4 +928,90 @@ public class BiometricSchedulerTest { mDestroyed = true; } } + + private static class TestInternalEnumerateClient extends InternalEnumerateClient<Object> { + private static final String TAG = "TestInternalEnumerateClient"; + + + protected TestInternalEnumerateClient(@NonNull Context context, + @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token, int userId, + @NonNull String owner, @NonNull List<Fingerprint> enrolledList, + @NonNull BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { + super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId, + logger, biometricContext); + } + + @Override + protected void startHalOperation() { + Slog.d(TAG, "TestInternalEnumerateClient#startHalOperation"); + onEnumerationResult(TEST_FINGERPRINT, 0 /* remaining */); + } + } + + private static class TestRemovalClient extends RemovalClient<Fingerprint, Object> { + private static final String TAG = "TestRemovalClient"; + + TestRemovalClient(@NonNull Context context, + @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token, + @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, + @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull Map<Integer, Long> authenticatorIds) { + super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, + logger, biometricContext, authenticatorIds); + } + + @Override + protected void startHalOperation() { + Slog.d(TAG, "Removing template from hw"); + onRemoved(TEST_FINGERPRINT, 0); + } + } + + private static class TestInternalCleanupClient extends + InternalCleanupClient<Fingerprint, Object> { + private List<Fingerprint> mFingerprints = new ArrayList<>(); + + TestInternalCleanupClient(@NonNull Context context, + @NonNull Supplier<Object> lazyDaemon, + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) { + super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, + utils, authenticatorIds); + } + + @Override + protected InternalEnumerateClient<Object> getEnumerateClient(Context context, + Supplier<Object> lazyDaemon, IBinder token, int userId, String owner, + List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { + return new TestInternalEnumerateClient(context, lazyDaemon, token, userId, owner, + enrolledList, utils, sensorId, + logger.swapAction(context, BiometricsProtoEnums.ACTION_ENUMERATE), + biometricContext); + } + + @Override + protected RemovalClient<Fingerprint, Object> getRemovalClient(Context context, + Supplier<Object> lazyDaemon, IBinder token, int biometricId, int userId, + String owner, BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + Map<Integer, Long> authenticatorIds) { + return new TestRemovalClient(context, lazyDaemon, token, null, + new int[]{biometricId}, userId, owner, utils, sensorId, logger, + biometricContext, authenticatorIds); + } + + @Override + protected void onAddUnknownTemplate(int userId, + @NonNull BiometricAuthenticator.Identifier identifier) { + mFingerprints.add((Fingerprint) identifier); + } + + public List<Fingerprint> getFingerprints() { + return mFingerprints; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java index 184a5562a689..3ff802c0125c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -33,6 +33,7 @@ import android.content.ComponentName; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.face.ISession; import android.hardware.face.Face; +import android.hardware.face.FaceAuthenticateOptions; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -153,14 +154,18 @@ public class FaceAuthenticationClientTest { when(mHal.getInterfaceVersion()).thenReturn(version); final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder() + .setOpPackageName("test-owner") + .setUserId(5) + .setSensorId(9) + .build(); return new FaceAuthenticationClient(mContext, () -> aidl, mToken, - 2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID, - false /* restricted */, "test-owner", 4 /* cookie */, - false /* requireConfirmation */, 9 /* sensorId */, + 2 /* requestId */, mClientMonitorCallbackConverter, OP_ID, + false /* restricted */, options, 4 /* cookie */, + false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, mUsageStats, null /* mLockoutCache */, false /* allowBackgroundAuthentication */, - null /* sensorPrivacyManager */, - 0 /* biometricStrength */) { + null /* sensorPrivacyManager */, 0 /* biometricStrength */) { @Override protected ActivityTaskManager getActivityTaskManager() { return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java index e0fdb8cfc74e..c4c550549f73 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.hardware.biometrics.face.ISession; +import android.hardware.face.FaceAuthenticateOptions; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -112,8 +113,13 @@ public class FaceDetectClientTest { final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); return new FaceDetectClient(mContext, () -> aidl, mToken, - 99 /* requestId */, mClientMonitorCallbackConverter, USER_ID, - "own-it", 5 /* sensorId */, mBiometricLogger, mBiometricContext, + 99 /* requestId */, mClientMonitorCallbackConverter, + new FaceAuthenticateOptions.Builder() + .setUserId(USER_ID) + .setSensorId(5) + .setOpPackageName("own-it") + .build(), + mBiometricLogger, mBiometricContext, false /* isStrongBiometric */, null /* sensorPrivacyManager */); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java index 3b66eaba6129..54d6478ecd4a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java @@ -144,7 +144,7 @@ public class BiometricStateCallbackTest { @Test public void testAuthentication_enrollmentCallbackNeverNotified() { - AuthenticationClient<?> client = mock(AuthenticationClient.class); + AuthenticationClient<?, ?> client = mock(AuthenticationClient.class); mCallback.onClientFinished(client, true /* success */); verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(), anyBoolean()); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java index a4048a27dfb5..25a700a5275f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java @@ -16,24 +16,35 @@ package com.android.server.biometrics.sensors.fingerprint; +import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; +import static android.app.AppOpsManager.OP_USE_BIOMETRIC; +import static android.app.AppOpsManager.OP_USE_FINGERPRINT; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; +import static android.hardware.fingerprint.FingerprintManager.SENSOR_ID_ANY; +import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR; +import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL; + +import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +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.app.AppOpsManager; import android.content.pm.PackageManager; import android.hardware.biometrics.IBiometricService; -import android.hardware.biometrics.common.CommonProps; -import android.hardware.biometrics.common.SensorStrength; -import android.hardware.biometrics.fingerprint.FingerprintSensorType; -import android.hardware.biometrics.fingerprint.IFingerprint; -import android.hardware.biometrics.fingerprint.SensorLocation; -import android.hardware.biometrics.fingerprint.SensorProps; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; +import android.hardware.fingerprint.IFingerprintServiceReceiver; +import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.testing.TestableContext; @@ -44,10 +55,13 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -64,6 +78,8 @@ public class FingerprintServiceTest { private static final int ID_VIRTUAL = 6; private static final String NAME_DEFAULT = "default"; private static final String NAME_VIRTUAL = "virtual"; + private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS = + List.of(); @Rule public final MockitoRule mMockito = MockitoJUnit.rule(); @@ -74,47 +90,80 @@ public class FingerprintServiceTest { public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule(); @Mock + private AppOpsManager mAppOpsManager; + @Mock private BiometricContext mBiometricContext; @Mock private IBiometricService mIBiometricService; @Mock - private IFingerprint mIFingerprintDefault; + private FingerprintProvider mFingerprintDefault; @Mock - private IFingerprint mIFingerprintVirtual; - - private final SensorProps mSensorPropsDefault = createProps(ID_DEFAULT, - SensorStrength.STRONG, FingerprintSensorType.POWER_BUTTON); - private final SensorProps mSensorPropsVirtual = createProps(ID_VIRTUAL, - SensorStrength.STRONG, FingerprintSensorType.UNDER_DISPLAY_OPTICAL); + private FingerprintProvider mFingerprintVirtual; + @Mock + private IFingerprintServiceReceiver mServiceReceiver; + @Mock + private IBinder mToken; + + @Captor + private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor; + + private final FingerprintSensorPropertiesInternal mSensorPropsDefault = + new FingerprintSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG, + 2 /* maxEnrollmentsPerUser */, + List.of(), + TYPE_REAR, + false /* resetLockoutRequiresHardwareAuthToken */); + private final FingerprintSensorPropertiesInternal mSensorPropsVirtual = + new FingerprintSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG, + 2 /* maxEnrollmentsPerUser */, + List.of(), + TYPE_UDFPS_OPTICAL, + false /* resetLockoutRequiresHardwareAuthToken */); private FingerprintService mService; @Before public void setup() throws Exception { - when(mIFingerprintDefault.getSensorProps()).thenReturn( - new SensorProps[]{mSensorPropsDefault}); - when(mIFingerprintVirtual.getSensorProps()).thenReturn( - new SensorProps[]{mSensorPropsVirtual}); - - mContext.getTestablePermissions().setPermission( - USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED); + when(mFingerprintDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault)); + when(mFingerprintVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual)); + when(mFingerprintDefault.containsSensor(anyInt())) + .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT)); + when(mFingerprintVirtual.containsSensor(anyInt())) + .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL)); + + mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager); + for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) { + when(mAppOpsManager.noteOp(eq(permission), anyInt(), any(), any(), any())) + .thenReturn(AppOpsManager.MODE_ALLOWED); + } + + for (String permission : List.of(USE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) { + mContext.getTestablePermissions().setPermission( + permission, PackageManager.PERMISSION_GRANTED); + } } private void initServiceWith(String... aidlInstances) { mService = new FingerprintService(mContext, mBiometricContext, () -> mIBiometricService, () -> aidlInstances, - (fqName) -> { - if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault; - if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual; + (name) -> { + if (NAME_DEFAULT.equals(name)) return mFingerprintDefault; + if (NAME_VIRTUAL.equals(name)) return mFingerprintVirtual; return null; }); } + private void initServiceWithAndWait(String... aidlInstances) throws Exception { + initServiceWith(aidlInstances); + mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS); + waitForRegistration(); + } + @Test public void registerAuthenticators_defaultOnly() throws Exception { initServiceWith(NAME_DEFAULT, NAME_VIRTUAL); - mService.mServiceWrapper.registerAuthenticators(List.of()); + mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS); waitForRegistration(); verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any()); @@ -126,7 +175,7 @@ public class FingerprintServiceTest { Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext), Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1); - mService.mServiceWrapper.registerAuthenticators(List.of()); + mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS); waitForRegistration(); verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any()); @@ -136,7 +185,7 @@ public class FingerprintServiceTest { public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception { initServiceWith(NAME_VIRTUAL); - mService.mServiceWrapper.registerAuthenticators(List.of()); + mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS); waitForRegistration(); verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any()); @@ -155,13 +204,47 @@ public class FingerprintServiceTest { latch.await(5, TimeUnit.SECONDS); } - private static SensorProps createProps(int id, byte strength, byte type) { - final SensorProps props = new SensorProps(); - props.commonProps = new CommonProps(); - props.commonProps.sensorId = id; - props.commonProps.sensorStrength = strength; - props.sensorType = type; - props.sensorLocations = new SensorLocation[]{new SensorLocation()}; - return props; + @Test + public void authenticateWithDefaultSensorId() throws Exception { + initServiceWithAndWait(NAME_DEFAULT, NAME_VIRTUAL); + + final long operationId = 2; + mService.mServiceWrapper.authenticate(mToken, operationId, mServiceReceiver, + new FingerprintAuthenticateOptions.Builder() + .setSensorId(SENSOR_ID_ANY) + .build()); + + final FingerprintAuthenticateOptions options = + verifyAuthenticateWithNewRequestId(mFingerprintDefault, operationId); + assertThat(options.getSensorId()).isEqualTo(ID_DEFAULT); + verifyNoAuthenticate(mFingerprintVirtual); + } + + + private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId( + FingerprintProvider provider, long operationId) { + return verifyAuthenticateWithNewRequestId( + provider, operationId, true /* shouldSchedule */); + } + + private void verifyNoAuthenticate(FingerprintProvider provider) { + verifyAuthenticateWithNewRequestId( + provider, 0 /* operationId */, false /* shouldSchedule */); + } + + private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId( + FingerprintProvider provider, long operationId, boolean shouldSchedule) { + verify(provider, shouldSchedule ? times(1) : never()) + .scheduleAuthenticate(eq(mToken), eq(operationId), anyInt(), any(), + mAuthenticateOptionsCaptor.capture(), anyBoolean(), anyInt(), + anyBoolean()); + verify(provider, never()).scheduleAuthenticate(eq(mToken), anyLong(), + anyInt(), any(), mAuthenticateOptionsCaptor.capture(), anyLong(), + anyBoolean(), anyInt(), anyBoolean()); + + if (shouldSchedule) { + return mAuthenticateOptionsCaptor.getValue(); + } + return null; } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 99f7905a9f70..f0f975ccf5ff 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -414,11 +415,16 @@ public class FingerprintAuthenticationClientTest { when(mHal.getInterfaceVersion()).thenReturn(version); final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder() + .setOpPackageName("test-owner") + .setUserId(5) + .setSensorId(9) + .build(); return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken, - REQUEST_ID, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID, - false /* restricted */, "test-owner", 4 /* cookie */, + REQUEST_ID, mClientMonitorCallbackConverter, OP_ID, + false /* restricted */, options, 4 /* cookie */, false /* requireConfirmation */, - 9 /* sensorId */, mBiometricLogger, mBiometricContext, + mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, null /* taskStackListener */, null /* lockoutCache */, mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java index 2dbd8f69d694..e741e446da85 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; @@ -117,8 +118,13 @@ public class FingerprintDetectClientTest { final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); return new FingerprintDetectClient(mContext, () -> aidl, mToken, - 6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */, - "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext, + 6 /* requestId */, mClientMonitorCallbackConverter, + new FingerprintAuthenticateOptions.Builder() + .setUserId(2) + .setSensorId(1) + .setOpPackageName("a-test") + .build(), + mBiometricLogger, mBiometricContext, mUdfpsOverlayController, null, true /* isStrongBiometric */); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java index f0d8616ece9f..580644347c84 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java @@ -164,10 +164,9 @@ public class FingerprintInternalCleanupClientTest { } protected FingerprintInternalCleanupClient createClient() { - final List<Fingerprint> enrollments = new ArrayList<>(); final Map<Integer, Long> authenticatorIds = new HashMap<>(); return new FingerprintInternalCleanupClient(mContext, () -> mAidlSession, 2 /* userId */, - "the.test.owner", SENSOR_ID, mLogger, mBiometricContext, enrollments, + "the.test.owner", SENSOR_ID, mLogger, mBiometricContext, mFingerprintUtils, authenticatorIds) { @Override protected void onAddUnknownTemplate(int userId, diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java index 6431e88b1acb..1259d7189a6d 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java @@ -81,7 +81,7 @@ public class SensorControllerTest { @Test public void createSensor_invalidHandle_throwsException() { doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor( - anyInt(), anyInt(), anyString(), anyString(), any()); + anyInt(), anyInt(), anyString(), anyString(), anyInt(), any()); Throwable thrown = assertThrows( RuntimeException.class, @@ -138,7 +138,7 @@ public class SensorControllerTest { private void doCreateSensorSuccessfully() { doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor( - anyInt(), anyInt(), anyString(), anyString(), any()); + anyInt(), anyInt(), anyString(), anyString(), anyInt(), any()); assertThat(mSensorController.createSensor(mSensorToken, mVirtualSensorConfig)) .isEqualTo(SENSOR_HANDLE); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 0cd50f03fbfa..8f4c81f26408 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -16,11 +16,11 @@ package com.android.server.companion.virtual; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS; +import static android.content.Context.DEVICE_ID_DEFAULT; +import static android.content.Context.DEVICE_ID_INVALID; import static android.content.Intent.ACTION_VIEW; import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES; @@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -67,7 +68,11 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.hardware.Sensor; +import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.IDisplayManager; +import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; import android.hardware.input.IInputManager; import android.hardware.input.VirtualDpadConfig; import android.hardware.input.VirtualKeyEvent; @@ -144,6 +149,7 @@ public class VirtualDeviceManagerServiceTest { private static final String DEVICE_NAME_3 = "device name 3"; private static final int DISPLAY_ID_1 = 2; private static final int DISPLAY_ID_2 = 3; + private static final int NON_EXISTENT_DISPLAY_ID = 42; private static final int DEVICE_OWNER_UID_1 = 50; private static final int DEVICE_OWNER_UID_2 = 51; private static final int UID_1 = 0; @@ -162,6 +168,8 @@ public class VirtualDeviceManagerServiceTest { private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000; private static final int VIRTUAL_DEVICE_ID_1 = 42; private static final int VIRTUAL_DEVICE_ID_2 = 43; + private static final VirtualDisplayConfig VIRTUAL_DISPLAY_CONFIG = + new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400).build(); private static final VirtualDpadConfig DPAD_CONFIG = new VirtualDpadConfig.Builder() .setVendorId(VENDOR_ID) @@ -221,6 +229,8 @@ public class VirtualDeviceManagerServiceTest { @Mock private DisplayManagerInternal mDisplayManagerInternalMock; @Mock + private IDisplayManager mIDisplayManager; + @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; @Mock private DevicePolicyManager mDevicePolicyManagerMock; @@ -237,6 +247,8 @@ public class VirtualDeviceManagerServiceTest { @Mock private IVirtualDeviceSoundEffectListener mSoundEffectListener; @Mock + private IVirtualDisplayCallback mVirtualDisplayCallback; + @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; @Mock private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener; @@ -271,9 +283,13 @@ public class VirtualDeviceManagerServiceTest { private Intent createRestrictedActivityBlockedIntent(List displayCategories, String targetDisplayCategory) { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(), + eq(NONBLOCKED_APP_PACKAGE_NAME))).thenReturn(DISPLAY_ID_1); + VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480, + 420).setDisplayCategories(displayCategories).build(); + mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback, + NONBLOCKED_APP_PACKAGE_NAME); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); doNothing().when(mContext).startActivityAsUser(any(), any(), any()); @@ -327,6 +343,7 @@ public class VirtualDeviceManagerServiceTest { mContext = Mockito.spy(new ContextWrapper( InstrumentationRegistry.getInstrumentation().getTargetContext())); doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt()); + doNothing().when(mContext).sendBroadcastAsUser(any(), any()); when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( mDevicePolicyManagerMock); @@ -369,15 +386,13 @@ public class VirtualDeviceManagerServiceTest { @Test public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() { - mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID_1); - - assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1)) + assertThat(mVdm.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID)) .isEqualTo(DEVICE_ID_DEFAULT); } @Test public void getDeviceIdForDisplayId_withValidVirtualDisplayId_returnsDeviceId() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1)) .isEqualTo(mDeviceImpl.getDeviceId()); @@ -485,7 +500,7 @@ public class VirtualDeviceManagerServiceTest { .build(); doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor( - anyInt(), anyInt(), anyString(), anyString(), any()); + anyInt(), anyInt(), anyString(), anyString(), anyInt(), any()); mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params); VirtualSensor sensor = mLocalService.getVirtualSensor(VIRTUAL_DEVICE_ID_1, SENSOR_HANDLE); @@ -503,10 +518,9 @@ public class VirtualDeviceManagerServiceTest { @Test public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() { - GenericWindowPolicyController gwpc = - mDeviceImpl.createWindowPolicyController(new ArrayList<>()); - mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1); - gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2)); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged( + Sets.newArraySet(UID_2)); Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); assertThat(deviceIds).isEmpty(); @@ -514,10 +528,9 @@ public class VirtualDeviceManagerServiceTest { @Test public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() { - GenericWindowPolicyController gwpc = - mDeviceImpl.createWindowPolicyController(new ArrayList<>()); - mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1); - gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1)); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged( + Sets.newArraySet(UID_1)); Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId()); @@ -525,10 +538,10 @@ public class VirtualDeviceManagerServiceTest { @Test public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() { - GenericWindowPolicyController gwpc = - mDeviceImpl.createWindowPolicyController(new ArrayList<>()); - mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1); - gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2)); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + + mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged( + Sets.newArraySet(UID_1, UID_2)); Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId()); @@ -538,11 +551,10 @@ public class VirtualDeviceManagerServiceTest { public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() { VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2, DEVICE_OWNER_UID_2); + addVirtualDisplay(secondDevice, DISPLAY_ID_2); - GenericWindowPolicyController gwpc = - secondDevice.createWindowPolicyController(new ArrayList<>()); - secondDevice.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_2); - gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1)); + secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged( + Sets.newArraySet(UID_1)); Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); assertThat(deviceIds).containsExactly(secondDevice.getDeviceId()); @@ -550,16 +562,16 @@ public class VirtualDeviceManagerServiceTest { @Test public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() { + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2, DEVICE_OWNER_UID_2); - GenericWindowPolicyController gwpc1 = - mDeviceImpl.createWindowPolicyController(new ArrayList<>()); - GenericWindowPolicyController gwpc2 = - secondDevice.createWindowPolicyController(new ArrayList<>()); - mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID_1); - secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2); - gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1)); - gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2)); + addVirtualDisplay(secondDevice, DISPLAY_ID_2); + + + mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged( + Sets.newArraySet(UID_1)); + secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged( + Sets.newArraySet(UID_1, UID_2)); Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1); assertThat(deviceIds).containsExactly( @@ -568,8 +580,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); - + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER); mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1)); @@ -609,8 +620,8 @@ public class VirtualDeviceManagerServiceTest { .setLanguageTag("fr-FR") .build(); - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); - secondDevice.mVirtualDisplayIds.add(DISPLAY_ID_2); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(secondDevice, DISPLAY_ID_2); mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER); secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder); @@ -640,10 +651,9 @@ public class VirtualDeviceManagerServiceTest { @Test public void onVirtualDisplayRemovedLocked_doesNotThrowException() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); // This call should not throw any exceptions. - mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1); + mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1); } @Test @@ -659,8 +669,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void onVirtualDisplayRemovedLocked_listenersNotified() { mLocalService.registerVirtualDisplayListener(mDisplayListener); - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); + + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1); TestableLooper.get(this).processAllMessages(); @@ -723,8 +733,7 @@ public class VirtualDeviceManagerServiceTest { verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), nullable(String.class), anyInt(), eq(null)); - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), nullable(String.class), eq(DISPLAY_ID_1), eq(null)); @@ -733,12 +742,9 @@ public class VirtualDeviceManagerServiceTest { @Test public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired() throws RemoteException { - GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController( - new ArrayList<>()); - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); assertThrows(IllegalStateException.class, - () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1)); + () -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1)); TestableLooper.get(this).processAllMessages(); verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), @@ -749,13 +755,12 @@ public class VirtualDeviceManagerServiceTest { public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() { final int unknownDisplayId = 999; assertThrows(IllegalStateException.class, - () -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId)); + () -> mDeviceImpl.onVirtualDisplayRemoved(unknownDisplayId)); } @Test public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class); TestableLooper.get(this).processAllMessages(); verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(), @@ -764,14 +769,13 @@ public class VirtualDeviceManagerServiceTest { nullable(String.class), eq(DISPLAY_ID_1), eq(null)); IBinder wakeLock = wakeLockCaptor.getValue(); - mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1); + mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1); verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt()); } @Test public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class); TestableLooper.get(this).processAllMessages(); verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(), @@ -825,7 +829,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualTouchscreen_positiveDisplayDimension_successful() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); VirtualTouchscreenConfig positiveConfig = new VirtualTouchscreenConfig.Builder( /* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800) @@ -863,7 +867,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); VirtualNavigationTouchpadConfig positiveConfig = new VirtualNavigationTouchpadConfig.Builder( /* touchpadHeight= */ 50, /* touchpadWidth= */ 50) @@ -888,7 +892,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualDpad_noPermission_failsSecurityException() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) { assertThrows(SecurityException.class, () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER)); @@ -897,7 +901,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualKeyboard_noPermission_failsSecurityException() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) { assertThrows(SecurityException.class, () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER)); @@ -906,7 +910,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualMouse_noPermission_failsSecurityException() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) { assertThrows(SecurityException.class, () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER)); @@ -915,7 +919,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualTouchscreen_noPermission_failsSecurityException() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) { assertThrows(SecurityException.class, () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER)); @@ -924,7 +928,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) { assertThrows(SecurityException.class, () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, @@ -934,7 +938,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void onAudioSessionStarting_noPermission_failsSecurityException() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) { assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionStarting( @@ -951,7 +955,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualDpad_hasDisplay_obtainFileDescriptor() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER); assertWithMessage("Virtual dpad should register fd when the display matches").that( mInputController.getInputDeviceDescriptors()).isNotEmpty(); @@ -961,7 +965,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches").that( mInputController.getInputDeviceDescriptors()).isNotEmpty(); @@ -971,7 +975,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualKeyboard_keyboardCreated_localeUpdated() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") .that(mInputController.getInputDeviceDescriptors()) @@ -992,7 +996,7 @@ public class VirtualDeviceManagerServiceTest { .setAssociatedDisplayId(DISPLAY_ID_1) .build(); - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") .that(mInputController.getInputDeviceDescriptors()) @@ -1005,7 +1009,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void virtualDeviceWithoutKeyboard_noLocaleUpdate() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); // no preceding call to createVirtualKeyboard() assertThat(mDeviceImpl.getDeviceLocaleList()).isNull(); @@ -1013,7 +1017,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualMouse_hasDisplay_obtainFileDescriptor() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER); assertWithMessage("Virtual mouse should register fd when the display matches").that( mInputController.getInputDeviceDescriptors()).isNotEmpty(); @@ -1023,7 +1027,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER); assertWithMessage("Virtual touchscreen should register fd when the display matches").that( mInputController.getInputDeviceDescriptors()).isNotEmpty(); @@ -1033,7 +1037,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER); assertWithMessage("Virtual navigation touchpad should register fd when the display matches") .that( @@ -1055,8 +1059,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void onAudioSessionStarting_hasVirtualAudioController() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback); @@ -1065,8 +1068,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void onAudioSessionEnded_noVirtualAudioController() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback); mDeviceImpl.onAudioSessionEnded(); @@ -1076,8 +1078,7 @@ public class VirtualDeviceManagerServiceTest { @Test public void close_cleanVirtualAudioController() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback); mDeviceImpl.close(); @@ -1321,9 +1322,9 @@ public class VirtualDeviceManagerServiceTest { @Test public void setShowPointerIcon_setsValueForAllDisplays() { - mDeviceImpl.mVirtualDisplayIds.add(1); - mDeviceImpl.mVirtualDisplayIds.add(2); - mDeviceImpl.mVirtualDisplayIds.add(3); + addVirtualDisplay(mDeviceImpl, 1); + addVirtualDisplay(mDeviceImpl, 2); + addVirtualDisplay(mDeviceImpl, 3); VirtualMouseConfig config1 = new VirtualMouseConfig.Builder() .setAssociatedDisplayId(1) .setInputDeviceName(DEVICE_NAME_1) @@ -1346,7 +1347,9 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualMouse(config1, BINDER); mDeviceImpl.createVirtualMouse(config2, BINDER); mDeviceImpl.createVirtualMouse(config3, BINDER); + clearInvocations(mInputManagerInternalMock); mDeviceImpl.setShowPointerIcon(false); + verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt()); verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt()); mDeviceImpl.setShowPointerIcon(true); @@ -1355,9 +1358,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); doNothing().when(mContext).startActivityAsUser(any(), any(), any()); @@ -1376,9 +1378,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); doNothing().when(mContext).startActivityAsUser(any(), any(), any()); @@ -1397,9 +1398,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); doNothing().when(mContext).startActivityAsUser(any(), any(), any()); @@ -1418,9 +1418,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void openVendingOnVirtualDisplay_startBlockedAlertActivity() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); doNothing().when(mContext).startActivityAsUser(any(), any(), any()); @@ -1439,9 +1438,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); doNothing().when(mContext).startActivityAsUser(any(), any(), any()); @@ -1460,9 +1458,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() { - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); doNothing().when(mContext).startActivityAsUser(any(), any(), any()); @@ -1482,9 +1479,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() { ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2)); - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); gwpc.onRunningAppsChanged(uids); @@ -1497,11 +1493,10 @@ public class VirtualDeviceManagerServiceTest { @Test public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() { ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2)); - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); - mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1); + gwpc.unregisterRunningAppsChangedListener(mDeviceImpl); // This call should not throw any exceptions. gwpc.onRunningAppsChanged(uids); @@ -1512,9 +1507,8 @@ public class VirtualDeviceManagerServiceTest { @Test public void canActivityBeLaunched_activityCanLaunch() { Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE)); - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); ArrayList<ActivityInfo> activityInfos = getActivityInfoList( NONBLOCKED_APP_PACKAGE_NAME, @@ -1537,9 +1531,8 @@ public class VirtualDeviceManagerServiceTest { doReturn(interceptor).when(interceptor).asBinder(); doReturn(interceptor).when(interceptor).queryLocalInterface(anyString()); - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); ArrayList<ActivityInfo> activityInfos = getActivityInfoList( NONBLOCKED_APP_PACKAGE_NAME, @@ -1581,9 +1574,8 @@ public class VirtualDeviceManagerServiceTest { doReturn(interceptor).when(interceptor).asBinder(); doReturn(interceptor).when(interceptor).queryLocalInterface(anyString()); - mDeviceImpl.onVirtualDisplayCreatedLocked( - mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1); - GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get( + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( DISPLAY_ID_1); ArrayList<ActivityInfo> activityInfos = getActivityInfoList( NONBLOCKED_APP_PACKAGE_NAME, @@ -1626,8 +1618,7 @@ public class VirtualDeviceManagerServiceTest { } @Test - public void - restrictedActivityOnNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() { + public void restrictedActivityNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() { Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "def"); verify(mContext).startActivityAsUser(argThat(intent -> intent.filterEquals(blockedAppIntent)), any(), any()); @@ -1654,15 +1645,15 @@ public class VirtualDeviceManagerServiceTest { @Test public void getDisplayIdsForDevice_oneDisplay_resultContainsCorrectDisplayId() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1); assertThat(displayIds).containsExactly(DISPLAY_ID_1); } @Test public void getDisplayIdsForDevice_twoDisplays_resultContainsCorrectDisplayIds() { - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1); - mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_2); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_2); ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1); assertThat(displayIds).containsExactly(DISPLAY_ID_1, DISPLAY_ID_2); } @@ -1677,15 +1668,22 @@ public class VirtualDeviceManagerServiceTest { private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid, VirtualDeviceParams params) { VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext, - mAssociationInfo, new Binder(), ownerUid, virtualDeviceId, - mInputController, mSensorController, mCameraAccessController, - /* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId), + mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId, + mInputController, mSensorController, mCameraAccessController + /* onDeviceCloseListener= */ /*deviceId -> mVdms.removeVirtualDevice(deviceId)*/, mPendingTrampolineCallback, mActivityListener, mSoundEffectListener, - mRunningAppsChangedCallback, params); + mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager)); mVdms.addVirtualDevice(virtualDeviceImpl); return virtualDeviceImpl; } + private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) { + when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback), + eq(virtualDevice), any(), any())).thenReturn(displayId); + virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback, + NONBLOCKED_APP_PACKAGE_NAME); + } + /** Helper class to drop permissions temporarily and restore them at the end of a test. */ static final class DropShellPermissionsTemporarily implements AutoCloseable { DropShellPermissionsTemporarily() { diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java index bb28a3689f68..a2e204d89424 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java @@ -16,8 +16,8 @@ package com.android.server.companion.virtual; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT; -import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID; +import static android.content.Context.DEVICE_ID_DEFAULT; +import static android.content.Context.DEVICE_ID_INVALID; import static com.google.common.truth.Truth.assertThat; diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java index 800f60bec828..ffe2fec380a8 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java @@ -43,6 +43,7 @@ import com.android.internal.os.BackgroundThread; import com.android.server.display.BrightnessThrottler.Injector; import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.mode.DisplayModeDirectorTest; import org.junit.Before; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index 6def7b1c8c35..8981160d1f25 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -176,21 +176,18 @@ public class BrightnessTrackerTest { assertFalse(mInjector.mColorSamplingEnabled); // Update brightness config to enabled color sampling. - mTracker.setBrightnessConfiguration(buildBrightnessConfiguration( - /* collectColorSamples= */ true)); + mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true); mInjector.waitForHandler(); assertTrue(mInjector.mColorSamplingEnabled); // Update brightness config to disable color sampling. - mTracker.setBrightnessConfiguration(buildBrightnessConfiguration( - /* collectColorSamples= */ false)); + mTracker.setShouldCollectColorSample(/* collectColorSamples= */ false); mInjector.waitForHandler(); assertFalse(mInjector.mColorSamplingEnabled); // Pretend screen is off, update config to turn on color sampling. mInjector.sendScreenChange(/* screenOn= */ false); - mTracker.setBrightnessConfiguration(buildBrightnessConfiguration( - /* collectColorSamples= */ true)); + mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true); mInjector.waitForHandler(); assertFalse(mInjector.mColorSamplingEnabled); @@ -883,7 +880,7 @@ public class BrightnessTrackerTest { private void startTracker(BrightnessTracker tracker, float initialBrightness, boolean collectColorSamples) { tracker.start(initialBrightness); - tracker.setBrightnessConfiguration(buildBrightnessConfiguration(collectColorSamples)); + tracker.setShouldCollectColorSample(collectColorSamples); mInjector.waitForHandler(); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 7e6eeee9e713..f256c8a17c56 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.display; +package com.android.server.display.mode; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS; @@ -27,8 +27,7 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_R import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE; -import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE; -import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID; +import static com.android.server.display.mode.DisplayModeDirector.Vote.INVALID_SIZE; import static com.google.common.truth.Truth.assertThat; @@ -88,9 +87,11 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.LocalServices; -import com.android.server.display.DisplayModeDirector.BrightnessObserver; -import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs; -import com.android.server.display.DisplayModeDirector.Vote; +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.TestUtils; +import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver; +import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs; +import com.android.server.display.mode.DisplayModeDirector.Vote; import com.android.server.sensors.SensorManagerInternal; import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import com.android.server.statusbar.StatusBarManagerInternal; @@ -126,6 +127,8 @@ public class DisplayModeDirectorTest { private static final int DISPLAY_ID = 0; private static final float TRANSITION_POINT = 0.763f; + private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY; + private Context mContext; private FakesInjector mInjector; private Handler mHandler; @@ -2234,7 +2237,7 @@ public class DisplayModeDirectorTest { ArgumentCaptor.forClass(IThermalEventListener.class); verify(mThermalServiceMock).registerThermalEventListenerWithType( - thermalEventListener.capture(), eq(Temperature.TYPE_SKIN)); + thermalEventListener.capture(), eq(Temperature.TYPE_SKIN)); final IThermalEventListener listener = thermalEventListener.getValue(); // Verify that there is no skin temperature vote initially. @@ -2548,7 +2551,7 @@ public class DisplayModeDirectorTest { KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps)); } - void setBrightnessThrottlingData(String brightnessThrottlingData) { + public void setBrightnessThrottlingData(String brightnessThrottlingData) { putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData); } diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java index 23d708227e4f..be1375310704 100644 --- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java +++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java @@ -16,6 +16,9 @@ package com.android.server.job; +import static android.app.job.JobInfo.NETWORK_TYPE_ANY; +import static android.app.job.JobInfo.NETWORK_TYPE_NONE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -525,7 +528,9 @@ public class PendingJobQueueTest { final boolean ui = random.nextBoolean(); final boolean ej = !ui && random.nextBoolean(); JobStatus job = createJobStatus("testPendingJobSorting_Random", - createJobInfo(i).setExpedited(ej).setUserInitiated(ui), random.nextInt(250)); + createJobInfo(i).setExpedited(ej).setUserInitiated(ui) + .setRequiredNetworkType(ui ? NETWORK_TYPE_ANY : NETWORK_TYPE_NONE), + random.nextInt(250)); job.enqueueTime = random.nextInt(1_000_000); jobQueue.add(job); } @@ -562,7 +567,9 @@ public class PendingJobQueueTest { final boolean ui = random.nextBoolean(); final boolean ej = !ui && random.nextBoolean(); JobStatus job = createJobStatus("testPendingJobSortingTransitivity", - createJobInfo(i).setExpedited(ej).setUserInitiated(ui), random.nextInt(50)); + createJobInfo(i).setExpedited(ej).setUserInitiated(ui) + .setRequiredNetworkType(ui ? NETWORK_TYPE_ANY : NETWORK_TYPE_NONE), + random.nextInt(50)); job.enqueueTime = random.nextInt(1_000_000); job.overrideState = random.nextInt(4); jobQueue.add(job); @@ -586,7 +593,8 @@ public class PendingJobQueueTest { final boolean ui = random.nextFloat() < .02; final boolean ej = !ui && random.nextFloat() < .03; JobStatus job = createJobStatus("testPendingJobSortingTransitivity_Concentrated", - createJobInfo(i).setExpedited(ej).setUserInitiated(ui), + createJobInfo(i).setExpedited(ej).setUserInitiated(ui) + .setRequiredNetworkType(ui ? NETWORK_TYPE_ANY : NETWORK_TYPE_NONE), random.nextInt(20)); job.enqueueTime = random.nextInt(250); job.overrideState = random.nextFloat() < .01 diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index 9686c385e91e..1c33d0de4568 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -165,10 +165,11 @@ public class LockSettingsServiceTestable extends LockSettingsService { } @Override - protected void tieProfileLockToParent(int userId, LockscreenCredential password) { + protected void tieProfileLockToParent(int profileUserId, int parentUserId, + LockscreenCredential password) { Parcel parcel = Parcel.obtain(); parcel.writeParcelable(password, 0); - mStorage.writeChildProfileLock(userId, parcel.marshall()); + mStorage.writeChildProfileLock(profileUserId, parcel.marshall()); parcel.recycle(); } diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java new file mode 100644 index 000000000000..24ed42cab63a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import android.annotation.IdRes; +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioManager; +import android.media.AudioRoutesInfo; +import android.media.IAudioRoutesObserver; +import android.media.MediaRoute2Info; +import android.os.RemoteException; +import android.text.TextUtils; + +import com.android.internal.R; +import com.android.server.audio.AudioService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.junit.runners.Parameterized; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.Collection; + +@RunWith(Enclosed.class) +public class DeviceRouteControllerTest { + + private static final String DEFAULT_ROUTE_NAME = "default_route"; + private static final String DEFAULT_HEADPHONES_NAME = "headphone"; + private static final String DEFAULT_HEADSET_NAME = "headset"; + private static final String DEFAULT_DOCK_NAME = "dock"; + private static final String DEFAULT_HDMI_NAME = "hdmi"; + private static final String DEFAULT_USB_NAME = "usb"; + private static final int VOLUME_DEFAULT_VALUE = 0; + private static final int VOLUME_VALUE_SAMPLE_1 = 10; + + private static AudioRoutesInfo createFakeBluetoothAudioRoute() { + AudioRoutesInfo btRouteInfo = new AudioRoutesInfo(); + btRouteInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER; + btRouteInfo.bluetoothName = "bt_device"; + return btRouteInfo; + } + + @RunWith(JUnit4.class) + public static class DefaultDeviceRouteValueTest { + @Mock + private Context mContext; + @Mock + private Resources mResources; + @Mock + private AudioManager mAudioManager; + @Mock + private AudioService mAudioService; + @Mock + private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mContext.getResources()).thenReturn(mResources); + } + + @Test + public void initialize_noRoutesInfo_defaultRouteIsNotNull() { + // Mocking default_audio_route_name. + when(mResources.getText(R.string.default_audio_route_name)) + .thenReturn(DEFAULT_ROUTE_NAME); + + // Default route should be initialized even when AudioService returns null. + when(mAudioService.startWatchingRoutes(any())).thenReturn(null); + + DeviceRouteController deviceRouteController = new DeviceRouteController( + mContext, + mAudioManager, + mAudioService, + mOnDeviceRouteChangedListener + ); + + MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute(); + + assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); + assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME)) + .isTrue(); + assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE); + } + + @Test + public void initialize_bluetoothRouteAvailable_deviceRouteReturnsDefaultRoute() { + // Mocking default_audio_route_name. + when(mResources.getText(R.string.default_audio_route_name)) + .thenReturn(DEFAULT_ROUTE_NAME); + + // This route should be ignored. + AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute(); + when(mAudioService.startWatchingRoutes(any())).thenReturn(fakeBluetoothAudioRoute); + + DeviceRouteController deviceRouteController = new DeviceRouteController( + mContext, + mAudioManager, + mAudioService, + mOnDeviceRouteChangedListener + ); + + MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute(); + + assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); + assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME)) + .isTrue(); + assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE); + } + } + + @RunWith(Parameterized.class) + public static class DeviceRouteInitializationTest { + + @Parameterized.Parameters + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { + { /* expected res */ + com.android.internal.R.string.default_audio_route_name_headphones, + /* expected name */ + DEFAULT_HEADPHONES_NAME, + /* expected type */ + MediaRoute2Info.TYPE_WIRED_HEADPHONES, + /* actual audio route type */ + AudioRoutesInfo.MAIN_HEADPHONES }, + { /* expected res */ + com.android.internal.R.string.default_audio_route_name_headphones, + /* expected name */ + DEFAULT_HEADSET_NAME, + /* expected type */ + MediaRoute2Info.TYPE_WIRED_HEADSET, + /* actual audio route type */ + AudioRoutesInfo.MAIN_HEADSET }, + { /* expected res */ + R.string.default_audio_route_name_dock_speakers, + /* expected name */ + DEFAULT_DOCK_NAME, + /* expected type */ + MediaRoute2Info.TYPE_DOCK, + /* actual audio route type */ + AudioRoutesInfo.MAIN_DOCK_SPEAKERS }, + { /* expected res */ + R.string.default_audio_route_name_external_device, + /* expected name */ + DEFAULT_HDMI_NAME, + /* expected type */ + MediaRoute2Info.TYPE_HDMI, + /* actual audio route type */ + AudioRoutesInfo.MAIN_HDMI }, + { /* expected res */ + R.string.default_audio_route_name_usb, + /* expected name */ + DEFAULT_USB_NAME, + /* expected type */ + MediaRoute2Info.TYPE_USB_DEVICE, + /* actual audio route type */ + AudioRoutesInfo.MAIN_USB } + }); + } + + @Mock + private Context mContext; + @Mock + private Resources mResources; + @Mock + private AudioManager mAudioManager; + @Mock + private AudioService mAudioService; + @Mock + private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; + + @IdRes + private final int mExpectedRouteNameResource; + private final String mExpectedRouteNameValue; + private final int mExpectedRouteType; + private final int mActualAudioRouteType; + + public DeviceRouteInitializationTest(int expectedRouteNameResource, + String expectedRouteNameValue, + int expectedMediaRouteType, + int actualAudioRouteType) { + this.mExpectedRouteNameResource = expectedRouteNameResource; + this.mExpectedRouteNameValue = expectedRouteNameValue; + this.mExpectedRouteType = expectedMediaRouteType; + this.mActualAudioRouteType = actualAudioRouteType; + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mContext.getResources()).thenReturn(mResources); + } + + @Test + public void initialize_wiredRouteAvailable_deviceRouteReturnsWiredRoute() { + // Mocking default_audio_route_name. + when(mResources.getText(R.string.default_audio_route_name)) + .thenReturn(DEFAULT_ROUTE_NAME); + + // At first, WiredRouteController should initialize device + // route based on AudioService response. + AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); + audioRoutesInfo.mainType = mActualAudioRouteType; + when(mAudioService.startWatchingRoutes(any())).thenReturn(audioRoutesInfo); + + when(mResources.getText(mExpectedRouteNameResource)) + .thenReturn(mExpectedRouteNameValue); + + DeviceRouteController deviceRouteController = new DeviceRouteController( + mContext, + mAudioManager, + mAudioService, + mOnDeviceRouteChangedListener + ); + + MediaRoute2Info actualMediaRoute = deviceRouteController.getDeviceRoute(); + + assertThat(actualMediaRoute.getType()).isEqualTo(mExpectedRouteType); + assertThat(TextUtils.equals(actualMediaRoute.getName(), mExpectedRouteNameValue)) + .isTrue(); + // Volume did not change, so it should be set to default value (0). + assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE); + } + } + + @RunWith(JUnit4.class) + public static class VolumeAndDeviceRoutesChangesTest { + @Mock + private Context mContext; + @Mock + private Resources mResources; + @Mock + private AudioManager mAudioManager; + @Mock + private AudioService mAudioService; + @Mock + private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; + + @Captor + private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor; + + private DeviceRouteController mDeviceRouteController; + private IAudioRoutesObserver.Stub mAudioRoutesObserver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mContext.getResources()).thenReturn(mResources); + + when(mResources.getText(R.string.default_audio_route_name)) + .thenReturn(DEFAULT_ROUTE_NAME); + + // Setting built-in speaker as default speaker. + AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); + audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER; + when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture())) + .thenReturn(audioRoutesInfo); + + mDeviceRouteController = new DeviceRouteController( + mContext, + mAudioManager, + mAudioService, + mOnDeviceRouteChangedListener + ); + + mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue(); + } + + @Test + public void newDeviceConnects_wiredDevice_deviceRouteReturnsWiredDevice() { + // Connecting wired headset + AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); + audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; + + when(mResources.getText( + com.android.internal.R.string.default_audio_route_name_headphones)) + .thenReturn(DEFAULT_HEADPHONES_NAME); + + // Simulating wired device being connected. + callAudioRoutesObserver(audioRoutesInfo); + + MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + + assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); + assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_HEADPHONES_NAME)) + .isTrue(); + assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE); + } + + @Test + public void newDeviceConnects_bluetoothDevice_deviceRouteReturnsBluetoothDevice() { + // Simulating bluetooth speaker being connected. + AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute(); + callAudioRoutesObserver(fakeBluetoothAudioRoute); + + MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + + assertThat(actualMediaRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); + assertThat(TextUtils.equals(actualMediaRoute.getName(), DEFAULT_ROUTE_NAME)) + .isTrue(); + assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE); + } + + @Test + public void updateVolume_differentValue_updatesDeviceRouteVolume() { + MediaRoute2Info actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_DEFAULT_VALUE); + + assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue(); + + actualMediaRoute = mDeviceRouteController.getDeviceRoute(); + assertThat(actualMediaRoute.getVolume()).isEqualTo(VOLUME_VALUE_SAMPLE_1); + } + + @Test + public void updateVolume_sameValue_returnsFalse() { + assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isTrue(); + assertThat(mDeviceRouteController.updateVolume(VOLUME_VALUE_SAMPLE_1)).isFalse(); + } + + /** + * Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)} + * from {@link AudioService}. This happens when there is a wired route change, + * like a wired headset being connected. + * + * @param audioRoutesInfo updated state of connected wired device + */ + private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) { + try { + // this is a captured observer implementation + // from WiredRoutesController's AudioService#startWatchingRoutes call + mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo); + } catch (RemoteException exception) { + // Should not happen since the object is mocked. + assertWithMessage("An unexpected RemoteException happened.").fail(); + } + } + } + +} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index b20c63ca504f..0a718e329498 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -118,10 +118,12 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -4005,6 +4007,156 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check all other fields } + public void testSaveCorruptAndLoadUser() throws Exception { + // First, create some shortcuts and save. + runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { + final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16); + final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.icon2)); + + final ShortcutInfo si1 = makeShortcut( + "s1", + "title1-1", + makeComponent(ShortcutActivity.class), + icon1, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class, + "key1", "val1", "nest", makeBundle("key", 123)), + /* weight */ 10); + + final ShortcutInfo si2 = makeShortcut( + "s2", + "title1-2", + /* activity */ null, + icon2, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), + /* weight */ 12); + + assertTrue(mManager.setDynamicShortcuts(list(si1, si2))); + + assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime()); + assertEquals(2, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> { + final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_16x64); + final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.icon2)); + + final ShortcutInfo si1 = makeShortcut( + "s1", + "title2-1", + makeComponent(ShortcutActivity.class), + icon1, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class, + "key1", "val1", "nest", makeBundle("key", 123)), + /* weight */ 10); + + final ShortcutInfo si2 = makeShortcut( + "s2", + "title2-2", + /* activity */ null, + icon2, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), + /* weight */ 12); + + assertTrue(mManager.setDynamicShortcuts(list(si1, si2))); + + assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime()); + assertEquals(2, mManager.getRemainingCallCount()); + }); + + mRunningUsers.put(USER_10, true); + + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64); + final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.icon2)); + + final ShortcutInfo si1 = makeShortcut( + "s1", + "title10-1-1", + makeComponent(ShortcutActivity.class), + icon1, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class, + "key1", "val1", "nest", makeBundle("key", 123)), + /* weight */ 10); + + final ShortcutInfo si2 = makeShortcut( + "s2", + "title10-1-2", + /* activity */ null, + icon2, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), + /* weight */ 12); + + assertTrue(mManager.setDynamicShortcuts(list(si1, si2))); + + assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime()); + assertEquals(2, mManager.getRemainingCallCount()); + }); + + // Save and corrupt the primary files. + mService.saveDirtyInfo(); + try (Writer os = new FileWriter(mService.getUserFile(UserHandle.USER_SYSTEM))) { + os.write("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<user locales=\"en\" last-app-scan-time2=\"14400000"); + } + try (Writer os = new FileWriter(mService.getUserFile(USER_10))) { + os.write("<?xml version='1.0' encoding='utf"); + } + + // Restore. + initService(); + + // Before the load, the map should be empty. + assertEquals(0, mService.getShortcutsForTest().size()); + + // this will pre-load the per-user info. + mService.handleUnlockUser(UserHandle.USER_SYSTEM); + + // Now it's loaded. + assertEquals(1, mService.getShortcutsForTest().size()); + + runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> { + assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon( + mManager.getDynamicShortcuts()))), "s1", "s2"); + assertEquals(2, mManager.getRemainingCallCount()); + + assertEquals("title1-1", getCallerShortcut("s1").getTitle()); + assertEquals("title1-2", getCallerShortcut("s2").getTitle()); + }); + runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> { + assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon( + mManager.getDynamicShortcuts()))), "s1", "s2"); + assertEquals(2, mManager.getRemainingCallCount()); + + assertEquals("title2-1", getCallerShortcut("s1").getTitle()); + assertEquals("title2-2", getCallerShortcut("s2").getTitle()); + }); + + // Start another user + mService.handleUnlockUser(USER_10); + + // Now the size is 2. + assertEquals(2, mService.getShortcutsForTest().size()); + + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon( + mManager.getDynamicShortcuts()))), "s1", "s2"); + assertEquals(2, mManager.getRemainingCallCount()); + + assertEquals("title10-1-1", getCallerShortcut("s1").getTitle()); + assertEquals("title10-1-2", getCallerShortcut("s2").getTitle()); + }); + + // Try stopping the user + mService.handleStopUser(USER_10); + + // Now it's unloaded. + assertEquals(1, mService.getShortcutsForTest().size()); + + // TODO Check all other fields + } + public void testCleanupPackage() { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index f8033557f0f0..d78ab8677800 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -231,6 +231,10 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { setExternalStatsSyncLocked(mExternalStatsSync); } + @Override + public void writeSyncLocked() { + } + public static class DummyExternalStatsSync implements ExternalStatsSync { public int flags = 0; diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 5a0867f8f584..daa682342836 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -386,16 +386,16 @@ public class TimeDetectorServiceTest { .when(mMockContext).enforceCallingPermission(anyString(), any()); assertThrows(SecurityException.class, - () -> mTimeDetectorService.clearNetworkTime()); + () -> mTimeDetectorService.clearLatestNetworkTime()); verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME), anyString()); } @Test - public void testClearNetworkTime() throws Exception { + public void testClearLatestNetworkSuggestion() throws Exception { doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); - mTimeDetectorService.clearNetworkTime(); + mTimeDetectorService.clearLatestNetworkTime(); verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.SET_TIME), anyString()); @@ -403,53 +403,48 @@ public class TimeDetectorServiceTest { } @Test - public void testLatestNetworkTime() { - NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult( - 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123)); - when(mMockNtpTrustedTime.getCachedTimeResult()) - .thenReturn(latestNetworkTime); - UnixEpochTime expected = new UnixEpochTime( - latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis()); - assertEquals(expected, mTimeDetectorService.latestNetworkTime()); + public void testGetLatestNetworkSuggestion() { + NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion(); + mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion); + + assertEquals(latestNetworkSuggestion, mTimeDetectorService.getLatestNetworkSuggestion()); } @Test - public void testLatestNetworkTime_noTimeAvailable() { - when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null); - assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime()); + public void testGetLatestNetworkSuggestion_noTimeAvailable() { + mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null); + + assertNull(mTimeDetectorService.getLatestNetworkSuggestion()); } @Test - public void testGetLatestNetworkSuggestion() { + public void testLatestNetworkTime() { if (TimeDetectorNetworkTimeHelper.isInUse()) { - NetworkTimeSuggestion latestNetworkTime = createNetworkTimeSuggestion(); - mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkTime); + NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion(); + mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion); - assertEquals(latestNetworkTime, mTimeDetectorService.getLatestNetworkSuggestion()); + assertEquals(latestNetworkSuggestion.getUnixEpochTime(), + mTimeDetectorService.latestNetworkTime()); } else { NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult( 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123)); when(mMockNtpTrustedTime.getCachedTimeResult()) .thenReturn(latestNetworkTime); - UnixEpochTime expectedUnixEpochTime = new UnixEpochTime( + UnixEpochTime expected = new UnixEpochTime( latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis()); - NetworkTimeSuggestion expected = new NetworkTimeSuggestion( - expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis()); - assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion()); + assertEquals(expected, mTimeDetectorService.latestNetworkTime()); } } @Test - public void testGetLatestNetworkSuggestion_noTimeAvailable() { + public void testLatestNetworkTime_noTimeAvailable() { if (TimeDetectorNetworkTimeHelper.isInUse()) { mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null); - - assertNull(mTimeDetectorService.getLatestNetworkSuggestion()); } else { when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null); - assertNull(mTimeDetectorService.getLatestNetworkSuggestion()); } + assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime()); } @Test diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index b921838e0bfc..4c0361d30d67 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -263,7 +263,7 @@ public class ShortcutManagerTestUtils { + instrumentation.getContext().getUserId() + " " + RoleManager.ROLE_HOME + " " + packageName + " 0"); waitUntil("Failed to get shortcut access", - () -> hasShortcutAccess(instrumentation, packageName), 20); + () -> hasShortcutAccess(instrumentation, packageName), 60); } public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) { diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java index 3ecbbfeed254..8f0a5e6f5d0d 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java @@ -141,9 +141,9 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { */ @Test public void testMetaN() throws RemoteException { - mPhoneWindowManager.overrideExpandNotificationsPanel(); + mPhoneWindowManager.overrideTogglePanel(); sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_N}, 0); - mPhoneWindowManager.assertExpandNotification(); + mPhoneWindowManager.assertTogglePanel(); } /** diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 6da9d0c606cf..b6939747a7b6 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -67,6 +67,7 @@ import android.os.RemoteException; import android.os.Vibrator; import android.service.dreams.DreamManagerInternal; import android.telecom.TelecomManager; +import android.util.FeatureFlagUtils; import android.view.Display; import android.view.KeyEvent; import android.view.autofill.AutofillManagerInternal; @@ -76,6 +77,7 @@ import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.server.GestureLauncherService; import com.android.server.LocalServices; import com.android.server.input.InputManagerInternal; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.vr.VrManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; @@ -114,6 +116,7 @@ class TestPhoneWindowManager { @Mock private Vibrator mVibrator; @Mock private PowerManager mPowerManager; @Mock private WindowManagerPolicy.WindowManagerFuncs mWindowManagerFuncsImpl; + @Mock private InputMethodManagerInternal mInputMethodManagerInternal; @Mock private AudioManagerInternal mAudioManagerInternal; @Mock private SearchManager mSearchManager; @@ -184,6 +187,8 @@ class TestPhoneWindowManager { () -> LocalServices.getService(eq(GestureLauncherService.class))); doReturn(null).when(() -> LocalServices.getService(eq(VrManagerInternal.class))); doReturn(null).when(() -> LocalServices.getService(eq(AutofillManagerInternal.class))); + LocalServices.removeServiceForTest(InputMethodManagerInternal.class); + LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal); doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class)); doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class)); @@ -242,6 +247,7 @@ class TestPhoneWindowManager { void tearDown() { mHandlerThread.quitSafely(); + LocalServices.removeServiceForTest(InputMethodManagerInternal.class); mMockitoSession.finishMocking(); } @@ -322,12 +328,12 @@ class TestPhoneWindowManager { doReturn(true).when(mTelecomManager).endCall(); } - void overrideExpandNotificationsPanel() { + void overrideTogglePanel() { // Can't directly mock on IStatusbarService, use spyOn and override the specific api. mPhoneWindowManager.getStatusBarService(); spyOn(mPhoneWindowManager.mStatusBarService); try { - doNothing().when(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel(); + doNothing().when(mPhoneWindowManager.mStatusBarService).togglePanel(); } catch (RemoteException e) { e.printStackTrace(); } @@ -417,7 +423,13 @@ class TestPhoneWindowManager { void assertSwitchKeyboardLayout(int direction) { waitForIdle(); - verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction)); + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) { + verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction)); + verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt()); + } else { + verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction)); + verify(mInputMethodManagerInternal, never()).switchKeyboardLayout(anyInt()); + } } void assertTakeBugreport() { @@ -428,9 +440,9 @@ class TestPhoneWindowManager { Assert.assertTrue(intentCaptor.getValue().getAction() == Intent.ACTION_BUG_REPORT); } - void assertExpandNotification() throws RemoteException { + void assertTogglePanel() throws RemoteException { waitForIdle(); - verify(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel(); + verify(mPhoneWindowManager.mStatusBarService).togglePanel(); } void assertToggleShortcutsMenu() { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index d7e4c5523eee..169586e2d9dc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -1023,7 +1023,7 @@ public class WindowOrganizerTests extends WindowTestsBase { RunningTaskInfo mInfo; @Override - public void addStartingWindow(StartingWindowInfo info) { } + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index fafe90a5df55..323894ca76df 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1583,10 +1583,10 @@ class WindowTestsBase extends SystemServiceTestsBase { } @Override - public void addStartingWindow(StartingWindowInfo info) { + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { synchronized (mWMService.mGlobalLock) { final ActivityRecord activity = mWMService.mRoot.getActivityRecord( - info.appToken); + appToken); IWindow iWindow = mock(IWindow.class); doReturn(mock(IBinder.class)).when(iWindow).asBinder(); final WindowState window = WindowTestsBase.createWindow(null, @@ -1596,8 +1596,8 @@ class WindowTestsBase extends SystemServiceTestsBase { iWindow, mPowerManagerWrapper); activity.mStartingWindow = window; - mAppWindowMap.put(info.appToken, window); - mTaskAppMap.put(info.taskInfo.taskId, info.appToken); + mAppWindowMap.put(appToken, window); + mTaskAppMap.put(info.taskInfo.taskId, appToken); } if (mRunnableWhenAddingSplashScreen != null) { mRunnableWhenAddingSplashScreen.run(); diff --git a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java index 0dcf8ce96192..b1cd99420737 100644 --- a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java @@ -19,7 +19,6 @@ package com.android.server.usb; import android.annotation.NonNull; import android.content.Context; import android.hardware.usb.UsbConfiguration; -import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; @@ -35,9 +34,12 @@ import android.os.Bundle; import android.service.usb.UsbDirectMidiDeviceProto; import android.util.Log; +import com.android.internal.midi.MidiEventMultiScheduler; import com.android.internal.midi.MidiEventScheduler; import com.android.internal.midi.MidiEventScheduler.MidiEvent; import com.android.internal.util.dump.DualDumpOutputStream; +import com.android.server.usb.descriptors.UsbACMidi10Endpoint; +import com.android.server.usb.descriptors.UsbDescriptor; import com.android.server.usb.descriptors.UsbDescriptorParser; import com.android.server.usb.descriptors.UsbEndpointDescriptor; import com.android.server.usb.descriptors.UsbInterfaceDescriptor; @@ -45,6 +47,7 @@ import com.android.server.usb.descriptors.UsbMidiBlockParser; import libcore.io.IoUtils; +import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; @@ -56,7 +59,7 @@ import java.util.ArrayList; */ public final class UsbDirectMidiDevice implements Closeable { private static final String TAG = "UsbDirectMidiDevice"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; private Context mContext; private String mName; @@ -74,9 +77,6 @@ public final class UsbDirectMidiDevice implements Closeable { private MidiDeviceServer mServer; - // event schedulers for each input port of the physical device - private MidiEventScheduler[] mEventSchedulers; - // Timeout for sending a packet to a device. // If bulkTransfer times out, retry sending the packet up to 20 times. private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 50; @@ -86,8 +86,21 @@ public final class UsbDirectMidiDevice implements Closeable { private static final int THREAD_JOIN_TIMEOUT_MILLISECONDS = 200; private ArrayList<UsbDeviceConnection> mUsbDeviceConnections; + + // Array of endpoints by device connection. private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints; private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints; + + // Array of cable counts by device connection. + // Each number here maps to an entry in mInputUsbEndpoints or mOutputUsbEndpoints. + // This is needed because this info is part of UsbEndpointDescriptor but not UsbEndpoint. + private ArrayList<ArrayList<Integer>> mInputUsbEndpointCableCounts; + private ArrayList<ArrayList<Integer>> mOutputUsbEndpointCableCounts; + + // Array of event schedulers by device connection. + // Each number here maps to an entry in mOutputUsbEndpoints. + private ArrayList<ArrayList<MidiEventMultiScheduler>> mMidiEventMultiSchedulers; + private ArrayList<Thread> mThreads; private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser(); @@ -97,8 +110,6 @@ public final class UsbDirectMidiDevice implements Closeable { private boolean mIsOpen; private boolean mServerAvailable; - private UsbMidiPacketConverter mUsbMidiPacketConverter; - private PowerBoostSetter mPowerBoostSetter = null; private static final byte MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE = 0x02; @@ -238,9 +249,9 @@ public final class UsbDirectMidiDevice implements Closeable { interfaceDescriptor.getEndpointDescriptor(endpointIndex); // 0 is output, 1 << 7 is input. if (endpoint.getDirection() == 0) { - numOutputs++; + numOutputs += getNumJacks(endpoint); } else { - numInputs++; + numInputs += getNumJacks(endpoint); } } } @@ -307,19 +318,21 @@ public final class UsbDirectMidiDevice implements Closeable { Log.d(TAG, "openLocked()"); UsbManager manager = mContext.getSystemService(UsbManager.class); - // Converting from raw MIDI to USB MIDI is not thread-safe. - // UsbMidiPacketConverter creates a converter from raw MIDI - // to USB MIDI for each USB output. - mUsbMidiPacketConverter = new UsbMidiPacketConverter(mNumOutputs); - mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(); mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(); mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(); + mInputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>(); + mOutputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>(); + mMidiEventMultiSchedulers = new ArrayList<ArrayList<MidiEventMultiScheduler>>(); mThreads = new ArrayList<Thread>(); for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) { ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>(); ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>(); + ArrayList<Integer> inputEndpointCableCounts = new ArrayList<Integer>(); + ArrayList<Integer> outputEndpointCableCounts = new ArrayList<Integer>(); + ArrayList<MidiEventMultiScheduler> midiEventMultiSchedulers = + new ArrayList<MidiEventMultiScheduler>(); UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex); for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints(); endpointIndex++) { @@ -328,8 +341,13 @@ public final class UsbDirectMidiDevice implements Closeable { // 0 is output, 1 << 7 is input. if (endpoint.getDirection() == 0) { outputEndpoints.add(endpoint.toAndroid(mParser)); + outputEndpointCableCounts.add(getNumJacks(endpoint)); + MidiEventMultiScheduler scheduler = + new MidiEventMultiScheduler(getNumJacks(endpoint)); + midiEventMultiSchedulers.add(scheduler); } else { inputEndpoints.add(endpoint.toAndroid(mParser)); + inputEndpointCableCounts.add(getNumJacks(endpoint)); } } if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) { @@ -341,40 +359,69 @@ public final class UsbDirectMidiDevice implements Closeable { mUsbDeviceConnections.add(connection); mInputUsbEndpoints.add(inputEndpoints); mOutputUsbEndpoints.add(outputEndpoints); + mInputUsbEndpointCableCounts.add(inputEndpointCableCounts); + mOutputUsbEndpointCableCounts.add(outputEndpointCableCounts); + mMidiEventMultiSchedulers.add(midiEventMultiSchedulers); } } - mEventSchedulers = new MidiEventScheduler[mNumOutputs]; - - for (int i = 0; i < mNumOutputs; i++) { - MidiEventScheduler scheduler = new MidiEventScheduler(); - mEventSchedulers[i] = scheduler; - mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver()); + // Set up event schedulers + int outputIndex = 0; + for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size(); + connectionIndex++) { + for (int endpointIndex = 0; + endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size(); + endpointIndex++) { + int cableCount = + mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex); + MidiEventMultiScheduler multiScheduler = + mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex); + for (int cableNumber = 0; cableNumber < cableCount; cableNumber++) { + MidiEventScheduler scheduler = multiScheduler.getEventScheduler(cableNumber); + mMidiInputPortReceivers[outputIndex].setReceiver(scheduler.getReceiver()); + outputIndex++; + } + } } final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); // Create input thread for each input port of the physical device - int portNumber = 0; + int portStartNumber = 0; for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size(); connectionIndex++) { for (int endpointIndex = 0; endpointIndex < mInputUsbEndpoints.get(connectionIndex).size(); endpointIndex++) { + // Each USB endpoint maps to one or more outputReceivers. USB MIDI data from an + // endpoint should be sent to the appropriate outputReceiver. A new thread is + // created and waits for incoming USB data. Once the data is received, it is added + // to the packet converter. The packet converter acts as an inverse multiplexer. + // With a for loop, data is pulled per cable and sent to the correct output + // receiver. The first byte of each legacy MIDI 1.0 USB message indicates which + // cable the data should be used and is how the reverse multiplexer directs data. + // For MIDI UMP endpoints, a multiplexer is not needed as we are just swapping + // the endianness of the packets. final UsbDeviceConnection connectionFinal = mUsbDeviceConnections.get(connectionIndex); final UsbEndpoint endpointFinal = mInputUsbEndpoints.get(connectionIndex).get(endpointIndex); - final int portFinal = portNumber; + final int portStartFinal = portStartNumber; + final int cableCountFinal = + mInputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex); - Thread newThread = new Thread("UsbDirectMidiDevice input thread " + portFinal) { + Thread newThread = new Thread("UsbDirectMidiDevice input thread " + + portStartFinal) { @Override public void run() { final UsbRequest request = new UsbRequest(); + final UsbMidiPacketConverter packetConverter = new UsbMidiPacketConverter(); + packetConverter.createDecoders(cableCountFinal); try { request.initialize(connectionFinal, endpointFinal); byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()]; - while (true) { + boolean keepGoing = true; + while (keepGoing) { if (Thread.currentThread().interrupted()) { Log.w(TAG, "input thread interrupted"); break; @@ -404,42 +451,56 @@ public final class UsbDirectMidiDevice implements Closeable { logByteArray("Input before conversion ", inputBuffer, 0, bytesRead); } + + // Add packets into the packet decoder. + if (!mIsUniversalMidiDevice) { + packetConverter.decodeMidiPackets(inputBuffer, bytesRead); + } + byte[] convertedArray; - if (mIsUniversalMidiDevice) { - // For USB, each 32 bit word of a UMP is - // sent with the least significant byte first. - convertedArray = swapEndiannessPerWord(inputBuffer, - bytesRead); - } else { - if (mUsbMidiPacketConverter == null) { - Log.w(TAG, "mUsbMidiPacketConverter is null"); - break; + for (int cableNumber = 0; cableNumber < cableCountFinal; + cableNumber++) { + if (mIsUniversalMidiDevice) { + // For USB, each 32 bit word of a UMP is + // sent with the least significant byte first. + convertedArray = swapEndiannessPerWord(inputBuffer, + bytesRead); + } else { + convertedArray = + packetConverter.pullDecodedMidiPackets( + cableNumber); } - convertedArray = - mUsbMidiPacketConverter.usbMidiToRawMidi( - inputBuffer, bytesRead); - } - if (DEBUG) { - logByteArray("Input after conversion ", convertedArray, - 0, convertedArray.length); - } + if (DEBUG) { + logByteArray("Input " + cableNumber + + " after conversion ", convertedArray, 0, + convertedArray.length); + } - if ((outputReceivers == null) - || (outputReceivers[portFinal] == null)) { - Log.w(TAG, "outputReceivers is null"); - break; - } - outputReceivers[portFinal].send(convertedArray, 0, - convertedArray.length, timestamp); - - // Boost power if there seems to be a voice message. - // For legacy devices, boost when message is more than size 1. - // For UMP devices, boost for channel voice messages. - if ((mPowerBoostSetter != null && convertedArray.length > 1) - && (!mIsUniversalMidiDevice - || isChannelVoiceMessage(convertedArray))) { - mPowerBoostSetter.boostPower(); + if (convertedArray.length == 0) { + continue; + } + + if ((outputReceivers == null) + || (outputReceivers[portStartFinal + cableNumber] + == null)) { + Log.w(TAG, "outputReceivers is null"); + keepGoing = false; + break; + } + outputReceivers[portStartFinal + cableNumber].send( + convertedArray, 0, convertedArray.length, + timestamp); + + // Boost power if there seems to be a voice message. + // For legacy devices, boost if message length > 1. + // For UMP devices, boost for channel voice messages. + if ((mPowerBoostSetter != null + && convertedArray.length > 1) + && (!mIsUniversalMidiDevice + || isChannelVoiceMessage(convertedArray))) { + mPowerBoostSetter.boostPower(); + } } } } @@ -455,64 +516,93 @@ public final class UsbDirectMidiDevice implements Closeable { }; newThread.start(); mThreads.add(newThread); - portNumber++; + portStartNumber += cableCountFinal; } } // Create output thread for each output port of the physical device - portNumber = 0; + portStartNumber = 0; for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size(); connectionIndex++) { for (int endpointIndex = 0; endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size(); endpointIndex++) { + // Each USB endpoint maps to one or more MIDI ports. Each port has an event + // scheduler that is used to pull incoming raw MIDI bytes from Android apps. + // With a MidiEventMultiScheduler, data can be pulled if any of the schedulers + // have new incoming data. This data is then packaged as USB MIDI packets before + // getting sent through USB. One thread will be created per endpoint that pulls + // data from all relevant event schedulers. Raw MIDI from the event schedulers + // will be converted to the correct USB MIDI format before getting sent through + // USB. + final UsbDeviceConnection connectionFinal = mUsbDeviceConnections.get(connectionIndex); final UsbEndpoint endpointFinal = mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex); - final int portFinal = portNumber; - final MidiEventScheduler eventSchedulerFinal = mEventSchedulers[portFinal]; - - Thread newThread = new Thread("UsbDirectMidiDevice output thread " + portFinal) { + final int portStartFinal = portStartNumber; + final int cableCountFinal = + mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex); + final MidiEventMultiScheduler multiSchedulerFinal = + mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex); + + Thread newThread = new Thread("UsbDirectMidiDevice output write thread " + + portStartFinal) { @Override public void run() { try { - while (true) { - if (Thread.currentThread().interrupted()) { - Log.w(TAG, "output thread interrupted"); + final ByteArrayOutputStream midi2ByteStream = + new ByteArrayOutputStream(); + final UsbMidiPacketConverter packetConverter = + new UsbMidiPacketConverter(); + packetConverter.createEncoders(cableCountFinal); + boolean isInterrupted = false; + while (!isInterrupted) { + boolean wasSuccessful = multiSchedulerFinal.waitNextEvent(); + if (!wasSuccessful) { + Log.d(TAG, "output thread closed"); break; } - MidiEvent event; - try { - event = (MidiEvent) eventSchedulerFinal.waitNextEvent(); - } catch (InterruptedException e) { - Log.w(TAG, "event scheduler interrupted"); - break; - } - if (event == null) { - Log.w(TAG, "event is null"); - break; + long now = System.nanoTime(); + for (int cableNumber = 0; cableNumber < cableCountFinal; + cableNumber++) { + MidiEventScheduler eventScheduler = + multiSchedulerFinal.getEventScheduler(cableNumber); + MidiEvent event = + (MidiEvent) eventScheduler.getNextEvent(now); + while (event != null) { + if (DEBUG) { + logByteArray("Output before conversion ", + event.data, 0, event.count); + } + if (mIsUniversalMidiDevice) { + // For USB, each 32 bit word of a UMP is + // sent with the least significant byte first. + byte[] convertedArray = swapEndiannessPerWord( + event.data, event.count); + midi2ByteStream.write(convertedArray, 0, + convertedArray.length); + } else { + packetConverter.encodeMidiPackets(event.data, + event.count, cableNumber); + } + eventScheduler.addEventToPool(event); + event = (MidiEvent) eventScheduler.getNextEvent(now); + } } - if (DEBUG) { - logByteArray("Output before conversion ", event.data, 0, - event.count); + if (Thread.currentThread().interrupted()) { + Log.d(TAG, "output thread interrupted"); + break; } - byte[] convertedArray; + byte[] convertedArray = new byte[0]; if (mIsUniversalMidiDevice) { - // For USB, each 32 bit word of a UMP is - // sent with the least significant byte first. - convertedArray = swapEndiannessPerWord(event.data, - event.count); + convertedArray = midi2ByteStream.toByteArray(); + midi2ByteStream.reset(); } else { - if (mUsbMidiPacketConverter == null) { - Log.w(TAG, "mUsbMidiPacketConverter is null"); - break; - } convertedArray = - mUsbMidiPacketConverter.rawMidiToUsbMidi( - event.data, event.count, portFinal); + packetConverter.pullEncodedMidiPackets(); } if (DEBUG) { @@ -520,7 +610,6 @@ public final class UsbDirectMidiDevice implements Closeable { convertedArray.length); } - boolean isInterrupted = false; // Split the packet into multiple if they are greater than the // endpoint's max packet size. for (int curPacketStart = 0; @@ -558,11 +647,9 @@ public final class UsbDirectMidiDevice implements Closeable { } } } - if (isInterrupted == true) { - break; - } - eventSchedulerFinal.addEventToPool(event); } + } catch (InterruptedException e) { + Log.w(TAG, "output thread: ", e); } catch (NullPointerException e) { Log.e(TAG, "output thread: ", e); } @@ -571,7 +658,7 @@ public final class UsbDirectMidiDevice implements Closeable { }; newThread.start(); mThreads.add(newThread); - portNumber++; + portStartNumber += cableCountFinal; } } @@ -667,11 +754,21 @@ public final class UsbDirectMidiDevice implements Closeable { } mThreads = null; - for (int i = 0; i < mEventSchedulers.length; i++) { + for (int i = 0; i < mMidiInputPortReceivers.length; i++) { mMidiInputPortReceivers[i].setReceiver(null); - mEventSchedulers[i].close(); } - mEventSchedulers = null; + + for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size(); + connectionIndex++) { + for (int endpointIndex = 0; + endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size(); + endpointIndex++) { + MidiEventMultiScheduler multiScheduler = + mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex); + multiScheduler.close(); + } + } + mMidiEventMultiSchedulers = null; for (UsbDeviceConnection connection : mUsbDeviceConnections) { connection.close(); @@ -679,8 +776,8 @@ public final class UsbDirectMidiDevice implements Closeable { mUsbDeviceConnections = null; mInputUsbEndpoints = null; mOutputUsbEndpoints = null; - - mUsbMidiPacketConverter = null; + mInputUsbEndpointCableCounts = null; + mOutputUsbEndpointCableCounts = null; mIsOpen = false; } @@ -788,4 +885,19 @@ public final class UsbDirectMidiDevice implements Closeable { return messageType == MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE || messageType == MESSAGE_TYPE_MIDI_2_CHANNEL_VOICE; } + + // Returns the number of jacks for MIDI 1.0 endpoints. + // For MIDI 2.0 endpoints, this concept does not exist and each endpoint should be treated as + // one port. + private int getNumJacks(UsbEndpointDescriptor usbEndpointDescriptor) { + UsbDescriptor classSpecificEndpointDescriptor = + usbEndpointDescriptor.getClassSpecificEndpointDescriptor(); + if (classSpecificEndpointDescriptor != null + && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) { + UsbACMidi10Endpoint midiEndpoint = + (UsbACMidi10Endpoint) classSpecificEndpointDescriptor; + return midiEndpoint.getNumJacks(); + } + return 1; + } } diff --git a/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java index 56bb23681741..65d7a41cf1ec 100644 --- a/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java +++ b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java @@ -16,12 +16,17 @@ package com.android.server.usb; +import android.util.Log; + import java.io.ByteArrayOutputStream; /** - * Converts between MIDI packets and USB MIDI 1.0 packets. + * Converts between raw MIDI packets and USB MIDI 1.0 packets. + * This is NOT thread-safe. Please handle locking outside this function for multiple threads. + * For data mapping to an invalid cable number, this converter will use the first cable. */ public class UsbMidiPacketConverter { + private static final String TAG = "UsbMidiPacketConverter"; // Refer to Table 4-1 in USB MIDI 1.0 spec. private static final int[] PAYLOAD_SIZE = new int[]{ @@ -74,54 +79,133 @@ public class UsbMidiPacketConverter { private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0; private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7; - private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder(); private UsbMidiEncoder[] mUsbMidiEncoders; + private ByteArrayOutputStream mEncoderOutputStream = new ByteArrayOutputStream(); - public UsbMidiPacketConverter(int numEncoders) { - mUsbMidiEncoders = new UsbMidiEncoder[numEncoders]; - for (int i = 0; i < numEncoders; i++) { - mUsbMidiEncoders[i] = new UsbMidiEncoder(); - } - } + private UsbMidiDecoder mUsbMidiDecoder; /** - * Converts a USB MIDI array into a raw MIDI array. + * Creates encoders. * - * @param usbMidiBytes the USB MIDI bytes to convert - * @param size the size of usbMidiBytes - * @return byte array of raw MIDI packets + * createEncoders() must be called before raw MIDI can be converted to USB MIDI. + * + * @param size the number of encoders to create */ - public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) { - return mUsbMidiDecoder.decode(usbMidiBytes, size); + public void createEncoders(int size) { + mUsbMidiEncoders = new UsbMidiEncoder[size]; + for (int i = 0; i < size; i++) { + mUsbMidiEncoders[i] = new UsbMidiEncoder(i); + } } /** * Converts a raw MIDI array into a USB MIDI array. * + * Call pullEncodedMidiPackets to retrieve the byte array. + * * @param midiBytes the raw MIDI bytes to convert * @param size the size of usbMidiBytes * @param encoderId which encoder to use + */ + public void encodeMidiPackets(byte[] midiBytes, int size, int encoderId) { + // Use the first encoder if the encoderId is invalid. + if (encoderId >= mUsbMidiEncoders.length) { + Log.w(TAG, "encoderId " + encoderId + " invalid"); + encoderId = 0; + } + byte[] encodedPacket = mUsbMidiEncoders[encoderId].encode(midiBytes, size); + mEncoderOutputStream.write(encodedPacket, 0, encodedPacket.length); + } + + /** + * Returns the encoded MIDI packets from encodeMidiPackets + * * @return byte array of USB MIDI packets */ - public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size, int encoderId) { - return mUsbMidiEncoders[encoderId].encode(midiBytes, size); + public byte[] pullEncodedMidiPackets() { + byte[] output = mEncoderOutputStream.toByteArray(); + mEncoderOutputStream.reset(); + return output; + } + + /** + * Creates decoders. + * + * createDecoders() must be called before USB MIDI can be converted to raw MIDI. + * + * @param size the number of decoders to create + */ + public void createDecoders(int size) { + mUsbMidiDecoder = new UsbMidiDecoder(size); + } + + /** + * Converts a USB MIDI array into a multiple MIDI arrays, one per cable. + * + * Call pullDecodedMidiPackets to retrieve the byte array. + * + * @param usbMidiBytes the USB MIDI bytes to convert + * @param size the size of usbMidiBytes + */ + public void decodeMidiPackets(byte[] usbMidiBytes, int size) { + mUsbMidiDecoder.decode(usbMidiBytes, size); + } + + /** + * Returns the decoded MIDI packets from decodeMidiPackets + * + * @param cableNumber the cable to pull data from + * @return byte array of raw MIDI packets + */ + public byte[] pullDecodedMidiPackets(int cableNumber) { + return mUsbMidiDecoder.pullBytes(cableNumber); } private class UsbMidiDecoder { + int mNumJacks; + ByteArrayOutputStream[] mDecodedByteArrays; + + UsbMidiDecoder(int numJacks) { + mNumJacks = numJacks; + mDecodedByteArrays = new ByteArrayOutputStream[numJacks]; + for (int i = 0; i < numJacks; i++) { + mDecodedByteArrays[i] = new ByteArrayOutputStream(); + } + } + // Decodes the data from USB MIDI to raw MIDI. // Each valid 4 byte input maps to a 1-3 byte output. // Reference the USB MIDI 1.0 spec for more info. - public byte[] decode(byte[] usbMidiBytes, int size) { + public void decode(byte[] usbMidiBytes, int size) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + if (size % 4 != 0) { + Log.w(TAG, "size " + size + " not multiple of 4"); + } for (int i = 0; i + 3 < size; i += 4) { + int cableNumber = (usbMidiBytes[i] >> 4) & 0x0f; int codeIndex = usbMidiBytes[i] & 0x0f; int numPayloadBytes = PAYLOAD_SIZE[codeIndex]; if (numPayloadBytes < 0) { continue; } - outputStream.write(usbMidiBytes, i + 1, numPayloadBytes); + // Use the first cable if the cable number is invalid. + if (cableNumber >= mNumJacks) { + Log.w(TAG, "cableNumber " + cableNumber + " invalid"); + cableNumber = 0; + } + mDecodedByteArrays[cableNumber].write(usbMidiBytes, i + 1, numPayloadBytes); } - return outputStream.toByteArray(); + } + + public byte[] pullBytes(int cableNumber) { + // Use the first cable if the cable number is invalid. + if (cableNumber >= mNumJacks) { + Log.w(TAG, "cableNumber " + cableNumber + " invalid"); + cableNumber = 0; + } + byte[] output = mDecodedByteArrays[cableNumber].toByteArray(); + mDecodedByteArrays[cableNumber].reset(); + return output; } } @@ -135,6 +219,13 @@ public class UsbMidiPacketConverter { private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data + private byte mShiftedCableNumber; + + UsbMidiEncoder(int cableNumber) { + // Jack Id is always the left nibble of every byte so shift this now. + mShiftedCableNumber = (byte) (cableNumber << 4); + } + // Encodes the data from raw MIDI to USB MIDI. // Each valid 1-3 byte input maps to a 4 byte output. // Reference the USB MIDI 1.0 spec for more info. @@ -153,7 +244,8 @@ public class UsbMidiPacketConverter { midiBytes[curLocation]; mNumStoredSystemExclusiveBytes++; if (mNumStoredSystemExclusiveBytes == 3) { - outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES); + outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES + | mShiftedCableNumber); outputStream.write(mStoredSystemExclusiveBytes, 0, 3); mNumStoredSystemExclusiveBytes = 0; } @@ -179,7 +271,7 @@ public class UsbMidiPacketConverter { byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f); int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber]; if (curLocation + channelMessageSize <= size) { - outputStream.write(codeIndexNumber); + outputStream.write(codeIndexNumber | mShiftedCableNumber); outputStream.write(midiBytes, curLocation, channelMessageSize); // Fill in the rest of the bytes with 0. outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize); @@ -197,8 +289,8 @@ public class UsbMidiPacketConverter { curLocation++; } else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) { // 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07 - outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE - + mNumStoredSystemExclusiveBytes); + outputStream.write((CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE + + mNumStoredSystemExclusiveBytes) | mShiftedCableNumber); mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] = midiBytes[curLocation]; mNumStoredSystemExclusiveBytes++; @@ -218,7 +310,7 @@ public class UsbMidiPacketConverter { } else { int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber]; if (curLocation + systemMessageSize <= size) { - outputStream.write(codeIndexNumber); + outputStream.write(codeIndexNumber | mShiftedCableNumber); outputStream.write(midiBytes, curLocation, systemMessageSize); // Fill in the rest of the bytes with 0. outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize); @@ -236,7 +328,7 @@ public class UsbMidiPacketConverter { } private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) { - outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE); + outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE | mShiftedCableNumber); outputStream.write(byteToWrite); outputStream.write(0); outputStream.write(0); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java index 1f448acac5e8..117a3d9f374e 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java @@ -118,7 +118,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor { mClassSpecificEndpointDescriptor = descriptor; } - UsbDescriptor getClassSpecificEndpointDescriptor() { + public UsbDescriptor getClassSpecificEndpointDescriptor() { return mClassSpecificEndpointDescriptor; } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java index ce1157e1d80c..7d5750e49907 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java @@ -30,11 +30,14 @@ import android.media.soundtrigger.SoundModel; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; +import android.os.BatteryStatsInternal; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.util.Log; import com.android.internal.util.LatencyTracker; +import com.android.server.LocalServices; import java.io.PrintWriter; import java.text.SimpleDateFormat; @@ -291,6 +294,8 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession) throws RemoteException { try { + BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger( + SystemClock.elapsedRealtime(), mOriginatorIdentity.uid); mCallbackDelegate.onRecognition(modelHandle, event, captureSession); logVoidReturn("onRecognition", modelHandle, event); } catch (Exception e) { @@ -304,6 +309,8 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt int captureSession) throws RemoteException { try { + BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger( + SystemClock.elapsedRealtime(), mOriginatorIdentity.uid); startKeyphraseEventLatencyTracking(event); mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession); logVoidReturn("onPhraseRecognition", modelHandle, event); @@ -387,6 +394,12 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt } } + private static class BatteryStatsHolder { + private static final BatteryStatsInternal INSTANCE = + LocalServices.getService(BatteryStatsInternal.class); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// // Actual logging logic below. private static final int NUM_EVENTS_TO_DUMP = 64; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 43592348adb3..025e1dc78c86 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -30,7 +30,7 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPH import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledSince; +import android.compat.annotation.Disabled; import android.content.ComponentName; import android.content.ContentCaptureOptions; import android.content.Context; @@ -41,7 +41,6 @@ import android.media.AudioFormat; import android.media.AudioManagerInternal; import android.media.permission.Identity; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; @@ -89,8 +88,7 @@ final class HotwordDetectionConnection { static final boolean DEBUG = false; /** - * For apps targeting Android API Build.VERSION_CODES#UPSIDE_DOWN_CAKE and above, - * implementors of the HotwordDetectionService must not augment the phrase IDs which are + * Implementors of the HotwordDetectionService must not augment the phrase IDs which are * supplied via HotwordDetectionService * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback). * @@ -104,7 +102,7 @@ final class HotwordDetectionConnection { * </p> */ @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Disabled public static final long ENFORCE_HOTWORD_PHRASE_ID = 215066299L; private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds"; diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java index 6b2bea032a00..97538c1c833f 100644 --- a/telecomm/java/android/telecom/CallControl.java +++ b/telecomm/java/android/telecom/CallControl.java @@ -76,7 +76,10 @@ public final class CallControl { } /** - * Request Telecom set the call state to active. + * Request Telecom set the call state to active. This method should be called when either an + * outgoing call is ready to go active or a held call is ready to go active again. For incoming + * calls that are ready to be answered, use + * {@link CallControl#answer(int, Executor, OutcomeReceiver)}. * * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback * will be called on. @@ -106,6 +109,43 @@ public final class CallControl { } /** + * Request Telecom answer an incoming call. For outgoing calls and calls that have been placed + * on hold, use {@link CallControl#setActive(Executor, OutcomeReceiver)}. + * + * @param videoState to report to Telecom. Telecom will store VideoState in the event another + * service/device requests it in order to continue the call on another screen. + * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback + * will be called on. + * @param callback that will be completed on the Telecom side that details success or failure + * of the requested operation. + * + * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully + * switched the call state to active + * + * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set + * the call state to active. A {@link CallException} will be passed + * that details why the operation failed. + */ + public void answer(@android.telecom.CallAttributes.CallType int videoState, + @CallbackExecutor @NonNull Executor executor, + @NonNull OutcomeReceiver<Void, CallException> callback) { + validateVideoState(videoState); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + if (mServerInterface != null) { + try { + mServerInterface.answer(videoState, mCallId, + new CallControlResultReceiver("answer", executor, callback)); + + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } else { + throw new IllegalStateException(INTERFACE_ERROR_MSG); + } + } + + /** * Request Telecom set the call state to inactive. This the same as hold for two call endpoints * but can be extended to setting a meeting to inactive. * @@ -343,4 +383,13 @@ public final class CallControl { } } + /** @hide */ + private void validateVideoState(@android.telecom.CallAttributes.CallType int videoState) { + if (videoState != CallAttributes.AUDIO_CALL && videoState != CallAttributes.VIDEO_CALL) { + throw new IllegalArgumentException(TextUtils.formatSimple( + "The VideoState argument passed in, %d , is not a valid VideoState. The " + + "VideoState choices are limited to CallAttributes.AUDIO_CALL or" + + "CallAttributes.VIDEO_CALL", videoState)); + } + } } diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index b6def1a22205..94c737d61b0a 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -1227,6 +1227,12 @@ public final class PhoneAccount implements Parcelable { if (hasCapabilities(CAPABILITY_VOICE_CALLING_AVAILABLE)) { sb.append("Voice "); } + if (hasCapabilities(CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)) { + sb.append("TransactOps "); + } + if (hasCapabilities(CAPABILITY_SUPPORTS_CALL_STREAMING)) { + sb.append("Stream "); + } return sb.toString(); } diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl index 3e651e92e612..5e2c923e4c9c 100644 --- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl +++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl @@ -27,6 +27,7 @@ import android.os.ResultReceiver; */ oneway interface ICallControl { void setActive(String callId, in ResultReceiver callback); + void answer(int videoState, String callId, in ResultReceiver callback); void setInactive(String callId, in ResultReceiver callback); void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback); void startCallStreaming(String callId, in ResultReceiver callback); diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java index c352f2bc3f83..abcce5f61aee 100644 --- a/telephony/java/android/telephony/DomainSelectionService.java +++ b/telephony/java/android/telephony/DomainSelectionService.java @@ -537,9 +537,9 @@ public class DomainSelectionService extends Service { } @Override - public void onWlanSelected() { + public void onWlanSelected(boolean useEmergencyPdn) { try { - mCallback.onWlanSelected(); + mCallback.onWlanSelected(useEmergencyPdn); } catch (Exception e) { Rlog.e(TAG, "onWlanSelected e=" + e); } @@ -702,9 +702,10 @@ public class DomainSelectionService extends Service { } @Override - public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) { + public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain, + boolean useEmergencyPdn) { try { - mCallback.onDomainSelected(domain); + mCallback.onDomainSelected(domain, useEmergencyPdn); } catch (Exception e) { Rlog.e(TAG, "onDomainSelected e=" + e); } diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index b9008c4ecd81..788d0e88170d 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -1526,7 +1526,7 @@ public class PhoneNumberUtils { * Formats the specified {@code phoneNumber} to the E.164 representation. * * @param phoneNumber the phone number to format. - * @param defaultCountryIso the ISO 3166-1 two letters country code. + * @param defaultCountryIso the ISO 3166-1 two letters country code in UPPER CASE. * @return the E.164 representation, or null if the given phone number is not valid. */ public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) { @@ -1537,7 +1537,7 @@ public class PhoneNumberUtils { * Formats the specified {@code phoneNumber} to the RFC3966 representation. * * @param phoneNumber the phone number to format. - * @param defaultCountryIso the ISO 3166-1 two letters country code. + * @param defaultCountryIso the ISO 3166-1 two letters country code in UPPER CASE. * @return the RFC3966 representation, or null if the given phone number is not valid. */ public static String formatNumberToRFC3966(String phoneNumber, String defaultCountryIso) { diff --git a/telephony/java/android/telephony/TransportSelectorCallback.java b/telephony/java/android/telephony/TransportSelectorCallback.java index d39679072d37..04752e418466 100644 --- a/telephony/java/android/telephony/TransportSelectorCallback.java +++ b/telephony/java/android/telephony/TransportSelectorCallback.java @@ -35,8 +35,10 @@ public interface TransportSelectorCallback { /** * Notify that WLAN transport has been selected. + * + * @param useEmergencyPdn Indicates whether Wi-Fi emergency services use emergency PDN or not. */ - void onWlanSelected(); + void onWlanSelected(boolean useEmergencyPdn); /** * Notify that WWAN transport has been selected. diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java index b3682caa4eb3..f9c2620cfaf8 100644 --- a/telephony/java/android/telephony/WwanSelectorCallback.java +++ b/telephony/java/android/telephony/WwanSelectorCallback.java @@ -46,6 +46,7 @@ public interface WwanSelectorCallback { * this interface can be discarded. * * @param domain The selected domain. + * @param useEmergencyPdn Indicates whether emergency services use emergency PDN or not. */ - void onDomainSelected(@NetworkRegistrationInfo.Domain int domain); + void onDomainSelected(@NetworkRegistrationInfo.Domain int domain, boolean useEmergencyPdn); } diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java index 98ed1fa5ddeb..ecd703960d08 100755 --- a/telephony/java/android/telephony/ims/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -584,6 +584,7 @@ public class ImsCallSession { * Gets the call ID of the session. * * @return the call ID + * If null is returned for getCallId, then that means that the call ID has not been set yet. */ public String getCallId() { if (mClosed) { @@ -1779,7 +1780,7 @@ public class ImsCallSession { sb.append("[ImsCallSession objId:"); sb.append(System.identityHashCode(this)); sb.append(" callId:"); - sb.append(getCallId()); + sb.append(mCallId != null ? mCallId : "[UNINITIALIZED]"); sb.append("]"); return sb.toString(); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index f8cf81ce4993..64454cc50a5c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -166,8 +166,8 @@ public class SatelliteManager { public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility"; /** - * Bundle key to get the respoonse from - * {@link #sendSatelliteDatagram(long, int, SatelliteDatagram, Executor, OutcomeReceiver)}. + * Bundle key to get the respoonse from {@link + * #sendSatelliteDatagram(long, int, SatelliteDatagram, boolean, Executor, OutcomeReceiver)}. * @hide */ public static final String KEY_SEND_SATELLITE_DATAGRAM = "send_satellite_datagram"; @@ -615,9 +615,7 @@ public class SatelliteManager { /** * The default state indicating that datagram transfer is idle. - * This should be sent immediately after either - * {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_SUCCESS} or - * {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED}. + * This should be sent if there are no message transfer activity happening. */ public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; /** @@ -625,25 +623,41 @@ public class SatelliteManager { */ public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; /** + * An end state indicating that datagram sending completed successfully. + * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} + * will be sent if no more messages are pending. + */ + public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; + /** + * An end state indicating that datagram sending completed with a failure. + * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} + * must be sent before reporting any additional datagram transfer state changes. All pending + * messages will be reported as failed, to the corresponding applications. + */ + public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; + /** * A transition state indicating that a datagram is being received. */ - public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 2; + public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; /** - * A transition state indicating that datagram transfer is being retried. + * An end state indicating that datagram receiving completed successfully. + * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} + * will be sent if no more messages are pending. */ - public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RETRYING = 3; + public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; /** - * An end state indicating that datagram transfer completed successfully. + * An end state indicating that datagram receive operation found that there are no + * messages to be retrieved from the satellite. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} - * must be sent before reporting any additional datagram transfer state changes. + * will be sent if no more messages are pending. */ - public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SUCCESS = 4; + public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; /** - * An end state indicating that datagram transfer completed with a failure. + * An end state indicating that datagram receive completed with a failure. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} - * must be sent before reporting any additional datagram transfer state changes. + * will be sent if no more messages are pending. */ - public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED = 5; + public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; /** * The datagram transfer state is unknown. This generic datagram transfer state should be used * only when the datagram transfer state cannot be mapped to other specific datagram transfer @@ -655,10 +669,12 @@ public class SatelliteManager { @IntDef(prefix = {"SATELLITE_DATAGRAM_TRANSFER_STATE_"}, value = { SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, + SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS, + SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED, SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING, - SATELLITE_DATAGRAM_TRANSFER_STATE_RETRYING, - SATELLITE_DATAGRAM_TRANSFER_STATE_SUCCESS, - SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE, + SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED, SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN }) @Retention(RetentionPolicy.SOURCE) @@ -685,6 +701,10 @@ public class SatelliteManager { */ public static final int SATELLITE_MODEM_STATE_OFF = 4; /** + * Satellite modem is unavailable. + */ + public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; + /** * Satellite modem state is unknown. This generic modem state should be used only when the * modem state cannot be mapped to other specific modem states. */ @@ -697,6 +717,7 @@ public class SatelliteManager { SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING, SATELLITE_MODEM_STATE_DATAGRAM_RETRYING, SATELLITE_MODEM_STATE_OFF, + SATELLITE_MODEM_STATE_UNAVAILABLE, SATELLITE_MODEM_STATE_UNKNOWN }) @Retention(RetentionPolicy.SOURCE) @@ -733,9 +754,6 @@ public class SatelliteManager { * All other results indicate that this operation failed. * Once satellite position updates begin, datagram transfer state updates will be sent * through {@link SatellitePositionUpdateCallback}. - * Modem should report any changes in datagram transfer state and indicate success or failure - * by reporting {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_SUCCESS} or - * {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_FAILED}. * * @param executor The executor on which the callback and error code listener will be called. * @param errorCodeListener Listener for the {@link SatelliteError} result of the operation. diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 5dc1a6529151..d93ee217c2fb 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -31,7 +31,6 @@ oneway interface ISatellite { * Register the callback interface with satellite service. * * @param listener The callback interface to handle satellite service indications. - * @param errorCallback The callback to receive the error code result of the operation. * * Valid error codes returned: * SatelliteError:ERROR_NONE @@ -43,7 +42,7 @@ oneway interface ISatellite { * SatelliteError:REQUEST_NOT_SUPPORTED * SatelliteError:NO_RESOURCES */ - void setSatelliteListener(in ISatelliteListener listener, in IIntegerConsumer errorCallback); + void setSatelliteListener(in ISatelliteListener listener); /** * Request to enable or disable the satellite service listening mode. @@ -51,6 +50,8 @@ oneway interface ISatellite { * * @param enable True to enable satellite listening mode and false to disable. * @param isDemoMode Whether demo mode is enabled. + * @param timeout How long the satellite modem should wait for the next incoming page before + * disabling listening mode. * @param errorCallback The callback to receive the error code result of the operation. * * Valid error codes returned: @@ -63,7 +64,7 @@ oneway interface ISatellite { * SatelliteError:REQUEST_NOT_SUPPORTED * SatelliteError:NO_RESOURCES */ - void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode, + void requestSatelliteListeningEnabled(in boolean enable, in boolean isDemoMode, in int timeout, in IIntegerConsumer errorCallback); /** diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl index e24e892e5b55..d9668687e6e6 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl @@ -36,10 +36,10 @@ oneway interface ISatelliteListener { /** * Indicates that new datagrams have been received on the device. * - * @param datagrams Array of new datagrams received. - * @param pendingCount The number of datagrams that are pending. + * @param datagram New datagram that was received. + * @param pendingCount Number of additional datagrams yet to be received. */ - void onSatelliteDatagramsReceived(in SatelliteDatagram[] datagrams, in int pendingCount); + void onSatelliteDatagramReceived(in SatelliteDatagram datagram, in int pendingCount); /** * Indicates that the satellite has pending datagrams for the device to be pulled. diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index df5143243812..711dcbe3f62b 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -63,19 +63,19 @@ public class SatelliteImplBase extends SatelliteService { private final IBinder mBinder = new ISatellite.Stub() { @Override - public void setSatelliteListener(ISatelliteListener listener, - IIntegerConsumer errorCallback) throws RemoteException { + public void setSatelliteListener(ISatelliteListener listener) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this.setSatelliteListener(listener, errorCallback), + () -> SatelliteImplBase.this.setSatelliteListener(listener), "setSatelliteListener"); } @Override public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode, - IIntegerConsumer errorCallback) throws RemoteException { + int timeout, IIntegerConsumer errorCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestSatelliteListeningEnabled(enable, isDemoMode, errorCallback), + .requestSatelliteListeningEnabled( + enable, isDemoMode, timeout, errorCallback), "requestSatelliteListeningEnabled"); } @@ -229,7 +229,6 @@ public class SatelliteImplBase extends SatelliteService { * Register the callback interface with satellite service. * * @param listener The callback interface to handle satellite service indications. - * @param errorCallback The callback to receive the error code result of the operation. * * Valid error codes returned: * SatelliteError:ERROR_NONE @@ -241,8 +240,7 @@ public class SatelliteImplBase extends SatelliteService { * SatelliteError:REQUEST_NOT_SUPPORTED * SatelliteError:NO_RESOURCES */ - public void setSatelliteListener(@NonNull ISatelliteListener listener, - @NonNull IIntegerConsumer errorCallback) { + public void setSatelliteListener(@NonNull ISatelliteListener listener) { // stub implementation } @@ -252,6 +250,8 @@ public class SatelliteImplBase extends SatelliteService { * * @param enable True to enable satellite listening mode and false to disable. * @param isDemoMode Whether demo mode is enabled. + * @param timeout How long the satellite modem should wait for the next incoming page before + * disabling listening mode. * @param errorCallback The callback to receive the error code result of the operation. * * Valid error codes returned: @@ -264,7 +264,7 @@ public class SatelliteImplBase extends SatelliteService { * SatelliteError:REQUEST_NOT_SUPPORTED * SatelliteError:NO_RESOURCES */ - public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode, + public void requestSatelliteListeningEnabled(boolean enable, boolean isDemoMode, int timeout, @NonNull IIntegerConsumer errorCallback) { // stub implementation } diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl index 5ee7f9abdcf1..e4f94134caa1 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl +++ b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl @@ -42,6 +42,10 @@ enum SatelliteModemState { */ SATELLITE_MODEM_STATE_OFF = 4, /** + * Satellite modem is unavailable. + */ + SATELLITE_MODEM_STATE_UNAVAILABLE = 5, + /** * Satellite modem state is unknown. This generic modem state should be used only when the * modem state cannot be mapped to other specific modem states. */ diff --git a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl index aca83f4edbd7..6777256d171e 100644 --- a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl +++ b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl @@ -22,7 +22,7 @@ import com.android.internal.telephony.IWwanSelectorCallback; interface ITransportSelectorCallback { oneway void onCreated(in IDomainSelector selector); - oneway void onWlanSelected(); + oneway void onWlanSelected(boolean useEmergencyPdn); IWwanSelectorCallback onWwanSelected(); oneway void onWwanSelectedAsync(in ITransportSelectorResultCallback cb); oneway void onSelectionTerminated(int cause); diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl index 339fbee91812..94e7c871066e 100644 --- a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl +++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl @@ -21,6 +21,6 @@ import com.android.internal.telephony.IWwanSelectorResultCallback; oneway interface IWwanSelectorCallback { void onRequestEmergencyNetworkScan(in int[] preferredNetworks, int scanType, in IWwanSelectorResultCallback cb); - void onDomainSelected(int domain); + void onDomainSelected(int domain, boolean useEmergencyPdn); void onCancel(); } diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt index c5169e502344..d1a68d4e9cb2 100644 --- a/test-mock/api/current.txt +++ b/test-mock/api/current.txt @@ -235,7 +235,6 @@ package android.test.mock { method @Deprecated public android.content.Intent getLaunchIntentForPackage(String); method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String); method @Deprecated public String getNameForUid(int); - method @Deprecated public android.content.pm.PackageInfo getPackageArchiveInfo(String, int); method @Deprecated public int[] getPackageGids(String) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException; diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 855d3c1f4ea7..fef521163399 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -42,6 +42,8 @@ android_test { "androidx.test.ext.junit", "flickertestapplib", "flickerlib", + "flickerlib-apphelpers", + "flickerlib-helpers", "truth-prebuilt", "launcher-helper-lib", "launcher-aosp-tapl", @@ -65,6 +67,7 @@ java_library { ], static_libs: [ "flickerlib", + "flickerlib-helpers", "truth-prebuilt", "app-helpers-core", ], @@ -80,8 +83,10 @@ java_library { "**/helpers/*", ], static_libs: [ - "flickerlib", "flickertestapplib", + "flickerlib", + "flickerlib-apphelpers", + "flickerlib-helpers", "truth-prebuilt", "app-helpers-core", "wm-flicker-window-extensions", diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index 707b522c7024..f2ffc19f2a4e 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -24,7 +24,11 @@ </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> + <option name="run-command" value="settings put system show_touches 1" /> + <option name="run-command" value="settings put system pointer_location 1" /> <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> + <option name="teardown-command" value="settings delete system show_touches" /> + <option name="teardown-command" value="settings delete system pointer_location" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index 87bfdebec8e5..4e3ae0c0752a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -18,11 +18,13 @@ package com.android.server.wm.flicker import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerBuilderProvider +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import android.util.Log import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.junit.FlickerBuilderProvider -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.Assume import org.junit.AssumptionViolatedException import org.junit.Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index e3dc6999ceb7..9dc4bf034e66 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -18,12 +18,13 @@ package com.android.server.wm.flicker -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.component.matchers.IComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts -import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace +import android.tools.common.PlatformConsts +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.datatypes.component.IComponentNameMatcher +import android.tools.common.flicker.subject.region.RegionSubject +import android.tools.common.traces.wm.WindowManagerTrace +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.helpers.WindowUtils /** * Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all @@ -208,7 +209,7 @@ fun FlickerTest.statusBarLayerPositionAtStart( wmTrace: WindowManagerTrace? = this.reader.readWmTrace() ) { // collect navbar position for the equivalent WM state - val state = wmTrace?.firstOrNull() ?: error("WM state missing in $this") + val state = wmTrace?.entries?.firstOrNull() ?: error("WM state missing in $this") val display = state.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found") val navBarPosition = WindowUtils.getExpectedStatusBarPosition(display) assertLayersStart { @@ -224,7 +225,7 @@ fun FlickerTest.statusBarLayerPositionAtEnd( wmTrace: WindowManagerTrace? = this.reader.readWmTrace() ) { // collect navbar position for the equivalent WM state - val state = wmTrace?.lastOrNull() ?: error("WM state missing in $this") + val state = wmTrace?.entries?.lastOrNull() ?: error("WM state missing in $this") val display = state.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found") val navBarPosition = WindowUtils.getExpectedStatusBarPosition(display) assertLayersEnd { @@ -257,7 +258,7 @@ fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: ICompon if (snapshotLayers.isNotEmpty()) { val visibleAreas = snapshotLayers - .mapNotNull { snapshotLayer -> snapshotLayer.layer?.visibleRegion } + .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion } .toTypedArray() val snapshotRegion = RegionSubject(visibleAreas, this, timestamp) val appVisibleRegion = it.visibleRegion(component) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt index b7bdeeb7d834..7ef4d939fee2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.activityembedding +import android.tools.device.flicker.legacy.FlickerTest import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper import org.junit.Before diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt index 7f2e057febf1..ed17059e79e7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt @@ -17,12 +17,12 @@ package com.android.server.wm.flicker.activityembedding import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt index 20259a7d4485..c3cbb84fd37f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt @@ -17,13 +17,13 @@ package com.android.server.wm.flicker.activityembedding import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -59,14 +59,12 @@ class OpenActivityEmbeddingSecondaryToSplitTest(flicker: FlickerTest) : @Presubmit @Test fun mainActivityWindowIsAlwaysVisible() { - flicker.assertWm { - isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) - } + flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) } } /** - * Main activity surface is animated from fullscreen to ActivityEmbedding split. - * During the transition, there is a period of time that it is covered by a snapshot of itself. + * Main activity surface is animated from fullscreen to ActivityEmbedding split. During the + * transition, there is a period of time that it is covered by a snapshot of itself. */ @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index dba588a55c20..5dc2dd7d93a8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -17,12 +17,12 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt index 86f52d5d362e..9fa840190fbf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.server.wm.flicker.close -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 4d2b86a00524..b042a14b30da 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -17,12 +17,12 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt index 470764214665..136995a78fd7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.server.wm.flicker.close -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index 4d72729b368d..c4628aaa90af 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -17,14 +17,14 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.LAUNCHER +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.replacesLayer -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.LAUNCHER import org.junit.Test /** Base test class for transitions that close an app back to the launcher screen */ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt index 65d0fa991ae7..e531bc06fb3d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt @@ -17,6 +17,12 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.traces.wm.WindowManagerState.Companion.STATE_RESUMED +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import android.util.Log import androidx.test.uiautomator.By import androidx.test.uiautomator.Until @@ -24,10 +30,6 @@ import androidx.window.extensions.WindowExtensions import androidx.window.extensions.WindowExtensionsProvider import androidx.window.extensions.embedding.ActivityEmbeddingComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.windowmanager.WindowManagerState.Companion.STATE_RESUMED -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.Assume.assumeNotNull class ActivityEmbeddingAppHelper diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt index 73018a05bb58..afb9fbf47350 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt @@ -17,7 +17,8 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper class AppPairsHelper( instrumentation: Instrumentation, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt index 18563ffbc0b7..7aea05d0ce9b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt @@ -19,6 +19,7 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation import android.content.ComponentName import android.provider.Settings +import android.tools.device.helpers.FIND_TIMEOUT import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until @@ -31,10 +32,10 @@ constructor( val instr: Instrumentation, val component: ComponentName = ActivityOptions.ASSISTANT_SERVICE_COMPONENT_NAME, ) { - protected val uiDevice: UiDevice = UiDevice.getInstance(instr) - protected val defaultAssistant: String? = + private val uiDevice: UiDevice = UiDevice.getInstance(instr) + private val defaultAssistant: String? = Settings.Secure.getString(instr.targetContext.contentResolver, Settings.Secure.ASSISTANT) - protected val defaultVoiceInteractionService: String? = + private val defaultVoiceInteractionService: String? = Settings.Secure.getString( instr.targetContext.contentResolver, Settings.Secure.VOICE_INTERACTION_SERVICE diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt index cdf7ae5fbce3..47dd4e9fb32d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt @@ -17,9 +17,10 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent class FixedOrientationAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt index 2ae8e1d4932a..9227e07f5b11 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt @@ -18,16 +18,16 @@ package com.android.server.wm.flicker.helpers -import com.android.server.wm.flicker.IFlickerTestData -import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.Rotation +import android.tools.device.flicker.legacy.IFlickerTestData +import android.tools.device.flicker.rules.ChangeDisplayOrientationRule /** * Changes the device [rotation] and wait for the rotation animation to complete * * @param rotation New device rotation */ -fun IFlickerTestData.setRotation(rotation: PlatformConsts.Rotation) = +fun IFlickerTestData.setRotation(rotation: Rotation) = ChangeDisplayOrientationRule.setRotation( rotation, instrumentation, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt index f5aed411ed87..79c048a14e84 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt @@ -17,13 +17,14 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.Direction import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class GameAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt index a433b152ce73..73effbde4515 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt @@ -17,12 +17,14 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper open class ImeAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt index fb0242e8e679..a6e57d5641d6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt @@ -17,12 +17,13 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class ImeEditorPopupDialogAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt index fb04b32a7bf7..d61a500e3293 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt @@ -17,26 +17,28 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.datatypes.component.IComponentMatcher +import android.tools.common.traces.Condition +import android.tools.common.traces.DeviceStateDump +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.helpers.IME_PACKAGE +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import android.view.WindowInsets.Type.ime import android.view.WindowInsets.Type.navigationBars import android.view.WindowInsets.Type.statusBars import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.Condition -import com.android.server.wm.traces.common.DeviceStateDump -import com.android.server.wm.traces.common.component.matchers.IComponentMatcher -import com.android.server.wm.traces.common.service.PlatformConsts -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import java.util.regex.Pattern class ImeShownOnAppStartHelper @JvmOverloads constructor( instr: Instrumentation, - private val rotation: PlatformConsts.Rotation, + private val rotation: Rotation, private val imePackageName: String = IME_PACKAGE, launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL, component: ComponentNameMatcher = @@ -55,7 +57,12 @@ constructor( waitConditions: Array<Condition<DeviceStateDump>> ) { super.launchViaIntent( - wmHelper, launchedAppComponentMatcherOverride, action, stringExtras, waitConditions) + wmHelper, + launchedAppComponentMatcherOverride, + action, + stringExtras, + waitConditions + ) waitIMEShown(wmHelper) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt index 8a25e36394d2..fb5e1d2da26b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt @@ -17,9 +17,10 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent class ImeStateInitializeHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt index ba8dabd4dfec..b95d86b72f34 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt @@ -17,8 +17,9 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.parser.toFlickerComponent class LaunchBubbleHelper(instrumentation: Instrumentation) : StandardAppHelper( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt index d6ed24a6e4af..ab916858858a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt @@ -17,13 +17,15 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.Direction import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent class MailAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt index ae4223251683..e93e9c8e9bfb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt @@ -19,9 +19,10 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation import android.content.Context import android.provider.Settings +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper import android.util.Log import com.android.compatibility.common.util.SystemUtil -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import java.io.IOException class MultiWindowUtils( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt index 5c1eca32bbec..c547ad06fe6c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt @@ -17,13 +17,15 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class NewTasksAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt index 58da2d84a5ba..20ee3b9d236d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt @@ -17,9 +17,10 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent class NonResizeableAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt index d7f083031ec6..78f8bcf70b60 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt @@ -17,12 +17,14 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class NotificationAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt index 0c8589d6515f..0e852b6c84ff 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt @@ -19,17 +19,19 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation import android.media.session.MediaController import android.media.session.MediaSessionManager +import android.tools.common.datatypes.Rect +import android.tools.common.datatypes.Region +import android.tools.common.datatypes.component.IComponentMatcher +import android.tools.common.traces.ConditionsFactory +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.helpers.SYSTEMUI_PACKAGE +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import android.util.Log import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.helpers.GestureHelper.Tuple import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.Rect -import com.android.server.wm.traces.common.WindowManagerConditionsFactory -import com.android.server.wm.traces.common.component.matchers.IComponentMatcher -import com.android.server.wm.traces.common.region.Region -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper open class PipAppHelper(instrumentation: Instrumentation) : StandardAppHelper( @@ -93,10 +95,10 @@ open class PipAppHelper(instrumentation: Instrumentation) : // if the distance per step is less than 1, carry out the animation in two steps gestureHelper.pinch( - Tuple(initLeftX, yCoord), - Tuple(initRightX, yCoord), - Tuple(finalLeftX, yCoord), - Tuple(finalRightX, yCoord), + GestureHelper.Tuple(initLeftX, yCoord), + GestureHelper.Tuple(initRightX, yCoord), + GestureHelper.Tuple(finalLeftX, yCoord), + GestureHelper.Tuple(finalRightX, yCoord), adjustedSteps ) @@ -141,10 +143,10 @@ open class PipAppHelper(instrumentation: Instrumentation) : // if the distance per step is less than 1, carry out the animation in two steps gestureHelper.pinch( - Tuple(initLeftX, yCoord), - Tuple(initRightX, yCoord), - Tuple(finalLeftX, yCoord), - Tuple(finalRightX, yCoord), + GestureHelper.Tuple(initLeftX, yCoord), + GestureHelper.Tuple(initRightX, yCoord), + GestureHelper.Tuple(finalLeftX, yCoord), + GestureHelper.Tuple(finalRightX, yCoord), adjustedSteps ) @@ -167,7 +169,7 @@ open class PipAppHelper(instrumentation: Instrumentation) : launchedAppComponentMatcherOverride, action, stringExtras, - waitConditions = arrayOf(WindowManagerConditionsFactory.hasPipWindow()) + waitConditions = arrayOf(ConditionsFactory.hasPipWindow()) ) wmHelper.StateSyncBuilder().withPipShown().waitForAndVerify() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt index 8f54000a96d3..06e668ea07a1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt @@ -17,9 +17,10 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent class SeamlessRotationAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt index 61dabfc422a1..94c90dabd2c8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt @@ -17,9 +17,10 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent class ShowWhenLockedAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt index 9ed9d28efd36..64af811d1c4f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt @@ -17,9 +17,10 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent class SimpleAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt index 8f7049acbe07..316766a5c3f3 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt @@ -17,13 +17,15 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class TwoActivitiesAppHelper @JvmOverloads diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt index 092a4fd8a10a..c23cf34be60a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt @@ -17,16 +17,16 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.flicker.subject.region.RegionSubject +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -86,9 +86,7 @@ class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) if (imeSnapshotLayers.isNotEmpty()) { val visibleAreas = imeSnapshotLayers - .mapNotNull { imeSnapshotLayer -> - imeSnapshotLayer.layer?.visibleRegion - } + .mapNotNull { imeSnapshotLayer -> imeSnapshotLayer.layer.visibleRegion } .toTypedArray() val imeVisibleRegion = RegionSubject(visibleAreas, this, timestamp) val appVisibleRegion = it.visibleRegion(imeTestApp) @@ -105,7 +103,7 @@ class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt index 0870cec9ecab..823328af57aa 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt @@ -18,15 +18,15 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -117,7 +117,7 @@ open class CloseImeOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) { @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt index 96b23bc01a79..0fe52df5bb25 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt index 48dbf25cb5b5..a4e4b6f40867 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt @@ -17,15 +17,15 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -96,7 +96,7 @@ open class CloseImeShownOnAppStartOnGoHomeTest(flicker: FlickerTest) : BaseTest( fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( // b/190352379 (IME doesn't show on app launch in 90 degrees) - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt index ed5d3096d039..5aacb3011e79 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt index 7b935ff344a7..e85da1f09772 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt @@ -17,15 +17,15 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -90,7 +90,7 @@ open class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: FlickerTest) : B fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( // b/190352379 (IME doesn't show on app launch in 90 degrees) - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt index 0a899914a35d..eb81aed35011 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt index 1a0c95958a29..1b4d6ad4422f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt @@ -19,15 +19,15 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt index 37e8c6baf677..db1440b0c5b8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index 0b7b165aaad6..e2d6dbf428f9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -19,16 +19,16 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -93,7 +93,7 @@ open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(f @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt index 116bc1bf6f20..405ab6bcd9d6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt index dcffa4785f6d..579c10f62f52 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt @@ -18,8 +18,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.legacy.FlickerTest fun FlickerTest.imeLayerBecomesVisible() { assertLayers { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt index defb43755841..3a8db45ab767 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt @@ -16,17 +16,18 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Postsubmit +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.flicker.subject.region.RegionSubject +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -107,10 +108,9 @@ class OpenImeWindowToFixedPortraitAppTest(flicker: FlickerTest) : BaseTest(flick return FlickerTestFactory.nonRotationTests( supportedRotations = listOf( - PlatformConsts.Rotation.ROTATION_90, + Rotation.ROTATION_90, ), - supportedNavigationModes = - listOf(PlatformConsts.NavBar.MODE_3BUTTON, PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_3BUTTON, NavBar.MODE_GESTURAL) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt index 89d37dbbb780..1fee20d7803a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt @@ -18,17 +18,17 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.snapshotStartingWindowLayerCoversExactlyOnApp -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -64,7 +64,7 @@ open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: Fl } transitions { // Bring the existing IME activity to the front in landscape mode device rotation. - setRotation(PlatformConsts.Rotation.ROTATION_90) + setRotation(Rotation.ROTATION_90) imeTestApp.launchViaIntent(wmHelper) } teardown { imeTestApp.exit(wmHelper) } @@ -99,7 +99,7 @@ open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: Fl @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_90) + supportedRotations = listOf(Rotation.ROTATION_90) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt index 307821f2d151..3aca2a07f7cd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt index c72e4e47c951..6179fc2aef21 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt @@ -17,17 +17,17 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.reopenAppFromOverview import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper -import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -142,7 +142,7 @@ open class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: FlickerTes @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt index 167689cd1c18..954f589ffa7f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt @@ -18,18 +18,19 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -133,8 +134,8 @@ open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: Flicker @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL), - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt index 82c38a31f128..0f57467c0449 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt @@ -17,8 +17,8 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt index 6c6003f8e64f..a927102f2a08 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestShellTransit.kt @@ -17,10 +17,10 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt index e7cfb9e7ab78..7514c9befe4f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt @@ -17,17 +17,17 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -123,7 +123,7 @@ open class ShowImeOnAppStartWhenLaunchingAppTest(flicker: FlickerTest) : BaseTes @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt index e7ecb87ada24..194c86be3207 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnStartWhenLaunchingAppCfArmTest.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt index 851651eea91c..d1335294f629 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt @@ -18,14 +18,14 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -81,7 +81,7 @@ open class ShowImeWhenFocusingOnInputFieldTest(flicker: FlickerTest) : BaseTest( @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt index 0c5315524314..f5b22949e6b4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt index 605821225c1f..99b9bd2bfc66 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt @@ -17,18 +17,18 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import android.view.WindowInsets.Type.ime import android.view.WindowInsets.Type.navigationBars import android.view.WindowInsets.Type.statusBars import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.FixMethodOrder @@ -91,7 +91,7 @@ class ShowImeWhileDismissingThemedPopupDialogTest(flicker: FlickerTest) : BaseTe @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt index 5d963467229f..9ea12a9e22a0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt @@ -17,19 +17,19 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.traces.ConditionsFactory +import android.tools.device.flicker.isShellTransitionsEnabled +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.WindowManagerConditionsFactory -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore @@ -82,7 +82,7 @@ open class ShowImeWhileEnteringOverviewTest(flicker: FlickerTest) : BaseTest(fli private fun waitNavStatusBarVisibility(stateSync: WindowManagerStateHelper.StateSyncBuilder) { when { flicker.scenario.isLandscapeOrSeascapeAtStart && !flicker.scenario.isTablet -> - stateSync.add(WindowManagerConditionsFactory.isStatusBarVisible().negate()) + stateSync.add(ConditionsFactory.isStatusBarVisible().negate()) else -> stateSync.withNavOrTaskBarVisible().withStatusBarVisible() } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt index 9308fbbc3f06..fc3971351db7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt @@ -16,8 +16,8 @@ package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt index 7979cf9b7ba5..e8f9aa3038ef 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt @@ -17,16 +17,16 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.parser.toFlickerComponent import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt index 549b929a6e81..8b89a8b4c40d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt @@ -16,9 +16,9 @@ package com.android.server.wm.flicker.launch -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt index 09422876bd9b..549183f407e2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt @@ -16,12 +16,12 @@ package com.android.server.wm.flicker.launch +import android.tools.device.apphelpers.CameraAppHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.helpers.CameraAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt index 9d86f8c8dc81..19ecf6ab8799 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt @@ -16,13 +16,14 @@ package com.android.server.wm.flicker.launch +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -61,7 +62,7 @@ class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition( if (flicker.scenario.isTablet) { tapl.setExpectedRotation(flicker.scenario.startRotation.value) } else { - tapl.setExpectedRotation(PlatformConsts.Rotation.ROTATION_0.value) + tapl.setExpectedRotation(Rotation.ROTATION_0.value) } RemoveAllTasksButHomeRule.removeAllTasksButHome() } @@ -87,7 +88,7 @@ class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition( fun getParams(): Collection<FlickerTest> { // TAPL fails on landscape mode b/240916028 return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_3BUTTON) + supportedNavigationModes = listOf(NavBar.MODE_3BUTTON) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index 9fbec973e93f..8fdbb6445bac 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -17,14 +17,14 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt index 2f0e56f1a276..1a1d4036579f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt @@ -17,9 +17,9 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerTest +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.legacy.FlickerTest import com.android.server.wm.flicker.replacesLayer -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.Test /** Base class for app launch tests */ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt index fb6fb22b0b0f..63ffee6fd77b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt @@ -21,12 +21,12 @@ import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.platform.test.rule.SettingOverrideRule import android.provider.Settings +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.ClassRule import org.junit.FixMethodOrder import org.junit.Ignore diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt index 32276d6f151b..a221ef6963c3 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt @@ -20,13 +20,13 @@ import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.platform.test.rule.SettingOverrideRule import android.provider.Settings +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.statusBarLayerPositionAtEnd -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.ClassRule import org.junit.FixMethodOrder import org.junit.Ignore diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt index ff39611fbbf2..4efee55b97b5 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt @@ -19,14 +19,14 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.wakeUpAndGoToHomeScreen import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -46,8 +46,7 @@ import org.junit.runners.Parameterized @Postsubmit class OpenAppFromLockNotificationWithLockOverlayApp(flicker: FlickerTest) : OpenAppFromLockNotificationCold(flicker) { - private val showWhenLockedApp: ShowWhenLockedAppHelper = - ShowWhenLockedAppHelper(instrumentation) + private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation) // Although we are technically still locked here, the overlay app means we should open the // notification shade as if we were unlocked. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt index aa054a9333f1..730f78ff3bc2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt @@ -18,11 +18,11 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import com.android.server.wm.flicker.navBarLayerPositionAtEnd import com.android.server.wm.flicker.statusBarLayerPositionAtEnd -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.Assume import org.junit.Ignore import org.junit.Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt index 0ed3bba8311e..9c16b7938e73 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt @@ -18,13 +18,13 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.statusBarLayerPositionAtEnd -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Ignore import org.junit.Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt index af6c81d454b7..4a9507aabf75 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt @@ -18,24 +18,24 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.wakeUpAndGoToHomeScreen import android.view.WindowInsets import android.view.WindowManager import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.NotificationAppHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd import com.android.server.wm.flicker.navBarLayerPositionAtEnd import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 2b16ef0de5a8..00d7544f7217 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -18,14 +18,14 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -74,7 +74,7 @@ open class OpenAppFromOverviewTest(flicker: FlickerTest) : OpenAppFromLauncherTr if (flicker.scenario.isTablet) { tapl.setExpectedRotation(flicker.scenario.startRotation.value) } else { - tapl.setExpectedRotation(PlatformConsts.Rotation.ROTATION_0.value) + tapl.setExpectedRotation(Rotation.ROTATION_0.value) } tapl.workspace.switchToOverview() wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt index 1c979e825793..ff24190a7aef 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt @@ -16,10 +16,10 @@ package com.android.server.wm.flicker.launch -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 55e7a9926a04..9ab61566e13f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -19,14 +19,15 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible import com.android.server.wm.flicker.helpers.NonResizeableAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore @@ -213,8 +214,8 @@ open class OpenAppNonResizeableTest(flicker: FlickerTest) : OpenAppFromLockTrans @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL), - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt index 618fb8ab76ea..e0db96f3b5c6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt @@ -17,14 +17,14 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.Test /** Base class for app launch tests */ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt index 93bf09995984..cdd2d45769bb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt @@ -18,13 +18,13 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt index 8d2af38c8898..6005a81aac9e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt @@ -17,16 +17,16 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit +import android.tools.device.apphelpers.CameraAppHelper +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import android.view.KeyEvent import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.annotation.FlickerServiceCompatible -import com.android.server.wm.flicker.helpers.CameraAppHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt index c78d0e9c5c5f..da985232a1e3 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt @@ -20,21 +20,20 @@ import android.app.Instrumentation import android.os.Bundle import android.os.Handler import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.traces.ConditionsFactory +import android.tools.device.flicker.junit.FlickerBuilderProvider +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.R -import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.junit.FlickerBuilderProvider -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.WindowManagerConditionsFactory +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.helpers.SimpleAppHelper import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -58,7 +57,7 @@ import org.junit.runners.Parameterized class OverrideTaskTransitionTest(val flicker: FlickerTest) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) + private val testApp = SimpleAppHelper(instrumentation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -75,7 +74,7 @@ class OverrideTaskTransitionTest(val flicker: FlickerTest) { ) wmHelper .StateSyncBuilder() - .add(WindowManagerConditionsFactory.isWMStateComplete()) + .add(ConditionsFactory.isWMStateComplete()) .withAppTransitionIdle() .withWindowSurfaceAppeared(testApp) .waitForAndVerify() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 94afd815921c..dd9e4cffcd30 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -21,23 +21,23 @@ import android.app.WallpaperManager import android.content.res.Resources import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.DEFAULT_TASK_DISPLAY_AREA +import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.SPLASH_SCREEN +import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER +import android.tools.common.datatypes.component.ComponentSplashScreenMatcher +import android.tools.common.datatypes.component.IComponentMatcher +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.NewTasksAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.DEFAULT_TASK_DISPLAY_AREA -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.SPLASH_SCREEN -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER -import com.android.server.wm.traces.common.component.matchers.ComponentSplashScreenMatcher -import com.android.server.wm.traces.common.component.matchers.IComponentMatcher -import com.android.server.wm.traces.parser.toFlickerComponent import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt index b7faf8325fc2..78cee3c4e71c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -17,18 +17,18 @@ package com.android.server.wm.flicker.quickswitch import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.common.datatypes.Rect +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.Rect -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -237,7 +237,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(flicker: FlickerTest) : BaseTest(fl @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt index e6cdd1efa798..f970a79abcb8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt @@ -16,11 +16,10 @@ package com.android.server.wm.flicker.quickswitch -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.Rect -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.NavBar +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -32,13 +31,11 @@ import org.junit.runners.Parameterized class QuickSwitchBetweenTwoAppsBackTestCfArm(flicker: FlickerTest) : QuickSwitchBetweenTwoAppsBackTest(flicker) { companion object { - private var startDisplayBounds = Rect.EMPTY - @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt index 25d9753b363f..2b69e9b7d258 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt @@ -17,10 +17,10 @@ package com.android.server.wm.flicker.quickswitch import android.platform.test.annotations.FlakyTest +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt index 6294761ba1bf..cd7d6fac0e9c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -17,18 +17,18 @@ package com.android.server.wm.flicker.quickswitch import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.common.datatypes.Rect +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.Rect -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder @@ -255,7 +255,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(flicker: FlickerTest) : BaseTest @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt index aa9adf0116ae..9f48cdae20f1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt @@ -16,11 +16,10 @@ package com.android.server.wm.flicker.quickswitch -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.Rect -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.NavBar +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -32,13 +31,11 @@ import org.junit.runners.Parameterized class QuickSwitchBetweenTwoAppsForwardTestCfArm(flicker: FlickerTest) : QuickSwitchBetweenTwoAppsForwardTest(flicker) { companion object { - private var startDisplayBounds = Rect.EMPTY - @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL) + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt index b40ecac2d19a..b0d4e2753758 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt @@ -17,10 +17,10 @@ package com.android.server.wm.flicker.quickswitch import android.platform.test.annotations.FlakyTest +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt index c03cd2954e25..63299cb6cd7a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -18,17 +18,18 @@ package com.android.server.wm.flicker.quickswitch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.Rect +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.isShellTransitionsEnabled +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.Rect -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher -import com.android.server.wm.traces.common.service.PlatformConsts import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore @@ -283,9 +284,9 @@ open class QuickSwitchFromLauncherTest(flicker: FlickerTest) : BaseTest(flicker) @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL), + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), // TODO: Test with 90 rotation - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt index 8b216035f9f8..af671df194ae 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt @@ -16,10 +16,11 @@ package com.android.server.wm.flicker.quickswitch -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.service.PlatformConsts +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -35,9 +36,9 @@ open class QuickSwitchFromLauncherTestCfArm(flicker: FlickerTest) : @JvmStatic fun getParams(): Collection<FlickerTest> { return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL), + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), // TODO: Test with 90 rotation - supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0) + supportedRotations = listOf(Rotation.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index e7e39c69f5fb..4a4180b6bbff 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -18,13 +18,13 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt index 6420f79a01f1..0e6b20fc21c6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt @@ -16,9 +16,9 @@ package com.android.server.wm.flicker.rotation -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index 74ecddeb359a..3c0bbd6c4c1c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -17,12 +17,12 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest import com.android.server.wm.flicker.BaseTest -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.Test /** Base class for app rotation tests */ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index 1a69344ded40..17b3b2b97e4b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -18,16 +18,16 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit +import android.tools.common.ScenarioBuilder +import android.tools.common.datatypes.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import android.view.WindowManager import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerBuilder -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.ScenarioBuilder import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher import org.junit.FixMethodOrder import org.junit.Ignore import org.junit.Test @@ -108,8 +108,10 @@ open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(fl fun appWindowFullScreen() { flicker.assertWm { this.invoke("isFullScreen") { - val appWindow = it.windowState(testApp.`package`) - val flags = appWindow.windowState?.attributes?.flags ?: 0 + val appWindow = + it.windowState(testApp.`package`) + ?: error("App window for package ${testApp.`package`} not found") + val flags = appWindow.windowState.attributes.flags appWindow .check { "isFullScreen" } .that(flags.and(WindowManager.LayoutParams.FLAG_FULLSCREEN)) @@ -124,8 +126,10 @@ open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(fl fun appWindowSeamlessRotation() { flicker.assertWm { this.invoke("isRotationSeamless") { - val appWindow = it.windowState(testApp.`package`) - val rotationAnimation = appWindow.windowState?.attributes?.rotationAnimation ?: 0 + val appWindow = + it.windowState(testApp.`package`) + ?: error("App window for package ${testApp.`package`} not found") + val rotationAnimation = appWindow.windowState.attributes.rotationAnimation appWindow .check { "isRotationSeamless" } .that( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt index 0ebbf4eb6eae..b236d87616ab 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt @@ -16,9 +16,9 @@ package com.android.server.wm.flicker.rotation -import com.android.server.wm.flicker.FlickerTest -import com.android.server.wm.flicker.FlickerTestFactory -import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import com.android.server.wm.flicker.testapp.ActivityOptions import org.junit.FixMethodOrder import org.junit.runner.RunWith diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp index 9b1262dd193b..75e35ee9c765 100644 --- a/tests/FlickerTests/test-apps/flickerapp/Android.bp +++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp @@ -41,6 +41,7 @@ android_test { "kotlin-stdlib", "kotlinx-coroutines-android", "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", "wm-flicker-window-extensions", ], } diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java index c7e5a5ea3311..e59071bd1d88 100644 --- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java +++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java @@ -29,6 +29,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Debug.MemoryInfo; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.test.InstrumentationTestCase; @@ -40,6 +41,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; /** * This test is intended to measure the amount of memory applications use when @@ -313,17 +315,19 @@ public class MemoryUsageTest extends InstrumentationTestCase { public void run() { try { - String mimeType = mLaunchIntent.getType(); - if (mimeType == null && mLaunchIntent.getData() != null + AtomicReference<String> mimeType = new AtomicReference<>(mLaunchIntent.getType()); + if (mimeType.get() == null && mLaunchIntent.getData() != null && "content".equals(mLaunchIntent.getData().getScheme())) { - mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(), - UserHandle.USER_CURRENT); + mAm.getMimeTypeFilterAsync(mLaunchIntent.getData(), UserHandle.USER_CURRENT, + new RemoteCallback(result -> { + mimeType.set(result.getPairValue()); + })); } mAtm.startActivityAndWait(null, getInstrumentation().getContext().getBasePackageName(), getInstrumentation().getContext().getAttributionTag(), mLaunchIntent, - mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null, + mimeType.get(), null, null, 0, mLaunchIntent.getFlags(), null, null, UserHandle.USER_CURRENT_OR_SELF); } catch (RemoteException e) { Log.w(TAG, "Error launching app", e); diff --git a/tests/MidiTests/Android.bp b/tests/MidiTests/Android.bp new file mode 100644 index 000000000000..254770d21818 --- /dev/null +++ b/tests/MidiTests/Android.bp @@ -0,0 +1,40 @@ +// +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "MidiTests", + srcs: ["**/*.java"], + static_libs: [ + "androidx.test.rules", + "mockito-target-inline-minus-junit4", + "platform-test-annotations", + "services.midi", + "truth-prebuilt", + ], + jni_libs: ["libdexmakerjvmtiagent"], + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], +} diff --git a/tests/MidiTests/AndroidManifest.xml b/tests/MidiTests/AndroidManifest.xml new file mode 100644 index 000000000000..0ee1b4493764 --- /dev/null +++ b/tests/MidiTests/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.midi" > + + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.midi" + android:label="MidiTests"/> +</manifest> diff --git a/tests/MidiTests/AndroidTest.xml b/tests/MidiTests/AndroidTest.xml new file mode 100644 index 000000000000..9320f0aac090 --- /dev/null +++ b/tests/MidiTests/AndroidTest.xml @@ -0,0 +1,30 @@ +<!-- 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. +--> +<configuration description="Runs sample instrumentation test."> + <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="MidiTests.apk"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/> + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="MidiTests"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.server.midi"/> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/tests/MidiTests/OWNERS b/tests/MidiTests/OWNERS new file mode 100644 index 000000000000..af273a6f50e0 --- /dev/null +++ b/tests/MidiTests/OWNERS @@ -0,0 +1 @@ +include /services/midi/OWNERS diff --git a/tests/MidiTests/TEST_MAPPING b/tests/MidiTests/TEST_MAPPING new file mode 100644 index 000000000000..60416a8ab3f9 --- /dev/null +++ b/tests/MidiTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "MidiTests" + } + ] +} diff --git a/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java b/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java new file mode 100644 index 000000000000..1659cc07f021 --- /dev/null +++ b/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.midi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.midi.MidiEventMultiScheduler; +import com.android.internal.midi.MidiEventScheduler; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Random; + +/** + * Unit tests for com.android.internal.midi.MidiEventMultiScheduler. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class MidiEventMultiSchedulerTest { + private byte[] generateRandomByteStream(Random rnd, int size) { + byte[] output = new byte[size]; + rnd.nextBytes(output); + return output; + } + + private void compareByteArrays(byte[] expectedArray, byte[] outputArray) { + assertEquals(expectedArray.length, outputArray.length); + for (int i = 0; i < outputArray.length; i++) { + assertEquals(expectedArray[i], outputArray[i]); + } + } + + private long timeFromNow(long milliseconds) { + return System.nanoTime() + 1000000L * milliseconds; + } + + @Test + public void testMultiScheduler() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0, (byte) 0xf7}, + 0, 2, timeFromNow(100))); + scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1, (byte) 0xf2}, + 0, 2, timeFromNow(200))); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf3, (byte) 0xf4}, + 0, 2, timeFromNow(300))); + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf5, (byte) 0xf6}, + 0, 2, timeFromNow(400))); + assertTrue(multiScheduler.waitNextEvent()); + assertNotNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + assertNotNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNotNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNotNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerLargeData() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + + Random rnd = new Random(42); + + final int arraySize = 1000; + byte[] expectedArray = generateRandomByteStream(rnd, arraySize); + + scheduler0.add(scheduler0.createScheduledEvent(expectedArray, 0, arraySize, + timeFromNow(100))); + assertTrue(multiScheduler.waitNextEvent()); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + compareByteArrays(expectedArray, event.data); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerClose() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + scheduler0.close(); + // After all schedulers are closed, waitNextEvent() should return false. + assertFalse(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerMultiClose() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + multiScheduler.close(); + // After all schedulers are closed, waitNextEvent() should return false. + assertFalse(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerNoPreemptiveClose() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + scheduler0.close(); + scheduler1.close(); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf5, (byte) 0xf6}, + 0, 2, timeFromNow(100))); + assertTrue(multiScheduler.waitNextEvent()); + scheduler2.close(); + // After all schedulers are closed, waitNextEvent() should return false. + assertFalse(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerSpamEvents() { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + // Create a msg with size 1 + byte[] msg = new byte[1]; + for (int i = 0; i < 1000; i++) { + msg[0] = (byte) i; + scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0))); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals(msg[0], event.data[0]); + } + assertNull(scheduler0.getNextEvent(System.nanoTime())); + } + + @Test + public void testSchedulerSpamEventsPullLater() { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + // Create a msg with size 1 + byte[] msg = new byte[1]; + for (int i = 0; i < 1000; i++) { + msg[0] = (byte) i; + scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0))); + } + + for (int i = 0; i < 1000; i++) { + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) i, event.data[0]); + } + assertNull(scheduler0.getNextEvent(System.nanoTime())); + } + + @Test + public void testSchedulerSpamEventsCallbackLater() { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + // Create a msg with size 1 + byte[] msg = new byte[1]; + for (int i = 0; i < 1000; i++) { + msg[0] = (byte) i; + scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0))); + } + + for (int i = 0; i < 1000; i++) { + try { + assertTrue(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + } + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) i, event.data[0]); + } + assertNull(scheduler0.getNextEvent(System.nanoTime())); + } + + @Test + public void testMultiSchedulerOutOfOrder() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf3}, + 0, 1, + timeFromNow(400))); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf2}, + 0, 1, + timeFromNow(300))); + scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1}, + 0, 1, + timeFromNow(200))); + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0}, + 0, 1, + timeFromNow(100))); + + assertTrue(multiScheduler.waitNextEvent()); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf0, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + event = (MidiEventScheduler.MidiEvent) scheduler1.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf1, event.data[0]); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + event = (MidiEventScheduler.MidiEvent) scheduler2.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf2, event.data[0]); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf3, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testMultiSchedulerOutOfOrderNegativeTime() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf3}, + 0, 1, + timeFromNow(-100))); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf2}, + 0, 1, + timeFromNow(-200))); + scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1}, + 0, 1, + timeFromNow(-300))); + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0}, + 0, 1, + timeFromNow(-400))); + + assertTrue(multiScheduler.waitNextEvent()); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf0, event.data[0]); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler1.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf1, event.data[0]); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler2.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf2, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf3, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + } catch (InterruptedException ex) { + + } + } +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt index 7a8d9490b540..97398dc4e334 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt @@ -15,7 +15,7 @@ */ package com.android.test -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject +import android.tools.common.flicker.subject.layers.LayersTraceSubject import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue import org.junit.Test @@ -170,4 +170,4 @@ class BufferPresentationTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase assertTrue(failures) } } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt index da53387c935c..0cc18d657cf5 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt @@ -16,10 +16,11 @@ package com.android.test import android.graphics.Point -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject +import android.tools.common.flicker.subject.layers.LayersTraceSubject import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode import com.android.test.SurfaceViewBufferTestBase.Companion.Transform import junit.framework.Assert.assertEquals +import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -45,8 +46,9 @@ class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(us activity.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */) } // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE - LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist() - + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } // Verify the next buffer is submitted with the correct size LayersTraceSubject(trace).layer("SurfaceView", 3).also { it.hasBufferSize(defaultBufferSize) @@ -82,7 +84,9 @@ class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(us // verify buffer size is reset to default buffer size LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) - LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist() + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize) } @@ -110,7 +114,9 @@ class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(us // verify buffer size is reset to default buffer size LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) - LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist() + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize) LayersTraceSubject(trace).layer("SurfaceView", 3) .hasBufferOrientation(Transform.ROT_90.value) @@ -144,10 +150,11 @@ class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(us for (count in 0 until 5) { LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 1L) .hasBufferSize(defaultBufferSize) - LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 2L) - .doesNotExist() + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 2L) + } LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 3L) .hasBufferSize(bufferSize) } } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt index 2d6c664cca02..6f4d11c3aa1b 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt @@ -19,11 +19,12 @@ import android.graphics.Color import android.graphics.Point import android.graphics.Rect import android.os.SystemClock -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject +import android.tools.common.flicker.subject.layers.LayersTraceSubject import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode import com.android.test.SurfaceViewBufferTestBase.Companion.Transform import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue +import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -103,7 +104,9 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA // verify buffer size is reset to default buffer size LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) - LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist() + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize) } @@ -221,4 +224,4 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA it.hasBufferSize(defaultBufferSize) } } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt index cf4186d84e2d..e722ba537a8e 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt @@ -16,9 +16,10 @@ package com.android.test import android.graphics.Point -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject +import android.tools.common.flicker.subject.layers.LayersTraceSubject import com.android.test.SurfaceViewBufferTestBase.Companion.Transform import junit.framework.Assert.assertEquals +import org.junit.Assert import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.Test @@ -70,7 +71,9 @@ class InverseDisplayTransformTests(useBlastAdapter: Boolean) : // verify buffer size is reset to default buffer size LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) - LayersTraceSubject(trace).layer("SurfaceView", 2).doesNotExist() + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize) } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt index 61d4095c7cf3..be3ed715d4e2 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt @@ -17,7 +17,7 @@ package com.android.test import android.graphics.Color import android.graphics.Rect -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject +import android.tools.common.flicker.subject.layers.LayersTraceSubject import junit.framework.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -87,4 +87,4 @@ class SharedBufferModeTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(u checkPixels(svBounds, Color.BLUE) } } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt index 6383da5a0a98..cf4cb8c97ea1 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt @@ -21,9 +21,11 @@ import android.graphics.Color import android.graphics.Rect import android.util.Log import androidx.test.ext.junit.rules.ActivityScenarioRule -import com.android.server.wm.flicker.monitor.LayersTraceMonitor -import com.android.server.wm.flicker.monitor.withSFTracing -import com.android.server.wm.traces.common.layers.LayersTrace +import android.tools.common.flicker.subject.layers.LayerSubject +import android.tools.common.traces.surfaceflinger.LayersTrace +import android.tools.device.traces.io.ResultWriter +import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor +import android.tools.device.traces.monitors.withSFTracing import junit.framework.Assert import org.junit.After import org.junit.Before @@ -52,8 +54,7 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : } fun withTrace(predicate: (it: MainActivity) -> Unit): LayersTrace { - return withSFTracing(TRACE_FLAGS, - outputDir = instrumentation.targetContext.dataDir.toPath()) { + return withSFTracing(TRACE_FLAGS) { scenarioRule.getScenario().onActivity { predicate(it) } @@ -61,8 +62,7 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : } fun withTrace(predicate: () -> Unit): LayersTrace { - return withSFTracing(TRACE_FLAGS, - outputDir = instrumentation.targetContext.dataDir.toPath()) { + return withSFTracing(TRACE_FLAGS) { predicate() } } @@ -84,8 +84,7 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : } private fun stopLayerTrace() { - val tmpDir = instrumentation.targetContext.dataDir.toPath() - LayersTraceMonitor(tmpDir).stop() + LayersTraceMonitor().stop(ResultWriter()) } fun checkPixels(bounds: Rect, @ColorInt color: Int) { @@ -117,4 +116,4 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : private const val TRACE_FLAGS = (1 shl 0) or (1 shl 5) or (1 shl 6) // TRACE_CRITICAL | TRACE_BUFFERS | TRACE_SYNC } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt index 093c3125f253..bba967815ba5 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt @@ -18,6 +18,8 @@ package com.android.test import android.app.Instrumentation import android.graphics.Point import android.provider.Settings +import android.tools.common.datatypes.Size +import android.tools.common.flicker.subject.layers.LayerSubject import androidx.test.InstrumentationRegistry import org.junit.After import org.junit.Before @@ -69,6 +71,10 @@ open class SurfaceViewBufferTestBase(val useBlastAdapter: Boolean) { const val R8G8B8A8_UNORM = 1 val defaultBufferSize = Point(640, 480) + fun LayerSubject.hasBufferSize(point: Point) = hasBufferSize(Size.from(point.x, point.y)) + + fun LayerSubject.hasLayerSize(point: Point) = hasLayerSize(Size.from(point.x, point.y)) + // system/window.h definitions enum class ScalingMode() { FREEZE, // = 0 @@ -94,4 +100,4 @@ open class SurfaceViewBufferTestBase(val useBlastAdapter: Boolean) { INVERSE_DISPLAY(0x08) } } -}
\ No newline at end of file +} diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt index 722e671266e1..6f4f7b13af66 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt @@ -16,14 +16,15 @@ package com.android.test.taskembed import android.app.Instrumentation -import android.graphics.Point import android.graphics.Rect import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 -import com.android.server.wm.flicker.monitor.LayersTraceMonitor -import com.android.server.wm.flicker.monitor.withSFTracing -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject +import android.tools.common.datatypes.Size +import android.tools.common.flicker.subject.layers.LayersTraceSubject +import android.tools.device.traces.io.ResultWriter +import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor +import android.tools.device.traces.monitors.withSFTracing import org.junit.After import org.junit.Before import org.junit.FixMethodOrder @@ -46,8 +47,10 @@ class ResizeTasksSyncTest { @Before fun setup() { - val tmpDir = instrumentation.targetContext.dataDir.toPath() - LayersTraceMonitor(tmpDir).stop() + val monitor = LayersTraceMonitor() + if (monitor.isEnabled) { + monitor.stop(ResultWriter()) + } val firstTaskBounds = Rect(0, 0, 1080, 1000) val secondTaskBounds = Rect(0, 1000, 1080, 2000) @@ -68,8 +71,7 @@ class ResizeTasksSyncTest { val firstBounds = Rect(0, 0, 1080, 800) val secondBounds = Rect(0, 1000, 1080, 1800) - val trace = withSFTracing(TRACE_FLAGS, - outputDir = instrumentation.targetContext.dataDir.toPath()) { + val trace = withSFTracing(TRACE_FLAGS) { lateinit var resizeReadyLatch: CountDownLatch scenarioRule.getScenario().onActivity { resizeReadyLatch = it.resizeTaskView(firstBounds, secondBounds) @@ -91,13 +93,13 @@ class ResizeTasksSyncTest { // verify buffer size should be changed to expected values. LayersTraceSubject(trace).layer(FIRST_ACTIVITY, frame.toLong()).also { - val firstTaskSize = Point(firstBounds.width(), firstBounds.height()) + val firstTaskSize = Size.from(firstBounds.width(), firstBounds.height()) it.hasLayerSize(firstTaskSize) it.hasBufferSize(firstTaskSize) } LayersTraceSubject(trace).layer(SECOND_ACTIVITY, frame.toLong()).also { - val secondTaskSize = Point(secondBounds.width(), secondBounds.height()) + val secondTaskSize = Size.from(secondBounds.width(), secondBounds.height()) it.hasLayerSize(secondTaskSize) it.hasBufferSize(secondTaskSize) } @@ -108,4 +110,4 @@ class ResizeTasksSyncTest { private const val FIRST_ACTIVITY = "Activity1" private const val SECOND_ACTIVITY = "Activity2" } -}
\ No newline at end of file +} diff --git a/tests/UsbTests/src/com/android/server/usb/UsbMidiPacketConverterTest.java b/tests/UsbTests/src/com/android/server/usb/UsbMidiPacketConverterTest.java new file mode 100644 index 000000000000..ad701e5117fc --- /dev/null +++ b/tests/UsbTests/src/com/android/server/usb/UsbMidiPacketConverterTest.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.usb; + +import static org.junit.Assert.assertEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.Random; + +/** + * Unit tests for com.android.server.usb.UsbMidiPacketConverter. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class UsbMidiPacketConverterTest { + private byte[] generateRandomByteStream(Random rnd, int size) { + byte[] output = new byte[size]; + rnd.nextBytes(output); + return output; + } + + private void compareByteArrays(byte[] expectedArray, byte[] outputArray) { + assertEquals(expectedArray.length, outputArray.length); + for (int i = 0; i < outputArray.length; i++) { + assertEquals(expectedArray[i], outputArray[i]); + } + } + + @Test + public void testDecoderSinglePacket() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(2); + byte[] input = new byte[] {0x19 /* Cable 1 Note-On */, (byte) 0x91, 0x33, 0x66}; + byte[] expectedOutputCable0 = new byte[] {}; + byte[] expectedOutputCable1 = new byte[] {(byte) 0x91, 0x33, 0x66}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + compareByteArrays(expectedOutputCable1, actualOutputCable1); + } + + @Test + public void testDecoderMultiplePackets() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(4); + byte[] input = new byte[] { + 0x1B /* Cable 1 Control Change */, (byte) 0xB4, 0x55, 0x6E, + 0x35 /* Cable 3 Single byte SysEx */, (byte) 0xF8, 0x00, 0x00, + 0x02 /* Cable 0 Two byte System Common */, (byte) 0xF3, 0x12, 0x00}; + byte[] expectedOutputCable0 = new byte[] {(byte) 0xF3, 0x12}; + byte[] expectedOutputCable1 = new byte[] {(byte) 0xB4, 0x55, 0x6E}; + byte[] expectedOutputCable2 = new byte[] {}; + byte[] expectedOutputCable3 = new byte[] {(byte) 0xF8}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1); + byte[] actualOutputCable2 = usbMidiPacketConverter.pullDecodedMidiPackets(2); + byte[] actualOutputCable3 = usbMidiPacketConverter.pullDecodedMidiPackets(3); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + compareByteArrays(expectedOutputCable1, actualOutputCable1); + compareByteArrays(expectedOutputCable2, actualOutputCable2); + compareByteArrays(expectedOutputCable3, actualOutputCable3); + } + + @Test + public void testDecoderSysExEndFirstByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(2); + byte[] input = new byte[] { + 0x14 /* Cable 1 SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x15 /* Cable 1 Single byte SysEx End */, (byte) 0xF7, 0x00, 0x00}; + byte[] expectedOutputCable0 = new byte[] {}; + byte[] expectedOutputCable1 = new byte[] { + (byte) 0xF0, 0x00, 0x01, + (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + compareByteArrays(expectedOutputCable1, actualOutputCable1); + } + + @Test + public void testDecoderSysExEndSecondByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(1); + byte[] input = new byte[] { + 0x04 /* Cable 0 SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x06 /* Cable 0 Two byte SysEx End */, 0x02, (byte) 0xF7, 0x00}; + byte[] expectedOutputCable0 = new byte[] { + (byte) 0xF0, 0x00, 0x01, + 0x02, (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderSysExEndThirdByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + byte[] input = new byte[] { + 0x04 /* Cable 0 SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x07 /* Cable 0 Three byte SysEx End */, 0x02, 0x03, (byte) 0xF7}; + usbMidiPacketConverter.createDecoders(1); + byte[] expectedOutputCable0 = new byte[] { + (byte) 0xF0, 0x00, 0x01, + 0x02, 0x03, (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderSysExStartEnd() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + byte[] input = new byte[] { + 0x06 /* Cable 0 Two byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.createDecoders(1); + byte[] expectedOutputCable0 = new byte[] { + (byte) 0xF0, (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderSysExStartByteEnd() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + byte[] input = new byte[] { + 0x07 /* Cable 0 Three byte SysEx End */, (byte) 0xF0, 0x44, (byte) 0xF7}; + usbMidiPacketConverter.createDecoders(1); + byte[] expectedOutputCable0 = new byte[] { + (byte) 0xF0, 0x44, (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderDefaultToFirstCable() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + byte[] input = new byte[] {0x49 /* Cable 4 Note-On */, (byte) 0x91, 0x22, 0x33}; + usbMidiPacketConverter.createDecoders(1); + byte[] expectedOutputCable0 = new byte[] { + (byte) 0x91, 0x22, 0x33}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderLargePacketDoesNotCrash() { + for (long seed = 1001; seed < 5000; seed += 777) { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(3); + Random rnd = new Random(seed); + byte[] input = generateRandomByteStream(rnd, 1003 /* arbitrary large size */); + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + usbMidiPacketConverter.pullDecodedMidiPackets(0); + usbMidiPacketConverter.pullDecodedMidiPackets(1); + usbMidiPacketConverter.pullDecodedMidiPackets(2); + } + } + + @Test + public void testEncoderBasic() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] {(byte) 0x91 /* Note-On */, 0x33, 0x66}; + byte[] expectedOutput = new byte[] { + 0x09 /* Cable 0 Note-On */, (byte) 0x91, 0x33, 0x66}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderMultiplePackets() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(3); + byte[] inputCable2 = new byte[] { + (byte) 0xB4 /* Control Change */, 0x55, 0x6E}; + byte[] inputCable1 = new byte[] { + (byte) 0xF8 /* Timing Clock (Single Byte) */, + (byte) 0xF3 /* Song Select (Two Bytes) */, 0x12}; + byte[] expectedOutput = new byte[] { + 0x2B /* Cable 2 Control Change */, (byte) 0xB4, 0x55, 0x6E, + 0x15 /* Cable 1 Timing Clock */, (byte) 0xF8, 0x00, 0x00, + 0x12 /* Cable 1 Two Byte System Common */, (byte) 0xF3, 0x12, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(inputCable2, inputCable2.length, 2); + usbMidiPacketConverter.encodeMidiPackets(inputCable1, inputCable1.length, 1); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderWeavePackets() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(2); + byte[] inputCable1Msg1 = new byte[] { + (byte) 0x93 /* Note-On */, 0x23, 0x43}; + byte[] inputCable0Msg = new byte[] { + (byte) 0xB4 /* Control Change */, 0x65, 0x26}; + byte[] inputCable1Msg2 = new byte[] { + (byte) 0xA4 /* Poly-KeyPress */, 0x52, 0x76}; + byte[] expectedOutput = new byte[] { + 0x19 /* Cable 1 Note-On */, (byte) 0x93, 0x23, 0x43, + 0x0B /* Cable 0 Control Change */, (byte) 0xB4, 0x65, 0x26, + 0x1A /* Cable 1 Poly-KeyPress */, (byte) 0xA4, 0x52, 0x76}; + usbMidiPacketConverter.encodeMidiPackets(inputCable1Msg1, inputCable1Msg1.length, 1); + usbMidiPacketConverter.encodeMidiPackets(inputCable0Msg, inputCable0Msg.length, 0); + usbMidiPacketConverter.encodeMidiPackets(inputCable1Msg2, inputCable1Msg2.length, 1); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExEndFirstByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x00, 0x01, + (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x05 /* Cable 0 One Byte SysEx End */, (byte) 0xF7, 0x00, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExEndSecondByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x00, 0x01, + 0x02, (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x06 /* Cable 0 Two Byte SysEx End */, 0x02, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExEndThirdByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x00, 0x01, + 0x02, 0x03, (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x07 /* Cable 0 Three Byte SysEx End */, 0x02, 0x03, (byte) 0xF7}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExStartEnd() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x06 /* Cable 0 Two Byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExStartByteEnd() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x44, (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x07 /* Cable 0 Three Byte SysEx End */, (byte) 0xF0, 0x44, (byte) 0xF7}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderMultiplePulls() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x44, 0x55, + 0x66, 0x77}; // 0x66 and 0x77 will not be pulled the first time + byte[] expectedOutput = new byte[] { + 0x04 /* SysEx Start */, (byte) 0xF0, 0x44, 0x55}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + + input = new byte[] { + 0x11, // Combined with 0x66 and 0x77 above + 0x22, (byte) 0xF7 /* SysEx End */}; + expectedOutput = new byte[] { + 0x04 /* Cable 0 SysEx Continue */, 0x66, 0x77, 0x11, + 0x06 /* Cable 0 Two Byte SysEx End */, 0x22, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + + input = new byte[] { + (byte) 0xF0 /* SysEx Start */, (byte) 0xF7 /* SysEx End */}; + expectedOutput = new byte[] { + 0x06 /* Cable 0 Two Byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderDefaultToFirstCable() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(2); + byte[] input = new byte[] {(byte) 0x91 /* Note-On */, 0x22, 0x33}; + byte[] expectedOutput = new byte[] { + 0x09 /* Cable 0 Note-On */, (byte) 0x91, 0x22, 0x33}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 4); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderLargePacketDoesNotCrash() { + for (long seed = 234; seed < 4000; seed += 666) { + Random rnd = new Random(seed); + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(4); + for (int cableNumber = 0; cableNumber < 4; cableNumber++) { + byte[] input = generateRandomByteStream(rnd, 1003 /* arbitrary large size */); + usbMidiPacketConverter.encodeMidiPackets(input, input.length, cableNumber); + } + usbMidiPacketConverter.pullEncodedMidiPackets(); + } + } + + @Test + public void testEncodeDecode() { + final int bufferSize = 30; + final int numCables = 16; + final int bytesToEncodePerEncoding = 10; + byte[][] rawMidi = new byte[numCables][bufferSize]; + for (long seed = 45; seed < 3000; seed += 300) { + Random rnd = new Random(seed); + for (int cableNumber = 0; cableNumber < numCables; cableNumber++) { + rawMidi[cableNumber] = generateRandomByteStream(rnd, bufferSize); + + // Change the last byte to SysEx End. + // This way the encoder is guaranteed to flush all packets. + rawMidi[cableNumber][bufferSize - 1] = (byte) 0xF7; + } + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(numCables); + // Encode packets and interweave them + for (int startByte = 0; startByte < bufferSize; + startByte += bytesToEncodePerEncoding) { + for (int cableNumber = 0; cableNumber < numCables; cableNumber++) { + byte[] bytesToEncode = Arrays.copyOfRange(rawMidi[cableNumber], startByte, + startByte + bytesToEncodePerEncoding); + usbMidiPacketConverter.encodeMidiPackets(bytesToEncode, bytesToEncode.length, + cableNumber); + } + } + byte[] usbMidi = usbMidiPacketConverter.pullEncodedMidiPackets(); + + usbMidiPacketConverter.createDecoders(numCables); + + // Now decode the MIDI packets to check if they are the same as the original + usbMidiPacketConverter.decodeMidiPackets(usbMidi, usbMidi.length); + for (int cableNumber = 0; cableNumber < numCables; cableNumber++) { + byte[] decodedRawMidi = usbMidiPacketConverter.pullDecodedMidiPackets(cableNumber); + compareByteArrays(rawMidi[cableNumber], decodedRawMidi); + } + } + } +} diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java index 161c83ce2fab..1fb1c630304d 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java @@ -24,11 +24,12 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.ArraySet; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; import java.util.Objects; +import java.util.Set; /** * A data class representing a known Wi-Fi network. @@ -59,7 +60,7 @@ public final class KnownNetwork implements Parcelable { @NetworkSource private final int mNetworkSource; private final String mSsid; - @SecurityType private final int[] mSecurityTypes; + @SecurityType private final ArraySet<Integer> mSecurityTypes; private final DeviceInfo mDeviceInfo; /** @@ -68,11 +69,9 @@ public final class KnownNetwork implements Parcelable { public static final class Builder { @NetworkSource private int mNetworkSource = -1; private String mSsid; - @SecurityType private int[] mSecurityTypes; + @SecurityType private final ArraySet<Integer> mSecurityTypes = new ArraySet<>(); private android.net.wifi.sharedconnectivity.app.DeviceInfo mDeviceInfo; - public Builder() {} - /** * Sets the indicated source of the known network. * @@ -98,14 +97,14 @@ public final class KnownNetwork implements Parcelable { } /** - * Sets the security types of the known network. + * Adds a security type of the known network. * - * @param securityTypes The array of security types supported by the known network. + * @param securityType A security type supported by the known network. * @return Returns the Builder object. */ @NonNull - public Builder setSecurityTypes(@NonNull @SecurityType int[] securityTypes) { - mSecurityTypes = securityTypes; + public Builder addSecurityType(@SecurityType int securityType) { + mSecurityTypes.add(securityType); return this; } @@ -136,7 +135,7 @@ public final class KnownNetwork implements Parcelable { } } - private static void validate(int networkSource, String ssid, int [] securityTypes) { + private static void validate(int networkSource, String ssid, Set<Integer> securityTypes) { if (networkSource != NETWORK_SOURCE_CLOUD_SELF && networkSource != NETWORK_SOURCE_NEARBY_SELF) { throw new IllegalArgumentException("Illegal network source"); @@ -144,7 +143,7 @@ public final class KnownNetwork implements Parcelable { if (TextUtils.isEmpty(ssid)) { throw new IllegalArgumentException("SSID must be set"); } - if (securityTypes == null || securityTypes.length == 0) { + if (securityTypes.isEmpty()) { throw new IllegalArgumentException("SecurityTypes must be set"); } } @@ -152,12 +151,12 @@ public final class KnownNetwork implements Parcelable { private KnownNetwork( @NetworkSource int networkSource, @NonNull String ssid, - @NonNull @SecurityType int[] securityTypes, + @NonNull @SecurityType ArraySet<Integer> securityTypes, @NonNull DeviceInfo deviceInfo) { validate(networkSource, ssid, securityTypes); mNetworkSource = networkSource; mSsid = ssid; - mSecurityTypes = securityTypes; + mSecurityTypes = new ArraySet<>(securityTypes); mDeviceInfo = deviceInfo; } @@ -184,11 +183,11 @@ public final class KnownNetwork implements Parcelable { /** * Gets the security types of the known network. * - * @return Returns the array of security types supported by the known network. + * @return Returns a set with security types supported by the known network. */ @NonNull @SecurityType - public int[] getSecurityTypes() { + public Set<Integer> getSecurityTypes() { return mSecurityTypes; } @@ -208,14 +207,13 @@ public final class KnownNetwork implements Parcelable { KnownNetwork other = (KnownNetwork) obj; return mNetworkSource == other.getNetworkSource() && Objects.equals(mSsid, other.getSsid()) - && Arrays.equals(mSecurityTypes, other.getSecurityTypes()) + && Objects.equals(mSecurityTypes, other.getSecurityTypes()) && Objects.equals(mDeviceInfo, other.getDeviceInfo()); } @Override public int hashCode() { - return Objects.hash(mNetworkSource, mSsid, Arrays.hashCode(mSecurityTypes), - mDeviceInfo.hashCode()); + return Objects.hash(mNetworkSource, mSsid, mSecurityTypes, mDeviceInfo); } @Override @@ -227,7 +225,7 @@ public final class KnownNetwork implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mNetworkSource); dest.writeString(mSsid); - dest.writeIntArray(mSecurityTypes); + dest.writeArraySet(mSecurityTypes); mDeviceInfo.writeToParcel(dest, flags); } @@ -238,7 +236,8 @@ public final class KnownNetwork implements Parcelable { */ @NonNull public static KnownNetwork readFromParcel(@NonNull Parcel in) { - return new KnownNetwork(in.readInt(), in.readString(), in.createIntArray(), + return new KnownNetwork(in.readInt(), in.readString(), + (ArraySet<Integer>) in.readArraySet(null), DeviceInfo.readFromParcel(in)); } @@ -260,7 +259,7 @@ public final class KnownNetwork implements Parcelable { return new StringBuilder("KnownNetwork[") .append("NetworkSource=").append(mNetworkSource) .append(", ssid=").append(mSsid) - .append(", securityTypes=").append(Arrays.toString(mSecurityTypes)) + .append(", securityTypes=").append(mSecurityTypes.toString()) .append(", deviceInfo=").append(mDeviceInfo.toString()) .append("]").toString(); } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index d5d01d38ef62..a19510b3f9df 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -192,9 +192,9 @@ public class SharedConnectivityManager { mService = ISharedConnectivityService.Stub.asInterface(service); if (!mCallbackProxyCache.isEmpty()) { synchronized (mCallbackProxyCache) { - mCallbackProxyCache.keySet().forEach(callback -> { - registerCallbackInternal(callback, mCallbackProxyCache.get(callback)); - }); + mCallbackProxyCache.keySet().forEach(callback -> + registerCallbackInternal( + callback, mCallbackProxyCache.get(callback))); mCallbackProxyCache.clear(); } } @@ -258,7 +258,9 @@ public class SharedConnectivityManager { } /** - * Registers a callback for receiving updates to the list of Tether Networks and Known Networks. + * Registers a callback for receiving updates to the list of Tether Networks, Known Networks, + * shared connectivity settings state, tether network connection status and known network + * connection status. * The {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} will be called if the * registration failed. * @@ -418,4 +420,104 @@ public class SharedConnectivityManager { } return true; } + + /** + * Gets the list of tether networks the user can select to connect to. + * + * @return Returns a {@link List} of {@link TetherNetwork} objects, empty list on failure. + */ + @NonNull + public List<TetherNetwork> getTetherNetworks() { + if (mService == null) { + return List.of(); + } + + try { + return mService.getTetherNetworks(); + } catch (RemoteException e) { + Log.e(TAG, "Exception in getTetherNetworks", e); + } + return List.of(); + } + + /** + * Gets the list of known networks the user can select to connect to. + * + * @return Returns a {@link List} of {@link KnownNetwork} objects, empty list on failure. + */ + @NonNull + public List<KnownNetwork> getKnownNetworks() { + if (mService == null) { + return List.of(); + } + + try { + return mService.getKnownNetworks(); + } catch (RemoteException e) { + Log.e(TAG, "Exception in getKnownNetworks", e); + } + return List.of(); + } + + /** + * Gets the shared connectivity settings state. + * + * @return Returns a {@link SharedConnectivitySettingsState} object with the state, null on + * failure. + */ + @Nullable + public SharedConnectivitySettingsState getSettingsState() { + if (mService == null) { + return null; + } + + try { + return mService.getSettingsState(); + } catch (RemoteException e) { + Log.e(TAG, "Exception in getSettingsState", e); + } + return null; + } + + /** + * Gets the connection status of the tether network the user selected to connect to. + * + * @return Returns a {@link TetherNetworkConnectionStatus} object with the connection status, + * null on failure. If no connection is active the status will be + * {@link TetherNetworkConnectionStatus#CONNECTION_STATUS_UNKNOWN}. + */ + @Nullable + public TetherNetworkConnectionStatus getTetherNetworkConnectionStatus() { + if (mService == null) { + return null; + } + + try { + return mService.getTetherNetworkConnectionStatus(); + } catch (RemoteException e) { + Log.e(TAG, "Exception in getTetherNetworkConnectionStatus", e); + } + return null; + } + + /** + * Gets the connection status of the known network the user selected to connect to. + * + * @return Returns a {@link KnownNetworkConnectionStatus} object with the connection status, + * null on failure. If no connection is active the status will be + * {@link KnownNetworkConnectionStatus#CONNECTION_STATUS_UNKNOWN}. + */ + @Nullable + public KnownNetworkConnectionStatus getKnownNetworkConnectionStatus() { + if (mService == null) { + return null; + } + + try { + return mService.getKnownNetworkConnectionStatus(); + } catch (RemoteException e) { + Log.e(TAG, "Exception in getKnownNetworkConnectionStatus", e); + } + return null; + } } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java index af4fd4a2cc76..7b591d3a45bd 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/TetherNetwork.java @@ -25,12 +25,12 @@ import android.annotation.SystemApi; import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; import android.os.Parcel; import android.os.Parcelable; - +import android.util.ArraySet; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; import java.util.Objects; +import java.util.Set; /** * A data class representing an Instant Tether network. @@ -79,7 +79,7 @@ public final class TetherNetwork implements Parcelable { private final String mNetworkName; @Nullable private final String mHotspotSsid; @Nullable private final String mHotspotBssid; - @Nullable @SecurityType private final int[] mHotspotSecurityTypes; + @Nullable @SecurityType private final ArraySet<Integer> mHotspotSecurityTypes; /** * Builder class for {@link TetherNetwork}. @@ -91,9 +91,8 @@ public final class TetherNetwork implements Parcelable { private String mNetworkName; @Nullable private String mHotspotSsid; @Nullable private String mHotspotBssid; - @Nullable @SecurityType private int[] mHotspotSecurityTypes; - - public Builder() {} + @Nullable @SecurityType private final ArraySet<Integer> mHotspotSecurityTypes = + new ArraySet<>(); /** * Set the remote device ID. @@ -168,15 +167,14 @@ public final class TetherNetwork implements Parcelable { } /** - * Sets the hotspot security types supported by the remote device, or null if hotspot is - * off. + * Adds a security type supported by the hotspot created by the remote device. * - * @param hotspotSecurityTypes The array of security types supported by the hotspot. + * @param hotspotSecurityType A security type supported by the hotspot. * @return Returns the Builder object. */ @NonNull - public Builder setHotspotSecurityTypes(@NonNull @SecurityType int[] hotspotSecurityTypes) { - mHotspotSecurityTypes = hotspotSecurityTypes; + public Builder addHotspotSecurityType(@SecurityType int hotspotSecurityType) { + mHotspotSecurityTypes.add(hotspotSecurityType); return this; } @@ -218,7 +216,7 @@ public final class TetherNetwork implements Parcelable { @NonNull String networkName, @Nullable String hotspotSsid, @Nullable String hotspotBssid, - @Nullable @SecurityType int[] hotspotSecurityTypes) { + @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes) { validate(deviceId, networkType, networkName); @@ -228,7 +226,7 @@ public final class TetherNetwork implements Parcelable { mNetworkName = networkName; mHotspotSsid = hotspotSsid; mHotspotBssid = hotspotBssid; - mHotspotSecurityTypes = hotspotSecurityTypes; + mHotspotSecurityTypes = new ArraySet<>(hotspotSecurityTypes); } /** @@ -293,11 +291,11 @@ public final class TetherNetwork implements Parcelable { /** * Gets the hotspot security types supported by the remote device. * - * @return Returns the array of security types supported by the hotspot. + * @return Returns a set of the security types supported by the hotspot. */ - @Nullable + @NonNull @SecurityType - public int[] getHotspotSecurityTypes() { + public Set<Integer> getHotspotSecurityTypes() { return mHotspotSecurityTypes; } @@ -311,13 +309,13 @@ public final class TetherNetwork implements Parcelable { && Objects.equals(mNetworkName, other.getNetworkName()) && Objects.equals(mHotspotSsid, other.getHotspotSsid()) && Objects.equals(mHotspotBssid, other.getHotspotBssid()) - && Arrays.equals(mHotspotSecurityTypes, other.getHotspotSecurityTypes()); + && Objects.equals(mHotspotSecurityTypes, other.getHotspotSecurityTypes()); } @Override public int hashCode() { return Objects.hash(mDeviceId, mDeviceInfo, mNetworkName, mHotspotSsid, mHotspotBssid, - Arrays.hashCode(mHotspotSecurityTypes)); + mHotspotSecurityTypes); } @Override @@ -333,7 +331,7 @@ public final class TetherNetwork implements Parcelable { dest.writeString(mNetworkName); dest.writeString(mHotspotSsid); dest.writeString(mHotspotBssid); - dest.writeIntArray(mHotspotSecurityTypes); + dest.writeArraySet(mHotspotSecurityTypes); } /** @@ -345,7 +343,7 @@ public final class TetherNetwork implements Parcelable { public static TetherNetwork readFromParcel(@NonNull Parcel in) { return new TetherNetwork(in.readLong(), DeviceInfo.readFromParcel(in), in.readInt(), in.readString(), in.readString(), in.readString(), - in.createIntArray()); + (ArraySet<Integer>) in.readArraySet(null)); } @NonNull @@ -370,7 +368,7 @@ public final class TetherNetwork implements Parcelable { .append(", networkName=").append(mNetworkName) .append(", hotspotSsid=").append(mHotspotSsid) .append(", hotspotBssid=").append(mHotspotBssid) - .append(", hotspotSecurityTypes=").append(Arrays.toString(mHotspotSecurityTypes)) + .append(", hotspotSecurityTypes=").append(mHotspotSecurityTypes.toString()) .append("]").toString(); } } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl index 52da596081a4..9f33e99ad3d1 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl @@ -18,6 +18,9 @@ package android.net.wifi.sharedconnectivity.service; import android.net.wifi.sharedconnectivity.app.KnownNetwork; import android.net.wifi.sharedconnectivity.app.TetherNetwork; +import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus; +import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState; +import android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus; import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback; /* @@ -30,4 +33,9 @@ interface ISharedConnectivityService { void disconnectTetherNetwork(in TetherNetwork network); void connectKnownNetwork(in KnownNetwork network); void forgetKnownNetwork(in KnownNetwork network); + List<TetherNetwork> getTetherNetworks(); + List<KnownNetwork> getKnownNetworks(); + SharedConnectivitySettingsState getSettingsState(); + TetherNetworkConnectionStatus getTetherNetworkConnectionStatus(); + KnownNetworkConnectionStatus getKnownNetworkConnectionStatus(); } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java index ff7246f46859..d2cea613d2ec 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -33,16 +33,15 @@ import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager; import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState; import android.net.wifi.sharedconnectivity.app.TetherNetwork; import android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** @@ -61,30 +60,21 @@ public abstract class SharedConnectivityService extends Service { private static final boolean DEBUG = true; private Handler mHandler; - private final List<ISharedConnectivityCallback> mCallbacks = new ArrayList<>(); - // Used to find DeathRecipient when unregistering a callback to call unlinkToDeath. - private final Map<ISharedConnectivityCallback, DeathRecipient> mDeathRecipientMap = - new HashMap<>(); - + private final RemoteCallbackList<ISharedConnectivityCallback> mRemoteCallbackList = + new RemoteCallbackList<>(); private List<TetherNetwork> mTetherNetworks = Collections.emptyList(); private List<KnownNetwork> mKnownNetworks = Collections.emptyList(); - private SharedConnectivitySettingsState mSettingsState; - private TetherNetworkConnectionStatus mTetherNetworkConnectionStatus; - private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus; - - private final class DeathRecipient implements IBinder.DeathRecipient { - ISharedConnectivityCallback mCallback; - - DeathRecipient(ISharedConnectivityCallback callback) { - mCallback = callback; - } - - @Override - public void binderDied() { - mCallbacks.remove(mCallback); - mDeathRecipientMap.remove(mCallback); - } - } + private SharedConnectivitySettingsState mSettingsState = + new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(false) + .setExtras(Bundle.EMPTY).build(); + private TetherNetworkConnectionStatus mTetherNetworkConnectionStatus = + new TetherNetworkConnectionStatus.Builder() + .setStatus(TetherNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN) + .setExtras(Bundle.EMPTY).build(); + private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus = + new KnownNetworkConnectionStatus.Builder() + .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN) + .setExtras(Bundle.EMPTY).build(); @Override @Nullable @@ -128,12 +118,49 @@ public abstract class SharedConnectivityService extends Service { mHandler.post(() -> onForgetKnownNetwork(network)); } + @Override + public List<TetherNetwork> getTetherNetworks() { + checkPermissions(); + return mTetherNetworks; + } + + @Override + public List<KnownNetwork> getKnownNetworks() { + checkPermissions(); + return mKnownNetworks; + } + + @Override + public SharedConnectivitySettingsState getSettingsState() { + checkPermissions(); + return mSettingsState; + } + + @Override + public TetherNetworkConnectionStatus getTetherNetworkConnectionStatus() { + checkPermissions(); + return mTetherNetworkConnectionStatus; + } + + @Override + public KnownNetworkConnectionStatus getKnownNetworkConnectionStatus() { + checkPermissions(); + return mKnownNetworkConnectionStatus; + } + @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) + /** + * checkPermissions is using checkCallingOrSelfPermission to support CTS testing of this + * service. This does allow a process to bind to itself if it holds the proper + * permission. We do not consider this to be an issue given that the process can already + * access the service data since they are in the same process. + */ private void checkPermissions() { - if (checkCallingPermission(NETWORK_SETTINGS) != PackageManager.PERMISSION_GRANTED - && checkCallingPermission(NETWORK_SETUP_WIZARD) - != PackageManager.PERMISSION_GRANTED) { + if (checkCallingOrSelfPermission(NETWORK_SETTINGS) + != PackageManager.PERMISSION_GRANTED + && checkCallingOrSelfPermission(NETWORK_SETUP_WIZARD) + != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Calling process must have NETWORK_SETTINGS or" + " NETWORK_SETUP_WIZARD permission"); } @@ -148,85 +175,13 @@ public abstract class SharedConnectivityService extends Service { public void onBind() {} private void onRegisterCallback(ISharedConnectivityCallback callback) { - // Listener gets triggered on first register using cashed data - if (!notifyTetherNetworkUpdate(callback) || !notifyKnownNetworkUpdate(callback) - || !notifySettingsStateUpdate(callback) - || !notifyTetherNetworkConnectionStatusChanged(callback) - || !notifyKnownNetworkConnectionStatusChanged(callback)) { - if (DEBUG) Log.w(TAG, "Failed to notify client"); - return; - } - - DeathRecipient deathRecipient = new DeathRecipient(callback); - try { - callback.asBinder().linkToDeath(deathRecipient, 0); - mCallbacks.add(callback); - mDeathRecipientMap.put(callback, deathRecipient); - } catch (RemoteException e) { - if (DEBUG) Log.w(TAG, "Exception in registerCallback", e); - } + mRemoteCallbackList.register(callback); } private void onUnregisterCallback(ISharedConnectivityCallback callback) { - DeathRecipient deathRecipient = mDeathRecipientMap.get(callback); - if (deathRecipient != null) { - callback.asBinder().unlinkToDeath(deathRecipient, 0); - mDeathRecipientMap.remove(callback); - } - mCallbacks.remove(callback); + mRemoteCallbackList.unregister(callback); } - private boolean notifyTetherNetworkUpdate(ISharedConnectivityCallback callback) { - try { - callback.onTetherNetworksUpdated(mTetherNetworks); - } catch (RemoteException e) { - if (DEBUG) Log.w(TAG, "Exception in notifyTetherNetworkUpdate", e); - return false; - } - return true; - } - - private boolean notifyKnownNetworkUpdate(ISharedConnectivityCallback callback) { - try { - callback.onKnownNetworksUpdated(mKnownNetworks); - } catch (RemoteException e) { - if (DEBUG) Log.w(TAG, "Exception in notifyKnownNetworkUpdate", e); - return false; - } - return true; - } - - private boolean notifySettingsStateUpdate(ISharedConnectivityCallback callback) { - try { - callback.onSharedConnectivitySettingsChanged(mSettingsState); - } catch (RemoteException e) { - if (DEBUG) Log.w(TAG, "Exception in notifySettingsStateUpdate", e); - return false; - } - return true; - } - - private boolean notifyTetherNetworkConnectionStatusChanged( - ISharedConnectivityCallback callback) { - try { - callback.onTetherNetworkConnectionStatusChanged(mTetherNetworkConnectionStatus); - } catch (RemoteException e) { - if (DEBUG) Log.w(TAG, "Exception in notifyTetherNetworkConnectionStatusChanged", e); - return false; - } - return true; - } - - private boolean notifyKnownNetworkConnectionStatusChanged( - ISharedConnectivityCallback callback) { - try { - callback.onKnownNetworkConnectionStatusChanged(mKnownNetworkConnectionStatus); - } catch (RemoteException e) { - if (DEBUG) Log.w(TAG, "Exception in notifyKnownNetworkConnectionStatusChanged", e); - return false; - } - return true; - } /** * Implementing application should call this method to provide an up-to-date list of Tether * Networks to be displayed to the user. @@ -239,9 +194,15 @@ public abstract class SharedConnectivityService extends Service { public final void setTetherNetworks(@NonNull List<TetherNetwork> networks) { mTetherNetworks = networks; - for (ISharedConnectivityCallback callback:mCallbacks) { - notifyTetherNetworkUpdate(callback); + int count = mRemoteCallbackList.beginBroadcast(); + for (int i = 0; i < count; i++) { + try { + mRemoteCallbackList.getBroadcastItem(i).onTetherNetworksUpdated(mTetherNetworks); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in setTetherNetworks", e); + } } + mRemoteCallbackList.finishBroadcast(); } /** @@ -256,9 +217,15 @@ public abstract class SharedConnectivityService extends Service { public final void setKnownNetworks(@NonNull List<KnownNetwork> networks) { mKnownNetworks = networks; - for (ISharedConnectivityCallback callback:mCallbacks) { - notifyKnownNetworkUpdate(callback); + int count = mRemoteCallbackList.beginBroadcast(); + for (int i = 0; i < count; i++) { + try { + mRemoteCallbackList.getBroadcastItem(i).onKnownNetworksUpdated(mKnownNetworks); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in setKnownNetworks", e); + } } + mRemoteCallbackList.finishBroadcast(); } /** @@ -274,9 +241,16 @@ public abstract class SharedConnectivityService extends Service { public final void setSettingsState(@NonNull SharedConnectivitySettingsState settingsState) { mSettingsState = settingsState; - for (ISharedConnectivityCallback callback:mCallbacks) { - notifySettingsStateUpdate(callback); + int count = mRemoteCallbackList.beginBroadcast(); + for (int i = 0; i < count; i++) { + try { + mRemoteCallbackList.getBroadcastItem(i).onSharedConnectivitySettingsChanged( + mSettingsState); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in setSettingsState", e); + } } + mRemoteCallbackList.finishBroadcast(); } /** @@ -289,9 +263,18 @@ public abstract class SharedConnectivityService extends Service { public final void updateTetherNetworkConnectionStatus( @NonNull TetherNetworkConnectionStatus status) { mTetherNetworkConnectionStatus = status; - for (ISharedConnectivityCallback callback:mCallbacks) { - notifyTetherNetworkConnectionStatusChanged(callback); + + int count = mRemoteCallbackList.beginBroadcast(); + for (int i = 0; i < count; i++) { + try { + mRemoteCallbackList + .getBroadcastItem(i).onTetherNetworkConnectionStatusChanged( + mTetherNetworkConnectionStatus); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in updateTetherNetworkConnectionStatus", e); + } } + mRemoteCallbackList.finishBroadcast(); } /** @@ -305,9 +288,17 @@ public abstract class SharedConnectivityService extends Service { @NonNull KnownNetworkConnectionStatus status) { mKnownNetworkConnectionStatus = status; - for (ISharedConnectivityCallback callback:mCallbacks) { - notifyKnownNetworkConnectionStatusChanged(callback); + int count = mRemoteCallbackList.beginBroadcast(); + for (int i = 0; i < count; i++) { + try { + mRemoteCallbackList + .getBroadcastItem(i).onKnownNetworkConnectionStatusChanged( + mKnownNetworkConnectionStatus); + } catch (RemoteException e) { + if (DEBUG) Log.w(TAG, "Exception in updateKnownNetworkConnectionStatus", e); + } } + mRemoteCallbackList.finishBroadcast(); } /** diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java index f8f07008e34b..e6595eb2e2a3 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/DeviceInfoTest.java @@ -19,8 +19,7 @@ package android.net.wifi.sharedconnectivity.app; import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_LAPTOP; import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_PHONE; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; @@ -29,7 +28,7 @@ import androidx.test.filters.SmallTest; import org.junit.Test; /** - * Unit tests for {@link android.app.sharedconnectivity.DeviceInfo}. + * Unit tests for {@link DeviceInfo}. */ @SmallTest public class DeviceInfoTest { @@ -63,8 +62,8 @@ public class DeviceInfoTest { parcelR.setDataPosition(0); DeviceInfo fromParcel = DeviceInfo.CREATOR.createFromParcel(parcelR); - assertEquals(info, fromParcel); - assertEquals(info.hashCode(), fromParcel.hashCode()); + assertThat(fromParcel).isEqualTo(info); + assertThat(fromParcel.hashCode()).isEqualTo(info.hashCode()); } /** @@ -74,24 +73,24 @@ public class DeviceInfoTest { public void testEqualsOperation() { DeviceInfo info1 = buildDeviceInfoBuilder().build(); DeviceInfo info2 = buildDeviceInfoBuilder().build(); - assertEquals(info1, info2); + assertThat(info1).isEqualTo(info2); DeviceInfo.Builder builder = buildDeviceInfoBuilder().setDeviceType(DEVICE_TYPE_1); - assertNotEquals(info1, builder.build()); + assertThat(builder.build()).isNotEqualTo(info1); builder = buildDeviceInfoBuilder().setDeviceName(DEVICE_NAME_1); - assertNotEquals(info1, builder.build()); + assertThat(builder.build()).isNotEqualTo(info1); builder = buildDeviceInfoBuilder().setModelName(DEVICE_MODEL_1); - assertNotEquals(info1, builder.build()); + assertThat(builder.build()).isNotEqualTo(info1); builder = buildDeviceInfoBuilder() .setBatteryPercentage(BATTERY_PERCENTAGE_1); - assertNotEquals(info1, builder.build()); + assertThat(builder.build()).isNotEqualTo(info1); builder = buildDeviceInfoBuilder() .setConnectionStrength(CONNECTION_STRENGTH_1); - assertNotEquals(info1, builder.build()); + assertThat(builder.build()).isNotEqualTo(info1); } /** @@ -100,12 +99,19 @@ public class DeviceInfoTest { @Test public void testGetMethods() { DeviceInfo info = buildDeviceInfoBuilder().build(); - assertEquals(info.getDeviceType(), DEVICE_TYPE); - assertEquals(info.getDeviceName(), DEVICE_NAME); - assertEquals(info.getModelName(), DEVICE_MODEL); - assertEquals(info.getBatteryPercentage(), BATTERY_PERCENTAGE); - assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH); - assertEquals(info.getConnectionStrength(), CONNECTION_STRENGTH); + assertThat(info.getDeviceType()).isEqualTo(DEVICE_TYPE); + assertThat(info.getDeviceName()).isEqualTo(DEVICE_NAME); + assertThat(info.getModelName()).isEqualTo(DEVICE_MODEL); + assertThat(info.getBatteryPercentage()).isEqualTo(BATTERY_PERCENTAGE); + assertThat(info.getConnectionStrength()).isEqualTo(CONNECTION_STRENGTH); + } + + @Test + public void testHashCode() { + DeviceInfo info1 = buildDeviceInfoBuilder().build(); + DeviceInfo info2 = buildDeviceInfoBuilder().build(); + + assertThat(info1.hashCode()).isEqualTo(info2.hashCode()); } private DeviceInfo.Builder buildDeviceInfoBuilder() { diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java index 37dca8def0bd..8a0f21e5eea6 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatusTest.java @@ -22,8 +22,7 @@ import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURC import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVED; import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVE_FAILED; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static com.google.common.truth.Truth.assertThat; import android.os.Bundle; import android.os.Parcel; @@ -32,8 +31,10 @@ import androidx.test.filters.SmallTest; import org.junit.Test; +import java.util.Arrays; + /** - * Unit tests for {@link android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus}. + * Unit tests for {@link KnownNetworkConnectionStatus}. */ @SmallTest public class KnownNetworkConnectionStatusTest { @@ -45,6 +46,7 @@ public class KnownNetworkConnectionStatusTest { .setConnectionStrength(2).setBatteryPercentage(50).build(); private static final String SSID_1 = "TEST_SSID1"; private static final String BUNDLE_KEY = "INT-KEY"; + private static final int BUNDLE_VALUE = 1; /** * Verifies parcel serialization/deserialization. @@ -64,8 +66,8 @@ public class KnownNetworkConnectionStatusTest { KnownNetworkConnectionStatus fromParcel = KnownNetworkConnectionStatus.CREATOR.createFromParcel(parcelR); - assertEquals(status, fromParcel); - assertEquals(status.hashCode(), fromParcel.hashCode()); + assertThat(fromParcel).isEqualTo(status); + assertThat(fromParcel.hashCode()).isEqualTo(status.hashCode()); } /** @@ -75,15 +77,15 @@ public class KnownNetworkConnectionStatusTest { public void testEqualsOperation() { KnownNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build(); KnownNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build(); - assertEquals(status2, status2); + assertThat(status1).isEqualTo(status2); KnownNetworkConnectionStatus.Builder builder = buildConnectionStatusBuilder() .setStatus(CONNECTION_STATUS_SAVE_FAILED); - assertNotEquals(status1, builder.build()); + assertThat(builder.build()).isNotEqualTo(status1); builder = buildConnectionStatusBuilder() .setKnownNetwork(buildKnownNetworkBuilder().setSsid(SSID_1).build()); - assertNotEquals(status1, builder.build()); + assertThat(builder.build()).isNotEqualTo(status1); } /** @@ -92,9 +94,17 @@ public class KnownNetworkConnectionStatusTest { @Test public void testGetMethods() { KnownNetworkConnectionStatus status = buildConnectionStatusBuilder().build(); - assertEquals(status.getStatus(), CONNECTION_STATUS_SAVED); - assertEquals(status.getKnownNetwork(), buildKnownNetworkBuilder().build()); - assertEquals(status.getExtras().getInt(BUNDLE_KEY), buildBundle().getInt(BUNDLE_KEY)); + assertThat(status.getStatus()).isEqualTo(CONNECTION_STATUS_SAVED); + assertThat(status.getKnownNetwork()).isEqualTo(buildKnownNetworkBuilder().build()); + assertThat(status.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE); + } + + @Test + public void testHashCode() { + KnownNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build(); + KnownNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build(); + + assertThat(status1.hashCode()).isEqualTo(status2.hashCode()); } private KnownNetworkConnectionStatus.Builder buildConnectionStatusBuilder() { @@ -106,13 +116,15 @@ public class KnownNetworkConnectionStatusTest { private Bundle buildBundle() { Bundle bundle = new Bundle(); - bundle.putInt(BUNDLE_KEY, 1); + bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE); return bundle; } private KnownNetwork.Builder buildKnownNetworkBuilder() { - return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID) - .setSecurityTypes(SECURITY_TYPES).setDeviceInfo(DEVICE_INFO); + KnownNetwork.Builder builder = new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE) + .setSsid(SSID).setDeviceInfo(DEVICE_INFO); + Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType); + return builder; } } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java index 266afcc9a1a6..872dd2e63227 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java @@ -23,18 +23,19 @@ import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TAB import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_CLOUD_SELF; import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; +import android.util.ArraySet; import androidx.test.filters.SmallTest; import org.junit.Test; +import java.util.Arrays; + /** - * Unit tests for {@link android.app.sharedconnectivity.KnownNetwork}. + * Unit tests for {@link KnownNetwork}. */ @SmallTest public class KnownNetworkTest { @@ -69,8 +70,8 @@ public class KnownNetworkTest { parcelR.setDataPosition(0); KnownNetwork fromParcel = KnownNetwork.CREATOR.createFromParcel(parcelR); - assertEquals(network, fromParcel); - assertEquals(network.hashCode(), fromParcel.hashCode()); + assertThat(fromParcel).isEqualTo(network); + assertThat(fromParcel.hashCode()).isEqualTo(network.hashCode()); } /** @@ -80,20 +81,21 @@ public class KnownNetworkTest { public void testEqualsOperation() { KnownNetwork network1 = buildKnownNetworkBuilder().build(); KnownNetwork network2 = buildKnownNetworkBuilder().build(); - assertEquals(network1, network2); + assertThat(network1).isEqualTo(network2); KnownNetwork.Builder builder = buildKnownNetworkBuilder() .setNetworkSource(NETWORK_SOURCE_1); - assertNotEquals(network1, builder.build()); + assertThat(builder.build()).isNotEqualTo(network1); builder = buildKnownNetworkBuilder().setSsid(SSID_1); - assertNotEquals(network1, builder.build()); + assertThat(builder.build()).isNotEqualTo(network1); - builder = buildKnownNetworkBuilder().setSecurityTypes(SECURITY_TYPES_1); - assertNotEquals(network1, builder.build()); + builder = buildKnownNetworkBuilder(); + Arrays.stream(SECURITY_TYPES_1).forEach(builder::addSecurityType); + assertThat(builder.build()).isNotEqualTo(network1); builder = buildKnownNetworkBuilder().setDeviceInfo(DEVICE_INFO_1); - assertNotEquals(network1, builder.build()); + assertThat(builder.build()).isNotEqualTo(network1); } /** @@ -102,14 +104,27 @@ public class KnownNetworkTest { @Test public void testGetMethods() { KnownNetwork network = buildKnownNetworkBuilder().build(); - assertEquals(network.getNetworkSource(), NETWORK_SOURCE); - assertEquals(network.getSsid(), SSID); - assertArrayEquals(network.getSecurityTypes(), SECURITY_TYPES); - assertEquals(network.getDeviceInfo(), DEVICE_INFO); + ArraySet<Integer> securityTypes = new ArraySet<>(); + Arrays.stream(SECURITY_TYPES).forEach(securityTypes::add); + + assertThat(network.getNetworkSource()).isEqualTo(NETWORK_SOURCE); + assertThat(network.getSsid()).isEqualTo(SSID); + assertThat(network.getSecurityTypes()).containsExactlyElementsIn(securityTypes); + assertThat(network.getDeviceInfo()).isEqualTo(DEVICE_INFO); + } + + @Test + public void testHashCode() { + KnownNetwork network1 = buildKnownNetworkBuilder().build(); + KnownNetwork network2 = buildKnownNetworkBuilder().build(); + + assertThat(network1.hashCode()).isEqualTo(network2.hashCode()); } private KnownNetwork.Builder buildKnownNetworkBuilder() { - return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID) - .setSecurityTypes(SECURITY_TYPES).setDeviceInfo(DEVICE_INFO); + KnownNetwork.Builder builder = new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE) + .setSsid(SSID).setDeviceInfo(DEVICE_INFO); + Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType); + return builder; } } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java index 439d456d7657..7c0a8b65813c 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java @@ -22,9 +22,8 @@ import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TAB import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF; import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doThrow; @@ -36,6 +35,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService; +import android.os.Bundle; import android.os.Parcel; import android.os.RemoteException; @@ -46,6 +46,8 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.Executor; /** @@ -95,6 +97,7 @@ public class SharedConnectivityManagerTest { @Test public void bindingToService() { SharedConnectivityManager.create(mContext); + verify(mContext).bindService(any(), any(), anyInt()); } @@ -104,7 +107,8 @@ public class SharedConnectivityManagerTest { @Test public void resourcesNotDefined() { when(mResources.getString(anyInt())).thenThrow(new Resources.NotFoundException()); - assertNull(SharedConnectivityManager.create(mContext)); + + assertThat(SharedConnectivityManager.create(mContext)).isNull(); } /** @@ -115,8 +119,10 @@ public class SharedConnectivityManagerTest { throws Exception { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); + manager.registerCallback(mExecutor, mClientCallback); manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder); + // Since the binder is embedded in a proxy class, the call to registerCallback is done on // the proxy. So instead verifying that the proxy is calling the binder. verify(mIBinder).transact(anyInt(), any(Parcel.class), any(Parcel.class), anyInt()); @@ -126,9 +132,11 @@ public class SharedConnectivityManagerTest { public void registerCallback_serviceNotConnected_canUnregisterAndReregister() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); + manager.registerCallback(mExecutor, mClientCallback); manager.unregisterCallback(mClientCallback); manager.registerCallback(mExecutor, mClientCallback); + verify(mClientCallback, never()).onRegisterCallbackFailed(any(Exception.class)); } @@ -136,7 +144,9 @@ public class SharedConnectivityManagerTest { public void registerCallback_serviceConnected() throws Exception { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); + manager.registerCallback(mExecutor, mClientCallback); + verify(mService).registerCallback(any()); verify(mClientCallback, never()).onRegisterCallbackFailed(any(Exception.class)); } @@ -145,8 +155,10 @@ public class SharedConnectivityManagerTest { public void registerCallback_doubleRegistration_shouldFail() throws Exception { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); + manager.registerCallback(mExecutor, mClientCallback); manager.registerCallback(mExecutor, mClientCallback); + verify(mClientCallback).onRegisterCallbackFailed(any(IllegalStateException.class)); } @@ -155,7 +167,9 @@ public class SharedConnectivityManagerTest { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); doThrow(new RemoteException()).when(mService).registerCallback(any()); + manager.registerCallback(mExecutor, mClientCallback); + verify(mClientCallback).onRegisterCallbackFailed(any(RemoteException.class)); } @@ -166,22 +180,26 @@ public class SharedConnectivityManagerTest { public void unregisterCallback_withoutRegisteringFirst_serviceNotConnected_shouldFail() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); - assertFalse(manager.unregisterCallback(mClientCallback)); + + assertThat(manager.unregisterCallback(mClientCallback)).isFalse(); } @Test public void unregisterCallback_withoutRegisteringFirst_serviceConnected_shouldFail() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); - assertFalse(manager.unregisterCallback(mClientCallback)); + + assertThat(manager.unregisterCallback(mClientCallback)).isFalse(); } @Test public void unregisterCallback() throws Exception { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); + manager.registerCallback(mExecutor, mClientCallback); - assertTrue(manager.unregisterCallback(mClientCallback)); + + assertThat(manager.unregisterCallback(mClientCallback)).isTrue(); verify(mService).unregisterCallback(any()); } @@ -189,26 +207,32 @@ public class SharedConnectivityManagerTest { public void unregisterCallback_doubleUnregistration_serviceConnected_shouldFail() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); + manager.registerCallback(mExecutor, mClientCallback); manager.unregisterCallback(mClientCallback); - assertFalse(manager.unregisterCallback(mClientCallback)); + + assertThat(manager.unregisterCallback(mClientCallback)).isFalse(); } @Test public void unregisterCallback_doubleUnregistration_serviceNotConnected_shouldFail() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); + manager.registerCallback(mExecutor, mClientCallback); manager.unregisterCallback(mClientCallback); - assertFalse(manager.unregisterCallback(mClientCallback)); + + assertThat(manager.unregisterCallback(mClientCallback)).isFalse(); } @Test public void unregisterCallback_remoteException_shouldFail() throws Exception { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); + doThrow(new RemoteException()).when(mService).unregisterCallback(any()); - assertFalse(manager.unregisterCallback(mClientCallback)); + + assertThat(manager.unregisterCallback(mClientCallback)).isFalse(); } /** @@ -217,16 +241,20 @@ public class SharedConnectivityManagerTest { @Test public void onServiceConnected_registerCallbackBeforeConnection() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.registerCallback(mExecutor, mClientCallback); manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder); + verify(mClientCallback).onServiceConnected(); } @Test public void onServiceConnected_registerCallbackAfterConnection() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder); manager.registerCallback(mExecutor, mClientCallback); + verify(mClientCallback).onServiceConnected(); } @@ -236,18 +264,22 @@ public class SharedConnectivityManagerTest { @Test public void onServiceDisconnected_registerCallbackBeforeConnection() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.registerCallback(mExecutor, mClientCallback); manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder); manager.getServiceConnection().onServiceDisconnected(COMPONENT_NAME); + verify(mClientCallback).onServiceDisconnected(); } @Test public void onServiceDisconnected_registerCallbackAfterConnection() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder); manager.registerCallback(mExecutor, mClientCallback); manager.getServiceConnection().onServiceDisconnected(COMPONENT_NAME); + verify(mClientCallback).onServiceDisconnected(); } @@ -259,7 +291,8 @@ public class SharedConnectivityManagerTest { TetherNetwork network = buildTetherNetwork(); SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); - assertFalse(manager.connectTetherNetwork(network)); + + assertThat(manager.connectTetherNetwork(network)).isFalse(); } @Test @@ -267,7 +300,9 @@ public class SharedConnectivityManagerTest { TetherNetwork network = buildTetherNetwork(); SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); + manager.connectTetherNetwork(network); + verify(mService).connectTetherNetwork(network); } @@ -277,7 +312,8 @@ public class SharedConnectivityManagerTest { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); doThrow(new RemoteException()).when(mService).connectTetherNetwork(network); - assertFalse(manager.connectTetherNetwork(network)); + + assertThat(manager.connectTetherNetwork(network)).isFalse(); } /** @@ -288,7 +324,8 @@ public class SharedConnectivityManagerTest { TetherNetwork network = buildTetherNetwork(); SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); - assertFalse(manager.disconnectTetherNetwork(network)); + + assertThat(manager.disconnectTetherNetwork(network)).isFalse(); } @Test @@ -296,7 +333,9 @@ public class SharedConnectivityManagerTest { TetherNetwork network = buildTetherNetwork(); SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); + manager.disconnectTetherNetwork(network); + verify(mService).disconnectTetherNetwork(network); } @@ -306,7 +345,8 @@ public class SharedConnectivityManagerTest { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); doThrow(new RemoteException()).when(mService).disconnectTetherNetwork(any()); - assertFalse(manager.disconnectTetherNetwork(network)); + + assertThat(manager.disconnectTetherNetwork(network)).isFalse(); } /** @@ -317,7 +357,8 @@ public class SharedConnectivityManagerTest { KnownNetwork network = buildKnownNetwork(); SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); - assertFalse(manager.connectKnownNetwork(network)); + + assertThat(manager.connectKnownNetwork(network)).isFalse(); } @Test @@ -325,7 +366,9 @@ public class SharedConnectivityManagerTest { KnownNetwork network = buildKnownNetwork(); SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); + manager.connectKnownNetwork(network); + verify(mService).connectKnownNetwork(network); } @@ -335,7 +378,8 @@ public class SharedConnectivityManagerTest { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); doThrow(new RemoteException()).when(mService).connectKnownNetwork(network); - assertFalse(manager.connectKnownNetwork(network)); + + assertThat(manager.connectKnownNetwork(network)).isFalse(); } /** @@ -346,7 +390,8 @@ public class SharedConnectivityManagerTest { KnownNetwork network = buildKnownNetwork(); SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(null); - assertFalse(manager.forgetKnownNetwork(network)); + + assertThat(manager.forgetKnownNetwork(network)).isFalse(); } @Test @@ -354,7 +399,9 @@ public class SharedConnectivityManagerTest { KnownNetwork network = buildKnownNetwork(); SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); + manager.forgetKnownNetwork(network); + verify(mService).forgetKnownNetwork(network); } @@ -364,7 +411,158 @@ public class SharedConnectivityManagerTest { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.setService(mService); doThrow(new RemoteException()).when(mService).forgetKnownNetwork(network); - assertFalse(manager.forgetKnownNetwork(network)); + + assertThat(manager.forgetKnownNetwork(network)).isFalse(); + } + + /** + * Verify getters. + */ + @Test + public void getTetherNetworks_serviceNotConnected_shouldReturnEmptyList() { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(null); + + assertThat(manager.getKnownNetworks()).isEmpty(); + } + + @Test + public void getTetherNetworks_remoteException_shouldReturnEmptyList() throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(mService); + doThrow(new RemoteException()).when(mService).getTetherNetworks(); + + assertThat(manager.getKnownNetworks()).isEmpty(); + } + + @Test + public void getTetherNetworks_shouldReturnNetworksList() throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + List<TetherNetwork> networks = List.of(buildTetherNetwork()); + manager.setService(mService); + when(mService.getTetherNetworks()).thenReturn(networks); + + assertThat(manager.getTetherNetworks()).containsExactly(buildTetherNetwork()); + } + + @Test + public void getKnownNetworks_serviceNotConnected_shouldReturnEmptyList() + throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(null); + + assertThat(manager.getKnownNetworks()).isEmpty(); + } + + @Test + public void getKnownNetworks_remoteException_shouldReturnEmptyList() throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(mService); + doThrow(new RemoteException()).when(mService).getKnownNetworks(); + + assertThat(manager.getKnownNetworks()).isEmpty(); + } + + @Test + public void getKnownNetworks_shouldReturnNetworksList() throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + List<KnownNetwork> networks = List.of(buildKnownNetwork()); + manager.setService(mService); + when(mService.getKnownNetworks()).thenReturn(networks); + + assertThat(manager.getKnownNetworks()).containsExactly(buildKnownNetwork()); + } + + @Test + public void getSettingsState_serviceNotConnected_shouldReturnNull() throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(null); + + assertThat(manager.getSettingsState()).isNull(); + } + + @Test + public void getSettingsState_remoteException_shouldReturnNull() throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(mService); + doThrow(new RemoteException()).when(mService).getSettingsState(); + + assertThat(manager.getSettingsState()).isNull(); + } + + @Test + public void getSettingsState_serviceConnected_shouldReturnState() throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + SharedConnectivitySettingsState state = new SharedConnectivitySettingsState.Builder() + .setInstantTetherEnabled(true).setExtras(new Bundle()).build(); + manager.setService(mService); + when(mService.getSettingsState()).thenReturn(state); + + assertThat(manager.getSettingsState()).isEqualTo(state); + } + + @Test + public void getTetherNetworkConnectionStatus_serviceNotConnected_shouldReturnNull() + throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(null); + + assertThat(manager.getTetherNetworkConnectionStatus()).isNull(); + } + + @Test + public void getTetherNetworkConnectionStatus_remoteException_shouldReturnNull() + throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(mService); + doThrow(new RemoteException()).when(mService).getTetherNetworkConnectionStatus(); + + assertThat(manager.getTetherNetworkConnectionStatus()).isNull(); + } + + @Test + public void getTetherNetworkConnectionStatus_serviceConnected_shouldReturnStatus() + throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + TetherNetworkConnectionStatus status = new TetherNetworkConnectionStatus.Builder() + .setStatus(TetherNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT) + .setExtras(new Bundle()).build(); + manager.setService(mService); + when(mService.getTetherNetworkConnectionStatus()).thenReturn(status); + + assertThat(manager.getTetherNetworkConnectionStatus()).isEqualTo(status); + } + + @Test + public void getKnownNetworkConnectionStatus_serviceNotConnected_shouldReturnNull() + throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(null); + + assertThat(manager.getKnownNetworkConnectionStatus()).isNull(); + } + + @Test + public void getKnownNetworkConnectionStatus_remoteException_shouldReturnNull() + throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.setService(mService); + doThrow(new RemoteException()).when(mService).getKnownNetworkConnectionStatus(); + + assertThat(manager.getKnownNetworkConnectionStatus()).isNull(); + } + + @Test + public void getKnownNetworkConnectionStatus_serviceConnected_shouldReturnStatus() + throws RemoteException { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + KnownNetworkConnectionStatus status = new KnownNetworkConnectionStatus.Builder() + .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVED) + .setExtras(new Bundle()).build(); + manager.setService(mService); + when(mService.getKnownNetworkConnectionStatus()).thenReturn(status); + + assertThat(manager.getKnownNetworkConnectionStatus()).isEqualTo(status); } private void setResources(@Mock Context context) { @@ -374,18 +572,20 @@ public class SharedConnectivityManagerTest { } private TetherNetwork buildTetherNetwork() { - return new TetherNetwork.Builder() + TetherNetwork.Builder builder = new TetherNetwork.Builder() .setDeviceId(DEVICE_ID) .setDeviceInfo(DEVICE_INFO) .setNetworkType(NETWORK_TYPE) .setNetworkName(NETWORK_NAME) - .setHotspotSsid(HOTSPOT_SSID) - .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES) - .build(); + .setHotspotSsid(HOTSPOT_SSID); + Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType); + return builder.build(); } private KnownNetwork buildKnownNetwork() { - return new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE).setSsid(SSID) - .setSecurityTypes(SECURITY_TYPES).build(); + KnownNetwork.Builder builder = new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE) + .setSsid(SSID).setDeviceInfo(DEVICE_INFO); + Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType); + return builder.build(); } } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java index 3137c7268ae0..752b74905c97 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsStateTest.java @@ -16,8 +16,7 @@ package android.net.wifi.sharedconnectivity.app; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; @@ -26,7 +25,7 @@ import androidx.test.filters.SmallTest; import org.junit.Test; /** - * Unit tests for {@link android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState}. + * Unit tests for {@link SharedConnectivitySettingsState}. */ @SmallTest public class SharedConnectivitySettingsStateTest { @@ -51,8 +50,8 @@ public class SharedConnectivitySettingsStateTest { SharedConnectivitySettingsState fromParcel = SharedConnectivitySettingsState.CREATOR.createFromParcel(parcelR); - assertEquals(state, fromParcel); - assertEquals(state.hashCode(), fromParcel.hashCode()); + assertThat(fromParcel).isEqualTo(state); + assertThat(fromParcel.hashCode()).isEqualTo(state.hashCode()); } /** @@ -62,11 +61,11 @@ public class SharedConnectivitySettingsStateTest { public void testEqualsOperation() { SharedConnectivitySettingsState state1 = buildSettingsStateBuilder().build(); SharedConnectivitySettingsState state2 = buildSettingsStateBuilder().build(); - assertEquals(state1, state2); + assertThat(state1).isEqualTo(state2); SharedConnectivitySettingsState.Builder builder = buildSettingsStateBuilder() .setInstantTetherEnabled(INSTANT_TETHER_STATE_1); - assertNotEquals(state1, builder.build()); + assertThat(builder.build()).isNotEqualTo(state1); } /** @@ -75,7 +74,15 @@ public class SharedConnectivitySettingsStateTest { @Test public void testGetMethods() { SharedConnectivitySettingsState state = buildSettingsStateBuilder().build(); - assertEquals(state.isInstantTetherEnabled(), INSTANT_TETHER_STATE); + assertThat(state.isInstantTetherEnabled()).isEqualTo(INSTANT_TETHER_STATE); + } + + @Test + public void testHashCode() { + SharedConnectivitySettingsState state1 = buildSettingsStateBuilder().build(); + SharedConnectivitySettingsState state2 = buildSettingsStateBuilder().build(); + + assertThat(state1.hashCode()).isEqualTo(state2.hashCode()); } private SharedConnectivitySettingsState.Builder buildSettingsStateBuilder() { diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java index 1d9c2e6df38a..0844364e7a63 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkConnectionStatusTest.java @@ -23,8 +23,7 @@ import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT; import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_TIMEOUT; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static com.google.common.truth.Truth.assertThat; import android.os.Bundle; import android.os.Parcel; @@ -33,8 +32,10 @@ import androidx.test.filters.SmallTest; import org.junit.Test; +import java.util.Arrays; + /** - * Unit tests for {@link android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus}. + * Unit tests for {@link TetherNetworkConnectionStatus}. */ @SmallTest public class TetherNetworkConnectionStatusTest { @@ -49,6 +50,7 @@ public class TetherNetworkConnectionStatusTest { private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP}; private static final long DEVICE_ID_1 = 111L; private static final String BUNDLE_KEY = "INT-KEY"; + private static final int BUNDLE_VALUE = 1; /** * Verifies parcel serialization/deserialization. @@ -68,8 +70,8 @@ public class TetherNetworkConnectionStatusTest { TetherNetworkConnectionStatus fromParcel = TetherNetworkConnectionStatus.CREATOR.createFromParcel(parcelR); - assertEquals(status, fromParcel); - assertEquals(status.hashCode(), fromParcel.hashCode()); + assertThat(fromParcel).isEqualTo(status); + assertThat(fromParcel.hashCode()).isEqualTo(status.hashCode()); } /** @@ -79,15 +81,15 @@ public class TetherNetworkConnectionStatusTest { public void testEqualsOperation() { TetherNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build(); TetherNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build(); - assertEquals(status2, status2); + assertThat(status1).isEqualTo(status2); TetherNetworkConnectionStatus.Builder builder = buildConnectionStatusBuilder() .setStatus(CONNECTION_STATUS_TETHERING_TIMEOUT); - assertNotEquals(status1, builder.build()); + assertThat(builder.build()).isNotEqualTo(status1); builder = buildConnectionStatusBuilder() .setTetherNetwork(buildTetherNetworkBuilder().setDeviceId(DEVICE_ID_1).build()); - assertNotEquals(status1, builder.build()); + assertThat(builder.build()).isNotEqualTo(status1); } /** @@ -96,13 +98,20 @@ public class TetherNetworkConnectionStatusTest { @Test public void testGetMethods() { TetherNetworkConnectionStatus status = buildConnectionStatusBuilder().build(); - assertEquals(status.getStatus(), CONNECTION_STATUS_ENABLING_HOTSPOT); - assertEquals(status.getTetherNetwork(), buildTetherNetworkBuilder().build()); - assertEquals(status.getExtras().getInt(BUNDLE_KEY), buildBundle().getInt(BUNDLE_KEY)); + assertThat(status.getStatus()).isEqualTo(CONNECTION_STATUS_ENABLING_HOTSPOT); + assertThat(status.getTetherNetwork()).isEqualTo(buildTetherNetworkBuilder().build()); + assertThat(status.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE); } - private TetherNetworkConnectionStatus.Builder buildConnectionStatusBuilder() { + @Test + public void testHashCode() { + TetherNetworkConnectionStatus status1 = buildConnectionStatusBuilder().build(); + TetherNetworkConnectionStatus status2 = buildConnectionStatusBuilder().build(); + + assertThat(status1.hashCode()).isEqualTo(status2.hashCode()); + } + private TetherNetworkConnectionStatus.Builder buildConnectionStatusBuilder() { return new TetherNetworkConnectionStatus.Builder() .setStatus(CONNECTION_STATUS_ENABLING_HOTSPOT) .setTetherNetwork(buildTetherNetworkBuilder().build()) @@ -111,18 +120,19 @@ public class TetherNetworkConnectionStatusTest { private Bundle buildBundle() { Bundle bundle = new Bundle(); - bundle.putInt(BUNDLE_KEY, 1); + bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE); return bundle; } private TetherNetwork.Builder buildTetherNetworkBuilder() { - return new TetherNetwork.Builder() + TetherNetwork.Builder builder = new TetherNetwork.Builder() .setDeviceId(DEVICE_ID) .setDeviceInfo(DEVICE_INFO) .setNetworkType(NETWORK_TYPE) .setNetworkName(NETWORK_NAME) .setHotspotSsid(HOTSPOT_SSID) - .setHotspotBssid(HOTSPOT_BSSID) - .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES); + .setHotspotBssid(HOTSPOT_BSSID); + Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType); + return builder; } } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java index b01aec4ad1c1..a50d76782c4a 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/TetherNetworkTest.java @@ -24,18 +24,19 @@ import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TAB import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR; import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_WIFI; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static com.google.common.truth.Truth.assertThat; import android.os.Parcel; +import android.util.ArraySet; import androidx.test.filters.SmallTest; import org.junit.Test; +import java.util.Arrays; + /** - * Unit tests for {@link android.net.wifi.sharedconnectivity.app.TetherNetwork}. + * Unit tests for {@link TetherNetwork}. */ @SmallTest public class TetherNetworkTest { @@ -76,8 +77,8 @@ public class TetherNetworkTest { parcelR.setDataPosition(0); TetherNetwork fromParcel = TetherNetwork.CREATOR.createFromParcel(parcelR); - assertEquals(network, fromParcel); - assertEquals(network.hashCode(), fromParcel.hashCode()); + assertThat(fromParcel).isEqualTo(network); + assertThat(fromParcel.hashCode()).isEqualTo(network.hashCode()); } /** @@ -87,28 +88,31 @@ public class TetherNetworkTest { public void testEqualsOperation() { TetherNetwork network1 = buildTetherNetworkBuilder().build(); TetherNetwork network2 = buildTetherNetworkBuilder().build(); - assertEquals(network1, network2); + assertThat(network1).isEqualTo(network2); TetherNetwork.Builder builder = buildTetherNetworkBuilder().setDeviceId(DEVICE_ID_1); - assertNotEquals(network1, builder.build()); + assertThat(builder.build()).isNotEqualTo(network1); builder = buildTetherNetworkBuilder().setDeviceInfo(DEVICE_INFO_1); - assertNotEquals(network1, builder.build()); + assertThat(builder.build()).isNotEqualTo(network1); builder = buildTetherNetworkBuilder().setNetworkType(NETWORK_TYPE_1); - assertNotEquals(network1, builder.build()); + assertThat(builder.build()).isNotEqualTo(network1); builder = buildTetherNetworkBuilder().setNetworkName(NETWORK_NAME_1); - assertNotEquals(network1, builder.build()); + assertThat(builder.build()).isNotEqualTo(network1); builder = buildTetherNetworkBuilder().setHotspotSsid(HOTSPOT_SSID_1); - assertNotEquals(network1, builder.build()); + assertThat(builder.build()).isNotEqualTo(network1); builder = buildTetherNetworkBuilder().setHotspotBssid(HOTSPOT_BSSID_1); - assertNotEquals(network1, builder.build()); + assertThat(builder.build()).isNotEqualTo(network1); + + builder = buildTetherNetworkBuilder(); + TetherNetwork.Builder builder1 = buildTetherNetworkBuilder(); + Arrays.stream(HOTSPOT_SECURITY_TYPES_1).forEach(builder1::addHotspotSecurityType); - builder = buildTetherNetworkBuilder().setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES_1); - assertNotEquals(network1, builder.build()); + assertThat(builder1.build()).isNotEqualTo(builder.build()); } /** @@ -117,23 +121,35 @@ public class TetherNetworkTest { @Test public void testGetMethods() { TetherNetwork network = buildTetherNetworkBuilder().build(); - assertEquals(network.getDeviceId(), DEVICE_ID); - assertEquals(network.getDeviceInfo(), DEVICE_INFO); - assertEquals(network.getNetworkType(), NETWORK_TYPE); - assertEquals(network.getNetworkName(), NETWORK_NAME); - assertEquals(network.getHotspotSsid(), HOTSPOT_SSID); - assertEquals(network.getHotspotBssid(), HOTSPOT_BSSID); - assertArrayEquals(network.getHotspotSecurityTypes(), HOTSPOT_SECURITY_TYPES); + ArraySet<Integer> securityTypes = new ArraySet<>(); + Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(securityTypes::add); + + assertThat(network.getDeviceId()).isEqualTo(DEVICE_ID); + assertThat(network.getDeviceInfo()).isEqualTo(DEVICE_INFO); + assertThat(network.getNetworkType()).isEqualTo(NETWORK_TYPE); + assertThat(network.getNetworkName()).isEqualTo(NETWORK_NAME); + assertThat(network.getHotspotSsid()).isEqualTo(HOTSPOT_SSID); + assertThat(network.getHotspotBssid()).isEqualTo(HOTSPOT_BSSID); + assertThat(network.getHotspotSecurityTypes()).containsExactlyElementsIn(securityTypes); + } + + @Test + public void testHashCode() { + TetherNetwork network1 = buildTetherNetworkBuilder().build(); + TetherNetwork network2 = buildTetherNetworkBuilder().build(); + + assertThat(network1.hashCode()).isEqualTo(network2.hashCode()); } private TetherNetwork.Builder buildTetherNetworkBuilder() { - return new TetherNetwork.Builder() + TetherNetwork.Builder builder = new TetherNetwork.Builder() .setDeviceId(DEVICE_ID) .setDeviceInfo(DEVICE_INFO) .setNetworkType(NETWORK_TYPE) .setNetworkName(NETWORK_NAME) .setHotspotSsid(HOTSPOT_SSID) - .setHotspotBssid(HOTSPOT_BSSID) - .setHotspotSecurityTypes(HOTSPOT_SECURITY_TYPES); + .setHotspotBssid(HOTSPOT_BSSID); + Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType); + return builder; } } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java index d7f7fea4df3e..81efa79f6df8 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java @@ -16,14 +16,29 @@ package android.net.wifi.sharedconnectivity.service; -import static org.junit.Assert.assertNotNull; +import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP; +import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP; +import static android.net.wifi.sharedconnectivity.app.DeviceInfo.DEVICE_TYPE_TABLET; +import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF; +import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVED; +import static android.net.wifi.sharedconnectivity.app.TetherNetwork.NETWORK_TYPE_CELLULAR; +import static android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; +import android.net.wifi.sharedconnectivity.app.DeviceInfo; import android.net.wifi.sharedconnectivity.app.KnownNetwork; +import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus; +import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState; import android.net.wifi.sharedconnectivity.app.TetherNetwork; +import android.net.wifi.sharedconnectivity.app.TetherNetworkConnectionStatus; +import android.os.Bundle; import android.os.Looper; +import android.os.RemoteException; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; @@ -33,11 +48,37 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + /** - * Unit tests for {@link android.net.wifi.sharedconnectivity.service.SharedConnectivityService}. + * Unit tests for {@link SharedConnectivityService}. */ @SmallTest public class SharedConnectivityServiceTest { + private static final DeviceInfo DEVICE_INFO = new DeviceInfo.Builder() + .setDeviceType(DEVICE_TYPE_TABLET).setDeviceName("TEST_NAME").setModelName("TEST_MODEL") + .setConnectionStrength(2).setBatteryPercentage(50).build(); + private static final TetherNetwork TETHER_NETWORK = + new TetherNetwork.Builder().setDeviceId(1).setDeviceInfo(DEVICE_INFO) + .setNetworkType(NETWORK_TYPE_CELLULAR).setNetworkName("TEST_NETWORK") + .setHotspotSsid("TEST_SSID").setHotspotBssid("TEST_BSSID") + .addHotspotSecurityType(SECURITY_TYPE_WEP) + .addHotspotSecurityType(SECURITY_TYPE_EAP).build(); + private static final List<TetherNetwork> TETHER_NETWORKS = List.of(TETHER_NETWORK); + private static final KnownNetwork KNOWN_NETWORK = + new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE_NEARBY_SELF) + .setSsid("TEST_SSID").addSecurityType(SECURITY_TYPE_WEP) + .addSecurityType(SECURITY_TYPE_EAP).setDeviceInfo(DEVICE_INFO).build(); + private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK); + private static final SharedConnectivitySettingsState SETTINGS_STATE = + new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(true) + .setExtras(Bundle.EMPTY).build(); + private static final TetherNetworkConnectionStatus TETHER_NETWORK_CONNECTION_STATUS = + new TetherNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_UNKNOWN) + .setTetherNetwork(TETHER_NETWORK).setExtras(Bundle.EMPTY).build(); + private static final KnownNetworkConnectionStatus KNOWN_NETWORK_CONNECTION_STATUS = + new KnownNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_SAVED) + .setKnownNetwork(KNOWN_NETWORK).setExtras(Bundle.EMPTY).build(); @Mock Context mContext; @@ -66,20 +107,70 @@ public class SharedConnectivityServiceTest { when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); } - /** - * Verifies service returns - */ @Test - public void testOnBind() { + public void onBind_isNotNull() { + SharedConnectivityService service = createService(); + + assertThat(service.onBind(new Intent())).isNotNull(); + } + + @Test + public void getTetherNetworks() throws RemoteException { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + service.setTetherNetworks(TETHER_NETWORKS); + + assertThat(binder.getTetherNetworks()) + .containsExactlyElementsIn(List.copyOf(TETHER_NETWORKS)); + } + + @Test + public void getKnownNetworks() throws RemoteException { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + service.setKnownNetworks(KNOWN_NETWORKS); + + assertThat(binder.getKnownNetworks()) + .containsExactlyElementsIn(List.copyOf(KNOWN_NETWORKS)); + } + + @Test + public void getSharedConnectivitySettingsState() throws RemoteException { SharedConnectivityService service = createService(); - assertNotNull(service.onBind(new Intent())); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + service.setSettingsState(SETTINGS_STATE); + + assertThat(binder.getSettingsState()).isEqualTo(SETTINGS_STATE); } @Test - public void testCallbacks() { + public void updateTetherNetworkConnectionStatus() throws RemoteException { SharedConnectivityService service = createService(); ISharedConnectivityService.Stub binder = (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + service.updateTetherNetworkConnectionStatus(TETHER_NETWORK_CONNECTION_STATUS); + + assertThat(binder.getTetherNetworkConnectionStatus()) + .isEqualTo(TETHER_NETWORK_CONNECTION_STATUS); + } + + @Test + public void updateKnownNetworkConnectionStatus() throws RemoteException { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS); + + assertThat(binder.getKnownNetworkConnectionStatus()) + .isEqualTo(KNOWN_NETWORK_CONNECTION_STATUS); } private SharedConnectivityService createService() { |