diff options
660 files changed, 13425 insertions, 8593 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 97d28d1a6506..2f843f9d6164 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -345,7 +345,7 @@ aconfig_declarations { name: "android.nfc.flags-aconfig", package: "android.nfc", container: "system", - srcs: ["nfc/java/android/nfc/*.aconfig"], + srcs: ["nfc-non-updatable/flags/*.aconfig"], } cc_aconfig_library { diff --git a/Android.bp b/Android.bp index 529da53e58f7..a1f6e3079804 100644 --- a/Android.bp +++ b/Android.bp @@ -530,6 +530,50 @@ java_library { ], }, jarjar_prefix: "com.android.internal.hidden_from_bootclasspath", + + jarjar_shards: select(release_flag("RELEASE_USE_SHARDED_JARJAR_ON_FRAMEWORK_MINUS_APEX"), { + true: "10", + default: "1", + }), +} + +// This is identical to "framework-minus-apex" but with "jarjar_shards" hardcodd. +// (also "stem" is commented out to avoid a conflict with the "framework-minus-apex") +// TODO(b/383559945) This module is just for local testing / verification. It's not used +// by anything. Remove it once we roll out RELEASE_USE_SHARDED_JARJAR_ON_FRAMEWORK_MINUS_APEX. +java_library { + name: "framework-minus-apex_jarjar-sharded", + defaults: [ + "framework-minus-apex-with-libs-defaults", + "framework-non-updatable-lint-defaults", + ], + installable: true, + // For backwards compatibility. + // stem: "framework", + apex_available: ["//apex_available:platform"], + visibility: [ + "//frameworks/base", + "//frameworks/base/location", + // TODO(b/147128803) remove the below lines + "//frameworks/base/apex/blobstore/framework", + "//frameworks/base/apex/jobscheduler/framework", + "//frameworks/base/packages/Tethering/tests/unit", + "//packages/modules/Connectivity/Tethering/tests/unit", + ], + errorprone: { + javacflags: [ + "-Xep:AndroidFrameworkCompatChange:ERROR", + "-Xep:AndroidFrameworkUid:ERROR", + ], + }, + lint: { + baseline_filename: "lint-baseline.xml", + warning_checks: [ + "FlaggedApi", + ], + }, + jarjar_prefix: "com.android.internal.hidden_from_bootclasspath", + jarjar_shards: "10", } java_library { diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index b6f0c04b8c16..7fef4e502c97 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -23,6 +23,10 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.app.ActivityManager; import android.app.usage.UsageStatsManager; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.Disabled; +import android.compat.annotation.Overridable; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.pm.PackageManager; @@ -349,6 +353,16 @@ public class JobParameters implements Parcelable { private JobCleanupCallback mJobCleanupCallback; @Nullable private Cleaner.Cleanable mCleanable; + /** + * Override handling of abandoned jobs in the system. Overriding this change + * will prevent the system to handle abandoned jobs and report it as a new + * stop reason STOP_REASON_TIMEOUT_ABANDONED. + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_HANDLE_ABANDONED_JOBS = 372529068L; /** @hide */ public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras, @@ -677,6 +691,10 @@ public class JobParameters implements Parcelable { * @hide */ public void enableCleaner() { + if (!Flags.handleAbandonedJobs() + || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) { + return; + } // JobParameters objects are passed by reference in local Binder // transactions for clients running as SYSTEM. The life cycle of the // JobParameters objects are no longer controlled by the client. @@ -695,6 +713,10 @@ public class JobParameters implements Parcelable { * @hide */ public void disableCleaner() { + if (!Flags.handleAbandonedJobs() + || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) { + return; + } if (mJobCleanupCallback != null) { mJobCleanupCallback.disableCleaner(); if (mCleanable != null) { diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java index 69b83cc02b54..d460dcc65473 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java @@ -165,11 +165,9 @@ public abstract class JobServiceEngine { case MSG_EXECUTE_JOB: { final JobParameters params = (JobParameters) msg.obj; try { - if (Flags.handleAbandonedJobs()) { - params.enableCleaner(); - } + params.enableCleaner(); boolean workOngoing = JobServiceEngine.this.onStartJob(params); - if (Flags.handleAbandonedJobs() && !workOngoing) { + if (!workOngoing) { params.disableCleaner(); } ackStartMessage(params, workOngoing); @@ -196,9 +194,7 @@ public abstract class JobServiceEngine { IJobCallback callback = params.getCallback(); if (callback != null) { try { - if (Flags.handleAbandonedJobs()) { - params.disableCleaner(); - } + params.disableCleaner(); callback.jobFinished(params.getJobId(), needsReschedule); } catch (RemoteException e) { Log.e(TAG, "Error reporting job finish to system: binder has gone" + diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 8f44698ffd8d..5dfb3754e8fb 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1034,7 +1034,7 @@ class JobConcurrencyManager { for (int p = preferredUidOnly.size() - 1; p >= 0; --p) { final ContextAssignment assignment = preferredUidOnly.get(p); final JobStatus runningJob = assignment.context.getRunningJobLocked(); - if (runningJob.getUid() != nextPending.getUid()) { + if (runningJob == null || runningJob.getUid() != nextPending.getUid()) { continue; } final int jobBias = mService.evaluateJobBiasLocked(runningJob); @@ -1916,8 +1916,9 @@ class JobConcurrencyManager { for (int i = 0; i < mActiveServices.size(); i++) { final JobServiceContext jc = mActiveServices.get(i); final JobStatus js = jc.getRunningJobLocked(); - if (jc.stopIfExecutingLocked(pkgName, userId, namespace, matchJobId, jobId, - stopReason, internalStopReason)) { + if (js != null && + jc.stopIfExecutingLocked(pkgName, userId, namespace, + matchJobId, jobId, stopReason, internalStopReason)) { foundSome = true; pw.print("Stopping job: "); js.printUniqueId(pw); 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 0b884057ea19..4335cae65a3c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -16,6 +16,7 @@ package com.android.server.job; +import static android.app.job.JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; @@ -1986,8 +1987,8 @@ public class JobSchedulerService extends com.android.server.SystemService jobStatus.getNumAbandonedFailures(), /* 0 is reserved for UNKNOWN_POLICY */ jobStatus.getJob().getBackoffPolicy() + 1, - shouldUseAggressiveBackoff(jobStatus.getNumAbandonedFailures())); - + shouldUseAggressiveBackoff( + jobStatus.getNumAbandonedFailures(), jobStatus.getSourceUid())); // If the job is immediately ready to run, then we can just immediately // put it in the pending list and try to schedule it. This is especially @@ -2432,7 +2433,8 @@ public class JobSchedulerService extends com.android.server.SystemService cancelled.getNumAbandonedFailures(), /* 0 is reserved for UNKNOWN_POLICY */ cancelled.getJob().getBackoffPolicy() + 1, - shouldUseAggressiveBackoff(cancelled.getNumAbandonedFailures())); + shouldUseAggressiveBackoff( + cancelled.getNumAbandonedFailures(), cancelled.getSourceUid())); } // If this is a replacement, bring in the new version of the job if (incomingJob != null) { @@ -3024,6 +3026,7 @@ public class JobSchedulerService extends com.android.server.SystemService int numFailures = failureToReschedule.getNumFailures(); int numAbandonedFailures = failureToReschedule.getNumAbandonedFailures(); int numSystemStops = failureToReschedule.getNumSystemStops(); + final int uid = failureToReschedule.getSourceUid(); // We should back off slowly if JobScheduler keeps stopping the job, // but back off immediately if the issue appeared to be the app's fault // or the user stopped the job somehow. @@ -3033,6 +3036,7 @@ public class JobSchedulerService extends com.android.server.SystemService || stopReason == JobParameters.STOP_REASON_USER) { numFailures++; } else if (android.app.job.Flags.handleAbandonedJobs() + && !CompatChanges.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS, uid) && internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED) { numAbandonedFailures++; numFailures++; @@ -3041,7 +3045,7 @@ public class JobSchedulerService extends com.android.server.SystemService } int backoffPolicy = job.getBackoffPolicy(); - if (shouldUseAggressiveBackoff(numAbandonedFailures)) { + if (shouldUseAggressiveBackoff(numAbandonedFailures, uid)) { backoffPolicy = JobInfo.BACKOFF_POLICY_EXPONENTIAL; } @@ -3112,8 +3116,9 @@ public class JobSchedulerService extends com.android.server.SystemService * @return {@code true} if the given number of abandoned failures indicates that JobScheduler * should use an aggressive backoff policy. */ - public boolean shouldUseAggressiveBackoff(int numAbandonedFailures) { + public boolean shouldUseAggressiveBackoff(int numAbandonedFailures, int uid) { return android.app.job.Flags.handleAbandonedJobs() + && !CompatChanges.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS, uid) && numAbandonedFailures > mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF; } @@ -3223,7 +3228,9 @@ public class JobSchedulerService extends com.android.server.SystemService @VisibleForTesting void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) { boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT; - if (android.app.job.Flags.handleAbandonedJobs()) { + if (android.app.job.Flags.handleAbandonedJobs() + && !CompatChanges.isChangeEnabled( + OVERRIDE_HANDLE_ABANDONED_JOBS, jobStatus.getSourceUid())) { jobTimedOut |= (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED); } @@ -3309,6 +3316,8 @@ public class JobSchedulerService extends com.android.server.SystemService final JobStatus rescheduledJob = needsReschedule ? getRescheduleJobForFailureLocked(jobStatus, stopReason, debugStopReason) : null; final boolean isStopReasonAbandoned = android.app.job.Flags.handleAbandonedJobs() + && !CompatChanges.isChangeEnabled( + OVERRIDE_HANDLE_ABANDONED_JOBS, jobStatus.getSourceUid()) && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED); if (rescheduledJob != null && !rescheduledJob.shouldTreatAsUserInitiatedJob() 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 2b401c8ff6b1..ebfda527001d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -16,6 +16,8 @@ package com.android.server.job; +import static android.app.job.JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS; + import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static com.android.server.job.JobSchedulerService.safelyScaleBytesToKBForHistogram; @@ -550,7 +552,8 @@ public final class JobServiceContext implements ServiceConnection { job.getNumAbandonedFailures(), /* 0 is reserved for UNKNOWN_POLICY */ job.getJob().getBackoffPolicy() + 1, - mService.shouldUseAggressiveBackoff(job.getNumAbandonedFailures())); + mService.shouldUseAggressiveBackoff( + job.getNumAbandonedFailures(), job.getSourceUid())); sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount()); final String sourcePackage = job.getSourcePackageName(); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { @@ -1461,7 +1464,10 @@ public final class JobServiceContext implements ServiceConnection { final StringBuilder debugStopReason = new StringBuilder("client timed out"); if (android.app.job.Flags.handleAbandonedJobs() - && executing != null && executing.isAbandoned()) { + && executing != null + && !CompatChanges.isChangeEnabled( + OVERRIDE_HANDLE_ABANDONED_JOBS, executing.getSourceUid()) + && executing.isAbandoned()) { final String abandonedMessage = " and maybe abandoned"; stopReason = JobParameters.STOP_REASON_TIMEOUT_ABANDONED; internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED; @@ -1689,7 +1695,8 @@ public final class JobServiceContext implements ServiceConnection { completedJob.getNumAbandonedFailures(), /* 0 is reserved for UNKNOWN_POLICY */ completedJob.getJob().getBackoffPolicy() + 1, - mService.shouldUseAggressiveBackoff(completedJob.getNumAbandonedFailures())); + mService.shouldUseAggressiveBackoff( + completedJob.getNumAbandonedFailures(), completedJob.getSourceUid())); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, JobSchedulerService.TRACE_TRACK_NAME, getId()); diff --git a/api/api.go b/api/api.go index e4d783eba4c3..cbdb7e81ab86 100644 --- a/api/api.go +++ b/api/api.go @@ -105,7 +105,7 @@ func (a *CombinedApis) DepsMutator(ctx android.BottomUpMutatorContext) { func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) { ctx.WalkDeps(func(child, parent android.Module) bool { - if _, ok := child.(java.AndroidLibraryDependency); ok && child.Name() != "framework-res" { + if _, ok := android.OtherModuleProvider(ctx, child, java.AndroidLibraryInfoProvider); ok && child.Name() != "framework-res" { // Stubs of BCP and SSCP libraries should not have any dependencies on apps // This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true ctx.ModuleErrorf( diff --git a/boot/preloaded-classes b/boot/preloaded-classes index afd9984cb124..b83bd4e4d401 100644 --- a/boot/preloaded-classes +++ b/boot/preloaded-classes @@ -6583,6 +6583,7 @@ android.permission.LegacyPermissionManager android.permission.PermissionCheckerManager android.permission.PermissionControllerManager$1 android.permission.PermissionControllerManager +android.permission.PermissionManager android.permission.PermissionManager$1 android.permission.PermissionManager$2 android.permission.PermissionManager$OnPermissionsChangeListenerDelegate diff --git a/config/preloaded-classes b/config/preloaded-classes index 343de0bf3b98..e53c78f65877 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -6587,6 +6587,7 @@ android.permission.LegacyPermissionManager android.permission.PermissionCheckerManager android.permission.PermissionControllerManager$1 android.permission.PermissionControllerManager +android.permission.PermissionManager android.permission.PermissionManager$1 android.permission.PermissionManager$2 android.permission.PermissionManager$OnPermissionsChangeListenerDelegate diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist index 16f069394639..e3e929cb00d9 100644 --- a/config/preloaded-classes-denylist +++ b/config/preloaded-classes-denylist @@ -3,7 +3,6 @@ android.media.MediaCodecInfo$CodecCapabilities$FeatureList android.net.ConnectivityThread$Singleton android.os.FileObserver android.os.NullVibrator -android.permission.PermissionManager android.provider.MediaStore android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask android.view.HdrRenderState diff --git a/core/api/current.txt b/core/api/current.txt index ca4b2fae2f99..1b494c51a1f4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8110,7 +8110,7 @@ package android.app.admin { method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, conditional=true) public boolean addCrossProfileWidgetProvider(@Nullable android.content.ComponentName, String); method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting); method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK, conditional=true) public void addPersistentPreferredActivity(@Nullable android.content.ComponentName, android.content.IntentFilter, @NonNull android.content.ComponentName); - method public void addUserRestriction(@NonNull android.content.ComponentName, String); + method public void addUserRestriction(@Nullable android.content.ComponentName, String); method public void addUserRestrictionGlobally(@NonNull String); method public boolean bindDeviceAdminServiceAsUser(@NonNull android.content.ComponentName, @NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle); method public boolean bindDeviceAdminServiceAsUser(@NonNull android.content.ComponentName, @NonNull android.content.Intent, @NonNull android.content.ServiceConnection, @NonNull android.content.Context.BindServiceFlags, @NonNull android.os.UserHandle); @@ -8122,7 +8122,7 @@ package android.app.admin { method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK, conditional=true) public void clearPackagePersistentPreferredActivities(@Nullable android.content.ComponentName, String); method @Deprecated public void clearProfileOwner(@NonNull android.content.ComponentName); method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD, conditional=true) public boolean clearResetPasswordToken(@Nullable android.content.ComponentName); - method public void clearUserRestriction(@NonNull android.content.ComponentName, String); + method public void clearUserRestriction(@Nullable android.content.ComponentName, String); method public android.content.Intent createAdminSupportIntent(@NonNull String); method @Nullable public android.os.UserHandle createAndManageUser(@NonNull android.content.ComponentName, @NonNull String, @NonNull android.content.ComponentName, @Nullable android.os.PersistableBundle, int); method public void enableSystemApp(@NonNull android.content.ComponentName, String); @@ -8183,7 +8183,7 @@ package android.app.admin { method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName); method @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, conditional=true) public CharSequence getOrganizationName(@Nullable android.content.ComponentName); method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(@NonNull android.content.ComponentName); - method @NonNull public android.app.admin.DevicePolicyManager getParentProfileInstance(@NonNull android.content.ComponentName); + method @NonNull public android.app.admin.DevicePolicyManager getParentProfileInstance(@Nullable android.content.ComponentName); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY}, conditional=true) public int getPasswordComplexity(); method public long getPasswordExpiration(@Nullable android.content.ComponentName); method public long getPasswordExpirationTimeout(@Nullable android.content.ComponentName); @@ -57120,6 +57120,7 @@ package android.view.contentcapture { method public void close(); method @NonNull public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext); method public final void destroy(); + method @FlaggedApi("android.view.contentcapture.flags.ccapi_baklava_enabled") public void flush(); method @Nullable public final android.view.contentcapture.ContentCaptureContext getContentCaptureContext(); method @NonNull public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId(); method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f0f0fc98881e..d5cb6c034699 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5079,8 +5079,8 @@ package android.hardware.contexthub { } @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint { - method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback(); - method @Nullable public android.hardware.contexthub.IHubEndpointMessageCallback getMessageCallback(); + method @Nullable public android.hardware.contexthub.HubEndpointLifecycleCallback getLifecycleCallback(); + method @Nullable public android.hardware.contexthub.HubEndpointMessageCallback getMessageCallback(); method @NonNull public java.util.Collection<android.hardware.contexthub.HubServiceInfo> getServiceInfoCollection(); method @Nullable public String getTag(); method public int getVersion(); @@ -5095,14 +5095,19 @@ package android.hardware.contexthub { public static final class HubEndpoint.Builder { ctor public HubEndpoint.Builder(@NonNull android.content.Context); method @NonNull public android.hardware.contexthub.HubEndpoint build(); - method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback); - method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback); - method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.IHubEndpointMessageCallback); - method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointMessageCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.HubEndpointLifecycleCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointLifecycleCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.HubEndpointMessageCallback); + method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointMessageCallback); method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setServiceInfoCollection(@NonNull java.util.Collection<android.hardware.contexthub.HubServiceInfo>); method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String); } + @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointDiscoveryCallback { + method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>); + method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int); + } + @FlaggedApi("android.chre.flags.offload_api") public final class HubEndpointInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier(); @@ -5126,6 +5131,16 @@ package android.hardware.contexthub { method public long getHub(); } + @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointLifecycleCallback { + method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int); + method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String); + method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession); + } + + @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointMessageCallback { + method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage); + } + @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable { method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close(); method @Nullable public String getServiceDescriptor(); @@ -5174,21 +5189,6 @@ package android.hardware.contexthub { method @NonNull public android.hardware.contexthub.HubServiceInfo build(); } - @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointDiscoveryCallback { - method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>); - method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int); - } - - @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback { - method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int); - method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String); - method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession); - } - - @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointMessageCallback { - method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage); - } - } package android.hardware.devicestate { @@ -6192,16 +6192,16 @@ package android.hardware.location { method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback); method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler); method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, long); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, long); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, @NonNull String); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long); method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback); method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint); - method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback); + method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback); field public static final int AUTHORIZATION_DENIED = 0; // 0x0 field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1 field public static final int AUTHORIZATION_GRANTED = 2; // 0x2 @@ -10938,6 +10938,7 @@ package android.nfc.cardemulation { } @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable { + ctor @FlaggedApi("android.nfc.nfc_apdu_service_info_constructor") public ApduServiceInfo(@NonNull android.content.pm.ResolveInfo, boolean, @NonNull String, @NonNull java.util.List<android.nfc.cardemulation.AidGroup>, @NonNull java.util.List<android.nfc.cardemulation.AidGroup>, boolean, int, int, @NonNull String, @NonNull String, @NonNull String); ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String, boolean); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopPatternFilter(@NonNull String, boolean); @@ -19090,6 +19091,7 @@ package android.view.contentcapture { method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureEvent> CREATOR; field public static final int TYPE_CONTEXT_UPDATED = 6; // 0x6 + field @FlaggedApi("android.view.contentcapture.flags.ccapi_baklava_enabled") public static final int TYPE_SESSION_FLUSH = 11; // 0xb field public static final int TYPE_SESSION_PAUSED = 8; // 0x8 field public static final int TYPE_SESSION_RESUMED = 7; // 0x7 field public static final int TYPE_VIEW_APPEARED = 1; // 0x1 @@ -19315,6 +19317,7 @@ package android.webkit { method @Deprecated public abstract void setUseWebViewBackgroundForOverscrollBackground(boolean); method @Deprecated public abstract void setUserAgent(int); method public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean); + field @FlaggedApi("android.webkit.enable_chips") public static final long ENABLE_CHIPS = 380890146L; // 0x16b3ec22L field public static final long ENABLE_SIMPLIFIED_DARK_MODE = 214741472L; // 0xcccb1e0L field @FlaggedApi("android.webkit.user_agent_reduction") public static final long ENABLE_USER_AGENT_REDUCTION = 371034303L; // 0x161d88bfL } diff --git a/core/java/android/app/CameraCompatTaskInfo.java b/core/java/android/app/CameraCompatTaskInfo.java index 845d2acbaf9d..aff6b35a6ded 100644 --- a/core/java/android/app/CameraCompatTaskInfo.java +++ b/core/java/android/app/CameraCompatTaskInfo.java @@ -36,36 +36,42 @@ import java.lang.annotation.RetentionPolicy; */ public class CameraCompatTaskInfo implements Parcelable { /** + * Undefined camera compat mode. + */ + public static final int CAMERA_COMPAT_FREEFORM_UNSPECIFIED = 0; + + /** * The value to use when no camera compat treatment should be applied to a windowed task. */ - public static final int CAMERA_COMPAT_FREEFORM_NONE = 0; + public static final int CAMERA_COMPAT_FREEFORM_NONE = 1; /** * The value to use when camera compat treatment should be applied to an activity requesting * portrait orientation, while a device is in landscape. Applies only to freeform tasks. */ - public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE = 1; + public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE = 2; /** * The value to use when camera compat treatment should be applied to an activity requesting * landscape orientation, while a device is in landscape. Applies only to freeform tasks. */ - public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE = 2; + public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE = 3; /** * The value to use when camera compat treatment should be applied to an activity requesting * portrait orientation, while a device is in portrait. Applies only to freeform tasks. */ - public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT = 3; + public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT = 4; /** * The value to use when camera compat treatment should be applied to an activity requesting * landscape orientation, while a device is in portrait. Applies only to freeform tasks. */ - public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT = 4; + public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT = 5; @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "CAMERA_COMPAT_FREEFORM_" }, value = { + CAMERA_COMPAT_FREEFORM_UNSPECIFIED, CAMERA_COMPAT_FREEFORM_NONE, CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE, CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE, @@ -184,6 +190,7 @@ public class CameraCompatTaskInfo implements Parcelable { public static String freeformCameraCompatModeToString( @FreeformCameraCompatMode int freeformCameraCompatMode) { return switch (freeformCameraCompatMode) { + case CAMERA_COMPAT_FREEFORM_UNSPECIFIED -> "undefined"; case CAMERA_COMPAT_FREEFORM_NONE -> "inactive"; case CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE -> "app-portrait-device-landscape"; diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl index 1ff7a17e578f..d71ee7c712e7 100644 --- a/core/java/android/app/IUserSwitchObserver.aidl +++ b/core/java/android/app/IUserSwitchObserver.aidl @@ -19,10 +19,10 @@ package android.app; import android.os.IRemoteCallback; /** {@hide} */ -interface IUserSwitchObserver { - void onBeforeUserSwitching(int newUserId); - oneway void onUserSwitching(int newUserId, IRemoteCallback reply); - oneway void onUserSwitchComplete(int newUserId); - oneway void onForegroundProfileSwitch(int newProfileId); - oneway void onLockedBootComplete(int newUserId); +oneway interface IUserSwitchObserver { + void onBeforeUserSwitching(int newUserId, IRemoteCallback reply); + void onUserSwitching(int newUserId, IRemoteCallback reply); + void onUserSwitchComplete(int newUserId); + void onForegroundProfileSwitch(int newProfileId); + void onLockedBootComplete(int newUserId); } diff --git a/core/java/android/app/UserSwitchObserver.java b/core/java/android/app/UserSwitchObserver.java index 727799a1f948..1664cfb6f7a8 100644 --- a/core/java/android/app/UserSwitchObserver.java +++ b/core/java/android/app/UserSwitchObserver.java @@ -30,7 +30,11 @@ public class UserSwitchObserver extends IUserSwitchObserver.Stub { } @Override - public void onBeforeUserSwitching(int newUserId) throws RemoteException {} + public void onBeforeUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException { + if (reply != null) { + reply.sendResult(null); + } + } @Override public void onUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 9ddc729850c4..39c27a165588 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -12190,13 +12190,6 @@ public class DevicePolicyManager { * be enforced device-wide. These constants will also state in their documentation which * permission is required to manage the restriction using this API. * - * <p>For callers targeting Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or - * above, calling this API will result in applying the restriction locally on the calling user, - * or locally on the parent profile if called from the - * {@link DevicePolicyManager} instance obtained from - * {@link #getParentProfileInstance(ComponentName)}. To set a restriction globally, call - * {@link #addUserRestrictionGlobally} instead. - * * <p> * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the user restriction * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, @@ -12217,13 +12210,18 @@ public class DevicePolicyManager { * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult} * will contain the reason why the policy changed. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with or + * {@code null} if the caller is not a device admin. * @param key The key of the restriction. * @throws SecurityException if {@code admin} is not a device or profile owner and if the caller * has not been granted the permission to set the given user restriction. */ + // NB: For permission-based callers using this API will result in applying the restriction + // locally on the calling user or locally on the parent profile if called from through parent + // instance. To set a restriction globally, call addUserRestrictionGlobally() instead. + // Permission-based callers must target Android U or above. @SupportsCoexistence - public void addUserRestriction(@NonNull ComponentName admin, + public void addUserRestriction(@Nullable ComponentName admin, @UserManager.UserRestrictionKey String key) { if (mService != null) { try { @@ -12358,10 +12356,6 @@ public class DevicePolicyManager { * constants state in their documentation which permission is required to manage the restriction * using this API. * - * <p>For callers targeting Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or - * above, calling this API will result in clearing any local and global restriction with the - * specified key that was previously set by the caller. - * * <p> * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the user restriction * policy has been cleared, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, @@ -12382,13 +12376,17 @@ public class DevicePolicyManager { * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult} * will contain the reason why the policy changed. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with or + * {@code null} if the caller is not a device admin. * @param key The key of the restriction. * @throws SecurityException if {@code admin} is not a device or profile owner and if the * caller has not been granted the permission to set the given user restriction. */ + // NB: For permission-based callers using this API will result in clearing any local and global + // restriction with the specified key that was previously set by the caller. + // Permission-based callers must target Android U or above. @SupportsCoexistence - public void clearUserRestriction(@NonNull ComponentName admin, + public void clearUserRestriction(@Nullable ComponentName admin, @UserManager.UserRestrictionKey String key) { if (mService != null) { try { @@ -14556,8 +14554,8 @@ public class DevicePolicyManager { } /** - * Called by the profile owner of a managed profile to obtain a {@link DevicePolicyManager} - * whose calls act on the parent profile. + * Called by the profile owner of a managed profile or other apps in a managed profile to + * obtain a {@link DevicePolicyManager} whose calls act on the parent profile. * * <p>The following methods are supported for the parent instance, all other methods will * throw a SecurityException when called on the parent instance: @@ -14614,10 +14612,12 @@ public class DevicePolicyManager { * <li>{@link #wipeData}</li> * </ul> * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with or + * {@code null} if the caller is not a profile owner. * @return a new instance of {@link DevicePolicyManager} that acts on the parent profile. - * @throws SecurityException if {@code admin} is not a profile owner. + * @throws SecurityException if the current user is not a managed profile. */ - public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) { + public @NonNull DevicePolicyManager getParentProfileInstance(@Nullable ComponentName admin) { throwIfParentInstance("getParentProfileInstance"); UserManager um = mContext.getSystemService(UserManager.class); if (!um.isManagedProfile()) { diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java index 3438cc861661..ad43f271a910 100644 --- a/core/java/android/app/contextualsearch/ContextualSearchManager.java +++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java @@ -48,7 +48,9 @@ public final class ContextualSearchManager { /** * Key to get the entrypoint from the extras of the activity launched by contextual search. - * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_ENTRYPOINT = "android.app.contextualsearch.extra.ENTRYPOINT"; @@ -56,14 +58,18 @@ public final class ContextualSearchManager { /** * Key to get the flag_secure value from the extras of the activity launched by contextual * search. The value will be true if flag_secure is found in any of the visible activities. - * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_FLAG_SECURE_FOUND = "android.app.contextualsearch.extra.FLAG_SECURE_FOUND"; /** * Key to get the screenshot from the extras of the activity launched by contextual search. - * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_SCREENSHOT = "android.app.contextualsearch.extra.SCREENSHOT"; @@ -71,7 +77,9 @@ public final class ContextualSearchManager { /** * Key to check whether managed profile is visible from the extras of the activity launched by * contextual search. The value will be true if any one of the visible apps is managed. - * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE = "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE"; @@ -79,7 +87,9 @@ public final class ContextualSearchManager { /** * Key to get the list of visible packages from the extras of the activity launched by * contextual search. - * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_VISIBLE_PACKAGE_NAMES = "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES"; @@ -87,7 +97,9 @@ public final class ContextualSearchManager { /** * Key to get the time the user made the invocation request, based on * {@link SystemClock#uptimeMillis()}. - * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH * * TODO: un-hide in W * @@ -99,11 +111,24 @@ public final class ContextualSearchManager { /** * Key to get the binder token from the extras of the activity launched by contextual search. * This token is needed to invoke {@link CallbackToken#getContextualSearchState} method. - * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH */ public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN"; /** + * Key to check whether audio is playing when contextual search is invoked. + * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH. + * + * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH + * + * @hide + */ + public static final String EXTRA_IS_AUDIO_PLAYING = + "android.app.contextualsearch.extra.IS_AUDIO_PLAYING"; + + /** * Intent action for contextual search invocation. The app providing the contextual search * experience must add this intent filter action to the activity it wants to be launched. * <br> diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig index e8cfd79c9cc7..c19921dcdc61 100644 --- a/core/java/android/app/contextualsearch/flags.aconfig +++ b/core/java/android/app/contextualsearch/flags.aconfig @@ -8,6 +8,7 @@ flag { bug: "309689654" is_exported: true } + flag { name: "enable_token_refresh" namespace: "machine_learning" @@ -27,4 +28,11 @@ flag { namespace: "sysui_integrations" description: "Identify live contextual search UI to exclude from contextual search screenshot." bug: "372510690" +} + +flag { + name: "include_audio_playing_status" + namespace: "sysui_integrations" + description: "Add audio playing status to the contextual search invocation intent." + bug: "372935419" }
\ No newline at end of file diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 00ddae334ef2..0d219a901b9d 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -149,6 +149,13 @@ flag { } flag { + name: "cache_sdk_system_features" + namespace: "system_performance" + description: "Feature flag to enable optimized cache for SDK-defined system feature lookups." + bug: "375000483" +} + +flag { name: "provide_info_of_apk_in_apex" is_exported: true namespace: "package_manager_service" diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 34c0f7b19da9..321f09bd4760 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -482,8 +482,19 @@ public class CameraDeviceImpl extends CameraDevice mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice); Parcel resultParcel = Parcel.obtain(); - mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel, 0); + + // Passing in PARCELABLE_WRITE_RETURN_VALUE closes the ParcelFileDescriptors + // owned by MQDescriptor returned by getCaptureResultMetadataQueue() + // Though these will be closed when GC runs, that may not happen for a while. + // Also, apps running with StrictMode would get warnings / crash in the case they're not + // explicitly closed. + mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel, + Parcelable.PARCELABLE_WRITE_RETURN_VALUE); mFMQReader = nativeCreateFMQReader(resultParcel); + // Recycle since resultParcel would dup fds from MQDescriptor as well. We don't + // need them after the native FMQ reader has been created. That is since the native + // creates calls MQDescriptor.readFromParcel() which again dups the fds. + resultParcel.recycle(); IBinder remoteDeviceBinder = remoteDevice.asBinder(); // For legacy camera device, remoteDevice is in the same process, and diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java index b251aa1abc0d..1e5bed519e1a 100644 --- a/core/java/android/hardware/contexthub/HubEndpoint.java +++ b/core/java/android/hardware/contexthub/HubEndpoint.java @@ -99,8 +99,8 @@ public class HubEndpoint { private final Object mLock = new Object(); private final HubEndpointInfo mPendingHubEndpointInfo; - @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback; - @Nullable private final IHubEndpointMessageCallback mMessageCallback; + @Nullable private final HubEndpointLifecycleCallback mLifecycleCallback; + @Nullable private final HubEndpointMessageCallback mMessageCallback; @NonNull private final Executor mLifecycleCallbackExecutor; @NonNull private final Executor mMessageCallbackExecutor; @@ -335,9 +335,9 @@ public class HubEndpoint { private HubEndpoint( @NonNull HubEndpointInfo pendingEndpointInfo, - @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback, + @Nullable HubEndpointLifecycleCallback endpointLifecycleCallback, @NonNull Executor lifecycleCallbackExecutor, - @Nullable IHubEndpointMessageCallback endpointMessageCallback, + @Nullable HubEndpointMessageCallback endpointMessageCallback, @NonNull Executor messageCallbackExecutor) { mPendingHubEndpointInfo = pendingEndpointInfo; mLifecycleCallback = endpointLifecycleCallback; @@ -404,11 +404,11 @@ public class HubEndpoint { HubEndpointSession newSession; try { - // Request system service to assign session id. - int sessionId = mServiceToken.openSession(destinationInfo, serviceDescriptor); - - // Save the newly created session synchronized (mLock) { + // Request system service to assign session id. + int sessionId = mServiceToken.openSession(destinationInfo, serviceDescriptor); + + // Save the newly created session newSession = new HubEndpointSession( sessionId, @@ -485,12 +485,12 @@ public class HubEndpoint { } @Nullable - public IHubEndpointLifecycleCallback getLifecycleCallback() { + public HubEndpointLifecycleCallback getLifecycleCallback() { return mLifecycleCallback; } @Nullable - public IHubEndpointMessageCallback getMessageCallback() { + public HubEndpointMessageCallback getMessageCallback() { return mMessageCallback; } @@ -498,11 +498,11 @@ public class HubEndpoint { public static final class Builder { private final String mPackageName; - @Nullable private IHubEndpointLifecycleCallback mLifecycleCallback; + @Nullable private HubEndpointLifecycleCallback mLifecycleCallback; @NonNull private Executor mLifecycleCallbackExecutor; - @Nullable private IHubEndpointMessageCallback mMessageCallback; + @Nullable private HubEndpointMessageCallback mMessageCallback; @NonNull private Executor mMessageCallbackExecutor; private int mVersion; @@ -542,7 +542,7 @@ public class HubEndpoint { /** Attach a callback interface for lifecycle events for this Endpoint */ @NonNull public Builder setLifecycleCallback( - @NonNull IHubEndpointLifecycleCallback lifecycleCallback) { + @NonNull HubEndpointLifecycleCallback lifecycleCallback) { mLifecycleCallback = lifecycleCallback; return this; } @@ -554,7 +554,7 @@ public class HubEndpoint { @NonNull public Builder setLifecycleCallback( @NonNull @CallbackExecutor Executor executor, - @NonNull IHubEndpointLifecycleCallback lifecycleCallback) { + @NonNull HubEndpointLifecycleCallback lifecycleCallback) { mLifecycleCallbackExecutor = executor; mLifecycleCallback = lifecycleCallback; return this; @@ -562,7 +562,7 @@ public class HubEndpoint { /** Attach a callback interface for message events for this Endpoint */ @NonNull - public Builder setMessageCallback(@NonNull IHubEndpointMessageCallback messageCallback) { + public Builder setMessageCallback(@NonNull HubEndpointMessageCallback messageCallback) { mMessageCallback = messageCallback; return this; } @@ -574,7 +574,7 @@ public class HubEndpoint { @NonNull public Builder setMessageCallback( @NonNull @CallbackExecutor Executor executor, - @NonNull IHubEndpointMessageCallback messageCallback) { + @NonNull HubEndpointMessageCallback messageCallback) { mMessageCallbackExecutor = executor; mMessageCallback = messageCallback; return this; diff --git a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java b/core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java index a61a7ebd0de9..4672bbb74170 100644 --- a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java +++ b/core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java @@ -30,7 +30,7 @@ import java.util.List; */ @SystemApi @FlaggedApi(Flags.FLAG_OFFLOAD_API) -public interface IHubEndpointDiscoveryCallback { +public interface HubEndpointDiscoveryCallback { /** * Called when a list of hub endpoints have started. * diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java index 698ed0adfd80..6d75010711cc 100644 --- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java +++ b/core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java @@ -29,7 +29,7 @@ import android.chre.flags.Flags; */ @SystemApi @FlaggedApi(Flags.FLAG_OFFLOAD_API) -public interface IHubEndpointLifecycleCallback { +public interface HubEndpointLifecycleCallback { /** * Called when an endpoint is requesting a session be opened with another endpoint. * diff --git a/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java b/core/java/android/hardware/contexthub/HubEndpointMessageCallback.java index fde7017b5e76..5db54efc8893 100644 --- a/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java +++ b/core/java/android/hardware/contexthub/HubEndpointMessageCallback.java @@ -26,18 +26,18 @@ import android.chre.flags.Flags; * <p>This interface can be attached to an endpoint through {@link * HubEndpoint.Builder#setMessageCallback} method. Methods in this interface will only be called * when the endpoint is currently registered and has an open session. The endpoint will receive - * session lifecycle callbacks through {@link IHubEndpointLifecycleCallback}. + * session lifecycle callbacks through {@link HubEndpointLifecycleCallback}. * * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_OFFLOAD_API) -public interface IHubEndpointMessageCallback { +public interface HubEndpointMessageCallback { /** * Callback interface for receiving messages for a particular endpoint session. * * @param session The session this message is sent through. Previously specified in a {@link - * IHubEndpointLifecycleCallback#onSessionOpened(HubEndpointSession)} call. + * HubEndpointLifecycleCallback#onSessionOpened(HubEndpointSession)} call. * @param message The {@link HubMessage} object representing a message received by the endpoint * that registered this callback interface. This message is constructed by the */ diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java index 77f937ebeabc..f7f5636264e4 100644 --- a/core/java/android/hardware/contexthub/HubEndpointSession.java +++ b/core/java/android/hardware/contexthub/HubEndpointSession.java @@ -137,7 +137,7 @@ public class HubEndpointSession implements AutoCloseable { * no service associated to this session. * * <p>For hub initiated sessions, the object was previously used in as an argument for open - * request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. + * request in {@link HubEndpointLifecycleCallback#onSessionOpenRequest}. * * <p>For app initiated sessions, the object was previously used in an open request in {@link * android.hardware.location.ContextHubManager#openSession} diff --git a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java index 1f2bdb985008..b193d00467aa 100644 --- a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java +++ b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java @@ -23,7 +23,7 @@ import android.annotation.SystemApi; import android.chre.flags.Flags; /** - * Return type of {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines + * Return type of {@link HubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines * whether a open session request from the remote is accepted or not. * * @hide diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.java b/core/java/android/hardware/contexthub/HubServiceInfo.java index a1c52fb5864f..2f33e8f8872b 100644 --- a/core/java/android/hardware/contexthub/HubServiceInfo.java +++ b/core/java/android/hardware/contexthub/HubServiceInfo.java @@ -132,6 +132,21 @@ public final class HubServiceInfo implements Parcelable { return 0; } + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + out.append("Service: "); + out.append("descriptor="); + out.append(mServiceDescriptor); + out.append(", format="); + out.append(mFormat); + out.append(", version="); + out.append(Integer.toHexString(mMajorVersion)); + out.append("."); + out.append(Integer.toHexString(mMinorVersion)); + return out.toString(); + } + /** Parcel implementation details */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index ffa546067eff..9030810a1c1a 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -106,7 +106,7 @@ public final class DisplayManagerGlobal { @IntDef(prefix = {"EVENT_DISPLAY_"}, flag = true, value = { EVENT_DISPLAY_ADDED, - EVENT_DISPLAY_CHANGED, + EVENT_DISPLAY_BASIC_CHANGED, EVENT_DISPLAY_REMOVED, EVENT_DISPLAY_BRIGHTNESS_CHANGED, EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED, @@ -119,7 +119,8 @@ public final class DisplayManagerGlobal { public @interface DisplayEvent {} public static final int EVENT_DISPLAY_ADDED = 1; - public static final int EVENT_DISPLAY_CHANGED = 2; + public static final int EVENT_DISPLAY_BASIC_CHANGED = 2; + public static final int EVENT_DISPLAY_REMOVED = 3; public static final int EVENT_DISPLAY_BRIGHTNESS_CHANGED = 4; public static final int EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = 5; @@ -130,7 +131,7 @@ public final class DisplayManagerGlobal { @LongDef(prefix = {"INTERNAL_EVENT_FLAG_"}, flag = true, value = { INTERNAL_EVENT_FLAG_DISPLAY_ADDED, - INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, + INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED, INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED, INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED, @@ -143,7 +144,7 @@ public final class DisplayManagerGlobal { public @interface InternalEventFlag {} public static final long INTERNAL_EVENT_FLAG_DISPLAY_ADDED = 1L << 0; - public static final long INTERNAL_EVENT_FLAG_DISPLAY_CHANGED = 1L << 1; + public static final long INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED = 1L << 1; public static final long INTERNAL_EVENT_FLAG_DISPLAY_REMOVED = 1L << 2; public static final long INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED = 1L << 3; public static final long INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED = 1L << 4; @@ -485,7 +486,7 @@ public final class DisplayManagerGlobal { // There can be racing condition between DMS and WMS callbacks, so force triggering the // listener to make sure the client can get the onDisplayChanged callback even if // DisplayInfo is not changed (Display read from both DisplayInfo and WindowConfiguration). - handleDisplayEvent(displayId, EVENT_DISPLAY_CHANGED, true /* forceUpdate */); + handleDisplayEvent(displayId, EVENT_DISPLAY_BASIC_CHANGED, true /* forceUpdate */); } private static Looper getLooperForHandler(@Nullable Handler handler) { @@ -518,7 +519,8 @@ public final class DisplayManagerGlobal { } if (mDispatchNativeCallbacks) { mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; } if (!mTopologyListeners.isEmpty()) { @@ -571,7 +573,8 @@ public final class DisplayManagerGlobal { } info = getDisplayInfoLocked(displayId); - if (event == EVENT_DISPLAY_CHANGED && mDispatchNativeCallbacks) { + if ((event == EVENT_DISPLAY_BASIC_CHANGED + || event == EVENT_DISPLAY_REFRESH_RATE_CHANGED) && mDispatchNativeCallbacks) { // Choreographer only supports a single display, so only dispatch refresh rate // changes for the default display. if (displayId == Display.DEFAULT_DISPLAY) { @@ -1492,9 +1495,9 @@ public final class DisplayManagerGlobal { mListener.onDisplayAdded(displayId); } break; - case EVENT_DISPLAY_CHANGED: - if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) - != 0) { + case EVENT_DISPLAY_BASIC_CHANGED: + if ((mInternalEventFlagsMask + & INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) != 0) { if (info != null && (forceUpdate || !info.equals(mDisplayInfo))) { if (extraLogging()) { Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: " @@ -1691,8 +1694,8 @@ public final class DisplayManagerGlobal { switch (event) { case EVENT_DISPLAY_ADDED: return "ADDED"; - case EVENT_DISPLAY_CHANGED: - return "CHANGED"; + case EVENT_DISPLAY_BASIC_CHANGED: + return "BASIC_CHANGED"; case EVENT_DISPLAY_REMOVED: return "REMOVED"; case EVENT_DISPLAY_BRIGHTNESS_CHANGED: @@ -1763,7 +1766,11 @@ public final class DisplayManagerGlobal { } if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) { - baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CHANGED; + // For backward compatibility, a client subscribing to + // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and + // RR changes + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE; } if ((eventFlags diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index 47ef461dd53d..9f8505fd01ad 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -125,7 +125,10 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 75; public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 76; public static final int KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB = 77; - + public static final int KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT = 78; + public static final int KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT = 79; + public static final int KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP = 80; + public static final int KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN = 81; public static final int FLAG_CANCELLED = 1; @@ -217,7 +220,11 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION, KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK, KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, - KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB + KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB, + KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT, + KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT, + KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP, + KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN, }) @Retention(RetentionPolicy.SOURCE) public @interface KeyGestureType { @@ -792,6 +799,14 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW"; case KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB: return "KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB"; + case KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT: + return "KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT"; + case KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT: + return "KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT"; + case KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP: + return "KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP"; + case KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN: + return "KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN"; default: return Integer.toHexString(value); } diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 1e0cc94612dd..0633233e4ac7 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -36,11 +36,11 @@ import android.content.pm.PackageManager; import android.hardware.contexthub.ErrorCode; import android.hardware.contexthub.HubDiscoveryInfo; import android.hardware.contexthub.HubEndpoint; +import android.hardware.contexthub.HubEndpointDiscoveryCallback; import android.hardware.contexthub.HubEndpointInfo; +import android.hardware.contexthub.HubEndpointLifecycleCallback; import android.hardware.contexthub.HubServiceInfo; import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback; -import android.hardware.contexthub.IHubEndpointDiscoveryCallback; -import android.hardware.contexthub.IHubEndpointLifecycleCallback; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; @@ -207,7 +207,7 @@ public final class ContextHubManager { private Handler mCallbackHandler; /** A map of endpoint discovery callbacks currently registered */ - private Map<IHubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback> + private Map<HubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback> mDiscoveryCallbacks = new ConcurrentHashMap<>(); /** @@ -750,14 +750,15 @@ public final class ContextHubManager { /** * Creates an interface to invoke endpoint discovery callbacks to send down to the service. * - * @param callback the callback to invoke at the client process * @param executor the executor to invoke callbacks for this client + * @param callback the callback to invoke at the client process + * @param serviceDescriptor an optional descriptor to match discovery list with * @return the callback interface */ @FlaggedApi(Flags.FLAG_OFFLOAD_API) private IContextHubEndpointDiscoveryCallback createDiscoveryCallback( - IHubEndpointDiscoveryCallback callback, Executor executor, + HubEndpointDiscoveryCallback callback, @Nullable String serviceDescriptor) { return new IContextHubEndpointDiscoveryCallback.Stub() { @Override @@ -829,36 +830,36 @@ public final class ContextHubManager { } /** - * Equivalent to {@link #registerEndpointDiscoveryCallback(long, IHubEndpointDiscoveryCallback, - * Executor)} with the default executor in the main thread. + * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor, + * HubEndpointDiscoveryCallback, long)} with the default executor in the main thread. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void registerEndpointDiscoveryCallback( - long endpointId, @NonNull IHubEndpointDiscoveryCallback callback) { + @NonNull HubEndpointDiscoveryCallback callback, long endpointId) { registerEndpointDiscoveryCallback( - endpointId, callback, new HandlerExecutor(Handler.getMain())); + new HandlerExecutor(Handler.getMain()), callback, endpointId); } /** * Registers a callback to be notified when the hub endpoint with the corresponding endpoint ID * has started or stopped. * - * @param endpointId The identifier of the hub endpoint. - * @param callback The callback to be invoked. * @param executor The executor to invoke the callback on. + * @param callback The callback to be invoked. + * @param endpointId The identifier of the hub endpoint. * @throws UnsupportedOperationException If the operation is not supported. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void registerEndpointDiscoveryCallback( - long endpointId, - @NonNull IHubEndpointDiscoveryCallback callback, - @NonNull Executor executor) { - Objects.requireNonNull(callback, "callback cannot be null"); + @NonNull Executor executor, + @NonNull HubEndpointDiscoveryCallback callback, + long endpointId) { Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(callback, "callback cannot be null"); IContextHubEndpointDiscoveryCallback iCallback = - createDiscoveryCallback(callback, executor, null); + createDiscoveryCallback(executor, callback, null); try { mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback); } catch (RemoteException e) { @@ -869,42 +870,42 @@ public final class ContextHubManager { } /** - * Equivalent to {@link #registerEndpointDiscoveryCallback(String, - * IHubEndpointDiscoveryCallback, Executor)} with the default executor in the main thread. + * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor, + * HubEndpointDiscoveryCallback, String)} with the default executor in the main thread. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void registerEndpointDiscoveryCallback( - @NonNull String serviceDescriptor, @NonNull IHubEndpointDiscoveryCallback callback) { + @NonNull HubEndpointDiscoveryCallback callback, @NonNull String serviceDescriptor) { registerEndpointDiscoveryCallback( - serviceDescriptor, callback, new HandlerExecutor(Handler.getMain())); + new HandlerExecutor(Handler.getMain()), callback, serviceDescriptor); } /** * Registers a callback to be notified when the hub endpoint with the corresponding service * descriptor has started or stopped. * + * @param executor The executor to invoke the callback on. * @param serviceDescriptor The service descriptor of the hub endpoint. * @param callback The callback to be invoked. - * @param executor The executor to invoke the callback on. * @throws IllegalArgumentException if the serviceDescriptor is empty. * @throws UnsupportedOperationException If the operation is not supported. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void registerEndpointDiscoveryCallback( - @NonNull String serviceDescriptor, - @NonNull IHubEndpointDiscoveryCallback callback, - @NonNull Executor executor) { - Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null"); - Objects.requireNonNull(callback, "callback cannot be null"); + @NonNull Executor executor, + @NonNull HubEndpointDiscoveryCallback callback, + @NonNull String serviceDescriptor) { Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(callback, "callback cannot be null"); + Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null"); if (serviceDescriptor.isBlank()) { throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor); } IContextHubEndpointDiscoveryCallback iCallback = - createDiscoveryCallback(callback, executor, serviceDescriptor); + createDiscoveryCallback(executor, callback, serviceDescriptor); try { mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback); } catch (RemoteException e) { @@ -924,7 +925,7 @@ public final class ContextHubManager { @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) public void unregisterEndpointDiscoveryCallback( - @NonNull IHubEndpointDiscoveryCallback callback) { + @NonNull HubEndpointDiscoveryCallback callback) { Objects.requireNonNull(callback, "callback cannot be null"); IContextHubEndpointDiscoveryCallback iCallback = mDiscoveryCallbacks.remove(callback); if (iCallback == null) { @@ -1291,7 +1292,7 @@ public final class ContextHubManager { * service. * * <p>Context Hub Service will create the endpoint session and notify the registered endpoint. - * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback} + * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback} * object regarding the lifecycle events of the session. * * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link @@ -1311,7 +1312,7 @@ public final class ContextHubManager { * described by a {@link HubServiceInfo} object. * * <p>Context Hub Service will create the endpoint session and notify the registered endpoint. - * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback} + * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback} * object regarding the lifecycle events of the session. * * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig index afb982ba64ca..100d50d8e70c 100644 --- a/core/java/android/net/thread/flags.aconfig +++ b/core/java/android/net/thread/flags.aconfig @@ -17,5 +17,5 @@ flag { is_exported: true namespace: "thread_network" description: "Controls whether the Android Thread feature is enabled" - bug: "301473012" + bug: "384596973" } diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 50121278f0e6..ecd90e46e432 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -45,7 +45,8 @@ import java.util.function.BiFunction; * {@link PersistableBundle} subclass. */ @android.ravenwood.annotation.RavenwoodKeepWholeClass -public class BaseBundle { +@SuppressWarnings("HiddenSuperclass") +public class BaseBundle implements Parcel.ClassLoaderProvider { /** @hide */ protected static final String TAG = "Bundle"; static final boolean DEBUG = false; @@ -299,8 +300,9 @@ public class BaseBundle { /** * Return the ClassLoader currently associated with this Bundle. + * @hide */ - ClassLoader getClassLoader() { + public ClassLoader getClassLoader() { return mClassLoader; } @@ -405,6 +407,9 @@ public class BaseBundle { if ((mFlags & Bundle.FLAG_VERIFY_TOKENS_PRESENT) != 0) { Intent.maybeMarkAsMissingCreatorToken(object); } + } else if (object instanceof Bundle) { + Bundle bundle = (Bundle) object; + bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); } return (clazz != null) ? clazz.cast(object) : (T) object; } @@ -475,10 +480,10 @@ public class BaseBundle { map.erase(); map.ensureCapacity(count); } - int numLazyValues = 0; + int[] numLazyValues = new int[]{0}; try { - numLazyValues = parcelledData.readArrayMap(map, count, !parcelledByNative, - /* lazy */ ownsParcel, mClassLoader); + parcelledData.readArrayMap(map, count, !parcelledByNative, + /* lazy */ ownsParcel, this, numLazyValues); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); @@ -489,14 +494,14 @@ public class BaseBundle { } finally { mWeakParcelledData = null; if (ownsParcel) { - if (numLazyValues == 0) { + if (numLazyValues[0] == 0) { recycleParcel(parcelledData); } else { mWeakParcelledData = new WeakReference<>(parcelledData); } } - mLazyValues = numLazyValues; + mLazyValues = numLazyValues[0]; mParcelledByNative = false; mMap = map; // Set field last as it is volatile diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 819d58d9f059..55bfd451d97a 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -141,6 +141,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { STRIPPED.putInt("STRIPPED", 1); } + private boolean isFirstRetrievedFromABundle = false; + /** * Constructs a new, empty Bundle. */ @@ -1020,7 +1022,9 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { return null; } try { - return (Bundle) o; + Bundle bundle = (Bundle) o; + bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); + return bundle; } catch (ClassCastException e) { typeWarning(key, o, "Bundle", e); return null; @@ -1028,6 +1032,21 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Set the ClassLoader of a bundle to its container bundle. This is necessary so that when a + * bundle's ClassLoader is changed, it can be propagated to its children. Do this only when it + * is retrieved from the container bundle first time though. Once it is accessed outside of its + * container, its ClassLoader should no longer be changed by its container anymore. + * + * @param containerBundle the bundle this bundle is retrieved from. + */ + void setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(BaseBundle containerBundle) { + if (!isFirstRetrievedFromABundle) { + setClassLoader(containerBundle.getClassLoader()); + isFirstRetrievedFromABundle = true; + } + } + + /** * Returns the value associated with the given key, or {@code null} if * no mapping of the desired type exists for the given key or a {@code null} * value is explicitly associated with the key. diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index cf473ec9c3ea..5ba6553a58c9 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -4650,7 +4650,7 @@ public final class Parcel { * @hide */ @Nullable - public Object readLazyValue(@Nullable ClassLoader loader) { + private Object readLazyValue(@Nullable ClassLoaderProvider loaderProvider) { int start = dataPosition(); int type = readInt(); if (isLengthPrefixed(type)) { @@ -4661,12 +4661,17 @@ public final class Parcel { int end = MathUtils.addOrThrow(dataPosition(), objectLength); int valueLength = end - start; setDataPosition(end); - return new LazyValue(this, start, valueLength, type, loader); + return new LazyValue(this, start, valueLength, type, loaderProvider); } else { - return readValue(type, loader, /* clazz */ null); + return readValue(type, getClassLoader(loaderProvider), /* clazz */ null); } } + @Nullable + private static ClassLoader getClassLoader(@Nullable ClassLoaderProvider loaderProvider) { + return loaderProvider == null ? null : loaderProvider.getClassLoader(); + } + private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> { /** @@ -4680,7 +4685,12 @@ public final class Parcel { private final int mPosition; private final int mLength; private final int mType; - @Nullable private final ClassLoader mLoader; + // this member is set when a bundle that includes a LazyValue is unparceled. But it is used + // when apply method is called. Between these 2 events, the bundle's ClassLoader could have + // changed. Let the bundle be a ClassLoaderProvider allows the bundle provides its current + // ClassLoader at the time apply method is called. + @NonNull + private final ClassLoaderProvider mLoaderProvider; @Nullable private Object mObject; /** @@ -4691,12 +4701,13 @@ public final class Parcel { */ @Nullable private volatile Parcel mSource; - LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) { + LazyValue(Parcel source, int position, int length, int type, + @NonNull ClassLoaderProvider loaderProvider) { mSource = requireNonNull(source); mPosition = position; mLength = length; mType = type; - mLoader = loader; + mLoaderProvider = loaderProvider; } @Override @@ -4709,7 +4720,8 @@ public final class Parcel { int restore = source.dataPosition(); try { source.setDataPosition(mPosition); - mObject = source.readValue(mLoader, clazz, itemTypes); + mObject = source.readValue(mLoaderProvider.getClassLoader(), clazz, + itemTypes); } finally { source.setDataPosition(restore); } @@ -4782,7 +4794,8 @@ public final class Parcel { return Objects.equals(mObject, value.mObject); } // Better safely fail here since this could mean we get different objects. - if (!Objects.equals(mLoader, value.mLoader)) { + if (!Objects.equals(mLoaderProvider.getClassLoader(), + value.mLoaderProvider.getClassLoader())) { return false; } // Otherwise compare metadata prior to comparing payload. @@ -4796,10 +4809,24 @@ public final class Parcel { @Override public int hashCode() { // Accessing mSource first to provide memory barrier for mObject - return Objects.hash(mSource == null, mObject, mLoader, mType, mLength); + return Objects.hash(mSource == null, mObject, mLoaderProvider.getClassLoader(), mType, + mLength); } } + /** + * Provides a ClassLoader. + * @hide + */ + public interface ClassLoaderProvider { + /** + * Returns a ClassLoader. + * + * @return ClassLoader + */ + ClassLoader getClassLoader(); + } + /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { // Avoids allocating Class[0] array @@ -5537,8 +5564,8 @@ public final class Parcel { } private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, - int size, @Nullable ClassLoader loader) { - readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader); + int size, @Nullable ClassLoaderProvider loaderProvider) { + readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider, null); } /** @@ -5548,17 +5575,17 @@ public final class Parcel { * @param lazy Whether to populate the map with lazy {@link Function} objects for * length-prefixed values. See {@link Parcel#readLazyValue(ClassLoader)} for more * details. - * @return a count of the lazy values in the map + * @param lazyValueCount number of lazy values added here * @hide */ - int readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, - boolean lazy, @Nullable ClassLoader loader) { - int lazyValues = 0; + void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, + boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) { while (size > 0) { String key = readString(); - Object value = (lazy) ? readLazyValue(loader) : readValue(loader); + Object value = (lazy) ? readLazyValue(loaderProvider) : readValue( + getClassLoader(loaderProvider)); if (value instanceof LazyValue) { - lazyValues++; + lazyValueCount[0]++; } if (sorted) { map.append(key, value); @@ -5570,7 +5597,6 @@ public final class Parcel { if (sorted) { map.validate(); } - return lazyValues; } /** @@ -5578,12 +5604,12 @@ public final class Parcel { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal, - @Nullable ClassLoader loader) { + @Nullable ClassLoaderProvider loaderProvider) { final int N = readInt(); if (N < 0) { return; } - readArrayMapInternal(outVal, N, loader); + readArrayMapInternal(outVal, N, loaderProvider); } /** diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 907d96834857..0c5d9e97a77d 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -1221,6 +1221,17 @@ public class Process { */ public static final native int[] getExclusiveCores(); + + /** + * Get the CPU affinity masks from sched_getaffinity. + * + * @param tid The identifier of the thread/process to get the sched affinity. + * @return an array of CPU affinity masks, of which the size will be dynamic and just enough to + * include all bit masks for all currently online and possible CPUs of the device. + * @hide + */ + public static final native long[] getSchedAffinity(int tid); + /** * Set the priority of the calling thread, based on Linux priorities. See * {@link #setThreadPriority(int, int)} for more information. diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java index e2169925fdd3..289b98c9b1d4 100644 --- a/core/java/android/os/TestLooperManager.java +++ b/core/java/android/os/TestLooperManager.java @@ -41,6 +41,7 @@ public class TestLooperManager { private boolean mReleased; private boolean mLooperBlocked; + private final boolean mLooperIsMyLooper; /** * @hide @@ -54,8 +55,11 @@ public class TestLooperManager { } mLooper = looper; mQueue = mLooper.getQueue(); - // Post a message that will keep the looper blocked as long as we are dispatching. - new Handler(looper).post(new LooperHolder()); + mLooperIsMyLooper = Looper.myLooper() == looper; + if (!mLooperIsMyLooper) { + // Post a message that will keep the looper blocked as long as we are dispatching. + new Handler(looper).post(new LooperHolder()); + } } /** @@ -82,7 +86,7 @@ public class TestLooperManager { public Message next() { // Wait for the looper block to come up, to make sure we don't accidentally get // the message for the block. - while (!mLooperBlocked) { + while (!mLooperIsMyLooper && !mLooperBlocked) { synchronized (this) { try { wait(); @@ -114,9 +118,6 @@ public class TestLooperManager { * should be executed by this queue. * If the queue is empty or no messages are deliverable, returns null. * This method never blocks. - * - * <p>Callers should always call {@link #recycle(Message)} on the message when all interactions - * with it have completed. */ @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY) @SuppressWarnings("AutoBoxing") // box the primitive long, or return null to indicate no value @@ -165,6 +166,9 @@ public class TestLooperManager { // This is being called from the thread it should be executed on, we can just dispatch. message.target.dispatchMessage(message); } else { + if (mLooperIsMyLooper) { + throw new RuntimeException("Cannot call execute from non Looper thread"); + } MessageExecution execution = new MessageExecution(); execution.m = message; synchronized (execution) { diff --git a/core/java/android/os/health/OWNERS b/core/java/android/os/health/OWNERS index 6045344126c0..26fc8fae8f45 100644 --- a/core/java/android/os/health/OWNERS +++ b/core/java/android/os/health/OWNERS @@ -2,3 +2,6 @@ dplotnikov@google.com mwachens@google.com + +# for headroom API only +xwxw@google.com
\ No newline at end of file diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java index a95ce7914d8b..c7778dee5ebe 100644 --- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java +++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java @@ -22,7 +22,8 @@ import android.annotation.TestApi; import android.os.VibrationEffect; import android.util.Xml; -import com.android.internal.vibrator.persistence.VibrationEffectXmlSerializer; +import com.android.internal.vibrator.persistence.LegacyVibrationEffectXmlSerializer; +import com.android.internal.vibrator.persistence.VibrationEffectSerializer; import com.android.internal.vibrator.persistence.XmlConstants; import com.android.internal.vibrator.persistence.XmlSerializedVibration; import com.android.internal.vibrator.persistence.XmlSerializerException; @@ -123,7 +124,13 @@ public final class VibrationXmlSerializer { } try { - serializedVibration = VibrationEffectXmlSerializer.serialize(effect, serializerFlags); + if (android.os.vibrator.Flags.normalizedPwleEffects()) { + serializedVibration = VibrationEffectSerializer.serialize(effect, + serializerFlags); + } else { + serializedVibration = LegacyVibrationEffectXmlSerializer.serialize(effect, + serializerFlags); + } XmlValidator.checkSerializedVibration(serializedVibration, effect); } catch (XmlSerializerException e) { // Serialization failed or created incomplete representation, fail before writing. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index cf0e90fb43ce..c3a49305af87 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8950,6 +8950,18 @@ public final class Settings { "high_text_contrast_enabled"; /** + * Setting that specifies the status of the High Contrast Text + * rectangle refresh's one-time prompt. + * 0 = UNKNOWN + * 1 = PROMPT_SHOWN + * 2 = PROMPT_UNNECESSARY + * + * @hide + */ + public static final String ACCESSIBILITY_HCT_RECT_PROMPT_STATUS = + "accessibility_hct_rect_prompt_status"; + + /** * The color contrast, float in [-1, 1], 1 being the highest contrast. * * @hide diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java index 0ce040d7f862..d42ec7c71830 100644 --- a/core/java/android/service/autofill/augmented/FillWindow.java +++ b/core/java/android/service/autofill/augmented/FillWindow.java @@ -17,6 +17,7 @@ package android.service.autofill.augmented; import static android.service.autofill.augmented.AugmentedAutofillService.sDebug; import static android.service.autofill.augmented.AugmentedAutofillService.sVerbose; +import static android.service.autofill.Flags.addAccessibilityTitleForAugmentedAutofillDropdown; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -36,6 +37,7 @@ import android.view.WindowManager; import android.view.autofill.IAutofillWindowPresenter; import com.android.internal.annotations.GuardedBy; +import com.android.internal.R; import dalvik.system.CloseGuard; @@ -208,6 +210,12 @@ public final class FillWindow implements AutoCloseable { if (mWm != null && mFillView != null) { try { p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; + if (addAccessibilityTitleForAugmentedAutofillDropdown()) { + p.accessibilityTitle = + mFillView + .getContext() + .getString(R.string.autofill_picker_accessibility_title); + } if (!mShowing) { mWm.addView(mFillView, p); mShowing = true; diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 089b5c256b6e..6c50b5f945a5 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.flags.Flags.bufferStuffingRecovery; import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; @@ -965,22 +966,24 @@ public final class Choreographer { // Evaluate if buffer stuffing recovery needs to start or end, and // what actions need to be taken for recovery. - switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) { - case NONE: - // Without buffer stuffing recovery, offsetFrameTimeNanos is - // synonymous with frameTimeNanos. - break; - case OFFSET: - // Add animation offset. Used to update frame timeline with - // offset before jitter is calculated. - offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos; - break; - case DELAY_FRAME: - // Intentional frame delay to help reduce queued buffer count. - scheduleVsyncLocked(); - return; - default: - break; + if (bufferStuffingRecovery()) { + switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) { + case NONE: + // Without buffer stuffing recovery, offsetFrameTimeNanos is + // synonymous with frameTimeNanos. + break; + case OFFSET: + // Add animation offset. Used to update frame timeline with + // offset before jitter is calculated. + offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos; + break; + case DELAY_FRAME: + // Intentional frame delay to help reduce queued buffer count. + scheduleVsyncLocked(); + return; + default: + break; + } } try { diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0c8a0d60a96a..ca0959af3ff8 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1597,7 +1597,9 @@ public final class Display { // Although we only care about the HDR/SDR ratio changing, that can also come in the // form of the larger DISPLAY_CHANGED event mGlobal.registerDisplayListener(toRegister, executor, - DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + DisplayManagerGlobal + .INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | DisplayManagerGlobal .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED, ActivityThread.currentPackageName()); diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index ba098eb53246..e75b1b0bd17a 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -447,7 +447,18 @@ public final class DisplayInfo implements Parcelable { } public boolean equals(DisplayInfo other) { - return other != null + return equals(other, /* compareRefreshRate */ true); + } + + /** + * Compares if the two DisplayInfo objects are equal or not + * @param other The other DisplayInfo against which the comparison is to be done + * @param compareRefreshRate Indicates if the refresh rate is also to be considered in + * comparison + * @return + */ + public boolean equals(DisplayInfo other, boolean compareRefreshRate) { + boolean isEqualWithoutRefreshRate = other != null && layerStack == other.layerStack && flags == other.flags && type == other.type @@ -466,7 +477,6 @@ public final class DisplayInfo implements Parcelable { && logicalHeight == other.logicalHeight && Objects.equals(displayCutout, other.displayCutout) && rotation == other.rotation - && modeId == other.modeId && hasArrSupport == other.hasArrSupport && Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate) && Arrays.equals(supportedRefreshRates, other.supportedRefreshRates) @@ -490,7 +500,6 @@ public final class DisplayInfo implements Parcelable { && ownerUid == other.ownerUid && Objects.equals(ownerPackageName, other.ownerPackageName) && removeMode == other.removeMode - && getRefreshRate() == other.getRefreshRate() && brightnessMinimum == other.brightnessMinimum && brightnessMaximum == other.brightnessMaximum && brightnessDefault == other.brightnessDefault @@ -504,6 +513,13 @@ public final class DisplayInfo implements Parcelable { && Objects.equals( thermalBrightnessThrottlingDataId, other.thermalBrightnessThrottlingDataId) && canHostTasks == other.canHostTasks; + + if (compareRefreshRate) { + return isEqualWithoutRefreshRate + && (getRefreshRate() == other.getRefreshRate()) + && (modeId == other.modeId); + } + return isEqualWithoutRefreshRate; } @Override diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index dd9a95e58bd1..833f2d98554e 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -22,7 +22,6 @@ import static android.graphics.Matrix.MSKEW_X; import static android.graphics.Matrix.MSKEW_Y; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; -import static android.view.flags.Flags.bufferStuffingRecovery; import static android.view.SurfaceControlProto.HASH_CODE; import static android.view.SurfaceControlProto.LAYER_ID; import static android.view.SurfaceControlProto.NAME; @@ -4671,8 +4670,7 @@ public final class SurfaceControl implements Parcelable { * Sets the importance the layer's contents has to the app's user experience. * <p> * When a two layers within the same app are competing for a limited rendering resource, - * the priority will determine which layer gets access to the resource. The lower the - * priority, the more likely the layer will get access to the resource. + * the layer with the highest priority will gets access to the resource. * <p> * Resources managed by this priority: * <ul> @@ -5119,11 +5117,9 @@ public final class SurfaceControl implements Parcelable { */ @NonNull public Transaction setRecoverableFromBufferStuffing(@NonNull SurfaceControl sc) { - if (bufferStuffingRecovery()) { - checkPreconditions(sc); - nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING, - RECOVERABLE_FROM_BUFFER_STUFFING); - } + checkPreconditions(sc); + nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING, + RECOVERABLE_FROM_BUFFER_STUFFING); return this; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1d27574eca8c..ec1650947aec 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1828,7 +1828,8 @@ public final class ViewRootImpl implements ViewParent, | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_STATE | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED : DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; DisplayManagerGlobal .getInstance() diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 049ad20fd992..294e5da1edd1 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -233,6 +233,16 @@ flag { } flag { + name: "restore_a11y_secure_settings_on_hsum_device" + namespace: "accessibility" + description: "Grab the a11y settings and send the settings restored broadcast for current visible foreground user" + bug: "381294327" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "supplemental_description" namespace: "accessibility" description: "Feature flag for supplemental description api" diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java index 70c899f1efc7..8baa55f8e377 100644 --- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java @@ -142,6 +142,11 @@ final class ChildContentCaptureSession extends ContentCaptureSession { } @Override + void internalNotifySessionFlushEvent(int sessionId) { + getMainCaptureSession().internalNotifySessionFlushEvent(sessionId); + } + + @Override boolean isContentCaptureEnabled() { return getMainCaptureSession().isContentCaptureEnabled(); } diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index db4ac5de0b49..efd39163c3b8 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -18,7 +18,9 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; import static android.view.contentcapture.ContentCaptureManager.DEBUG; import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; +import static android.view.contentcapture.flags.Flags.FLAG_CCAPI_BAKLAVA_ENABLED; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -137,6 +139,12 @@ public final class ContentCaptureEvent implements Parcelable { */ public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10; + /** + * Called to flush a semantics meaningful view changes status to Intelligence Service. + */ + @FlaggedApi(FLAG_CCAPI_BAKLAVA_ENABLED) + public static final int TYPE_SESSION_FLUSH = 11; + /** @hide */ @IntDef(prefix = { "TYPE_" }, value = { TYPE_VIEW_APPEARED, @@ -149,6 +157,7 @@ public final class ContentCaptureEvent implements Parcelable { TYPE_SESSION_RESUMED, TYPE_VIEW_INSETS_CHANGED, TYPE_WINDOW_BOUNDS_CHANGED, + TYPE_SESSION_FLUSH, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType{} @@ -697,6 +706,8 @@ public final class ContentCaptureEvent implements Parcelable { return "VIEW_INSETS_CHANGED"; case TYPE_WINDOW_BOUNDS_CHANGED: return "TYPE_WINDOW_BOUNDS_CHANGED"; + case TYPE_SESSION_FLUSH: + return "TYPE_SESSION_FLUSH"; default: return "UKNOWN_TYPE: " + type; } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 0ca36ba28e3a..9aeec20ec9b7 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -19,8 +19,10 @@ import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID; +import static android.view.contentcapture.flags.Flags.FLAG_CCAPI_BAKLAVA_ENABLED; import android.annotation.CallSuper; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -548,6 +550,35 @@ public abstract class ContentCaptureSession implements AutoCloseable { abstract void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets); + /** + * Flushes an internal buffer of UI events and signals System Intelligence (SI) that a + * semantically meaningful state has been reached. SI uses this signal to potentially + * rebuild the view hierarchy and understand the current state of the UI. + * + * <p>UI events are often batched together for performance reasons. A semantic batch + * represents a series of events that, when applied sequentially, result in a + * meaningful and complete UI state. + * + * <p>It is crucial to call {@code flush()} after completing a semantic batch to ensure + * SI can accurately reconstruct the view hierarchy. + * + * <p><b>Premature Flushing:</b> Calling {@code flush()} within a semantic batch may + * lead to SI failing to rebuild the view hierarchy correctly. This could manifest as + * incorrect ordering of sibling nodes. + * + * <p><b>Delayed Flushing:</b> While not immediately flushing after a semantic batch is + * generally safe, it's recommended to do so as soon as possible. In the worst-case + * scenario where a {@code flush()} is never called, SI will attempt to process the + * events after a short delay based on view appearance and disappearance events. + */ + @FlaggedApi(FLAG_CCAPI_BAKLAVA_ENABLED) + public void flush() { + internalNotifySessionFlushEvent(mId); + } + + /** @hide */ + abstract void internalNotifySessionFlushEvent(int sessionId); + /** @hide */ public void notifyViewTreeEvent(boolean started) { internalNotifyViewTreeEvent(mId, started); diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index eb827dd5258d..2fb78c038ca2 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -17,6 +17,7 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FLUSH; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED; import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED; @@ -623,6 +624,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override public void flush(@FlushReason int reason) { + // TODO: b/380381249 renaming the internal APIs to prevent confusions between this and the + // public API. runOnContentCaptureThread(() -> flushImpl(reason)); } @@ -890,6 +893,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { enqueueEvent(event); } + @Override + void internalNotifySessionFlushEvent(int sessionId) { + final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_FLUSH); + enqueueEvent(event, FORCE_FLUSH); + } + private List<ContentCaptureEvent> clearBufferEvents() { final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>(); ContentCaptureEvent event; diff --git a/core/java/android/view/contentcapture/OWNERS b/core/java/android/view/contentcapture/OWNERS index 9ac273f515e7..30f4cae4bf19 100644 --- a/core/java/android/view/contentcapture/OWNERS +++ b/core/java/android/view/contentcapture/OWNERS @@ -1,4 +1,5 @@ # Bug component: 544200 -hackz@google.com -shivanker@google.com +dariofreni@google.com +klikli@google.com +shikhamalhotra@google.com diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig index 416a877d87ab..20d193ea2351 100644 --- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig +++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig @@ -7,3 +7,10 @@ flag { description: "Feature flag for running content capture tasks on background thread" bug: "309411951" } + +flag { + name: "ccapi_baklava_enabled" + namespace: "machine_learning" + description: "Feature flag for baklava content capture API" + bug: "309411951" +} diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index ab5969bb381c..14b208aecf5c 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -1788,4 +1788,17 @@ public abstract class WebSettings { * @see #setDisabledActionModeMenuItems */ public static final int MENU_ITEM_PROCESS_TEXT = 1 << 2; + + /** + * Enable CHIPS for webview. + * This provides a means to check if partitioned cookies are enabled by default. + * CHIPS will only be enabled by default for apps targeting Android B or above. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM) + @FlaggedApi(android.webkit.Flags.FLAG_ENABLE_CHIPS) + @SystemApi + public static final long ENABLE_CHIPS = 380890146L; } diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig index c5176a2f1f15..16cbb8abc9d6 100644 --- a/core/java/android/webkit/flags.aconfig +++ b/core/java/android/webkit/flags.aconfig @@ -36,6 +36,14 @@ flag { } flag { + name: "enable_chips" + is_exported: true + namespace: "webview" + description: "New feature enable CHIPS for webview" + bug: "359448044" +} + +flag { name: "file_system_access" is_exported: true namespace: "webview" diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index 289c5cf4bf85..be69d3da3874 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -86,7 +86,9 @@ public enum DesktopModeFlags { ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX( Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, false), ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX( - Flags::enableDesktopAppLaunchTransitionsBugfix, false); + Flags::enableDesktopAppLaunchTransitionsBugfix, false), + INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC( + Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true); private static final String TAG = "DesktopModeFlagsUtil"; // Function called to obtain aconfig flag value. diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index 5397da11eb36..435c8c79122f 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -44,20 +44,16 @@ import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_AT import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; import static com.android.internal.policy.DecorView.getNavigationBarRect; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityThread; import android.content.Context; import android.graphics.Canvas; -import android.graphics.GraphicBuffer; import android.graphics.Paint; -import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.IBinder; import android.util.Log; -import android.view.InsetsState; import android.view.SurfaceControl; import android.view.ViewGroup; import android.view.WindowInsets; @@ -66,7 +62,6 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.DecorView; -import com.android.window.flags.Flags; /** * Utils class to help draw a snapshot on a surface. @@ -103,8 +98,6 @@ public class SnapshotDrawerUtils { | FLAG_SECURE | FLAG_DIM_BEHIND; - private static final Paint sBackgroundPaint = new Paint(); - /** * The internal object to hold the surface and drawing on it. */ @@ -115,54 +108,29 @@ public class SnapshotDrawerUtils { private final TaskSnapshot mSnapshot; private final CharSequence mTitle; - private SystemBarBackgroundPainter mSystemBarBackgroundPainter; - private final Rect mFrame = new Rect(); - private final Rect mSystemBarInsets = new Rect(); private final int mSnapshotW; private final int mSnapshotH; - private boolean mSizeMismatch; + private final int mContainerW; + private final int mContainerH; public SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot, - CharSequence title) { + Rect windowBounds, CharSequence title) { mRootSurface = rootSurface; mSnapshot = snapshot; mTitle = title; final HardwareBuffer hwBuffer = snapshot.getHardwareBuffer(); mSnapshotW = hwBuffer.getWidth(); mSnapshotH = hwBuffer.getHeight(); + mContainerW = windowBounds.width(); + mContainerH = windowBounds.height(); } - /** - * Initiate system bar painter to draw the system bar background. - */ - @VisibleForTesting - public void initiateSystemBarPainter(int windowFlags, int windowPrivateFlags, - int appearance, ActivityManager.TaskDescription taskDescription, - @WindowInsets.Type.InsetsType int requestedVisibleTypes) { - mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags, - windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes); - int backgroundColor = taskDescription.getBackgroundColor(); - sBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE); - } - - /** - * Set frame size that the snapshot should fill. It is the bounds of a task or activity. - */ - @VisibleForTesting - public void setFrames(Rect frame, Rect systemBarInsets) { - mFrame.set(frame); + private void drawSnapshot(boolean releaseAfterDraw) { final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); - mSizeMismatch = (mFrame.width() != mSnapshotW || mFrame.height() != mSnapshotH) + final boolean sizeMismatch = mContainerW != mSnapshotW || mContainerH != mSnapshotH || letterboxInsets.left != 0 || letterboxInsets.top != 0; - if (!Flags.drawSnapshotAspectRatioMatch() && systemBarInsets != null) { - mSystemBarInsets.set(systemBarInsets); - mSystemBarBackgroundPainter.setInsets(systemBarInsets); - } - } - - private void drawSnapshot(boolean releaseAfterDraw) { - Log.v(TAG, "Drawing snapshot surface sizeMismatch=" + mSizeMismatch); - if (mSizeMismatch) { + Log.v(TAG, "Drawing snapshot surface sizeMismatch=" + sizeMismatch); + if (sizeMismatch) { // The dimensions of the buffer and the window don't match, so attaching the buffer // will fail. Better create a child window with the exact dimensions and fill the // parent window with the background color! @@ -189,11 +157,6 @@ public class SnapshotDrawerUtils { private void drawSizeMismatchSnapshot() { final HardwareBuffer buffer = mSnapshot.getHardwareBuffer(); - // We consider nearly matched dimensions as there can be rounding errors and the user - // won't notice very minute differences from scaling one dimension more than the other - boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshotW, mSnapshotH) - && !Flags.drawSnapshotAspectRatioMatch(); - // Keep a reference to it such that it doesn't get destroyed when finalized. SurfaceControl childSurfaceControl = new SurfaceControl.Builder() .setName(mTitle + " - task-snapshot-surface") @@ -203,166 +166,28 @@ public class SnapshotDrawerUtils { .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot") .build(); - final Rect frame; final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); float offsetX = letterboxInsets.left; float offsetY = letterboxInsets.top; // We can just show the surface here as it will still be hidden as the parent is // still hidden. mTransaction.show(childSurfaceControl); - if (aspectRatioMismatch) { - Rect crop = null; - if (letterboxInsets.left != 0 || letterboxInsets.top != 0 - || letterboxInsets.right != 0 || letterboxInsets.bottom != 0) { - // Clip off letterbox. - crop = calculateSnapshotCrop(letterboxInsets); - // If the snapshot can cover the frame, then no need to draw background. - aspectRatioMismatch = !isAspectRatioMatch(mFrame, crop); - } - // if letterbox doesn't match window frame, try crop by content insets - if (aspectRatioMismatch) { - // Clip off ugly navigation bar. - final Rect contentInsets = mSnapshot.getContentInsets(); - crop = calculateSnapshotCrop(contentInsets); - offsetX = contentInsets.left; - offsetY = contentInsets.top; - } - frame = calculateSnapshotFrame(crop); - mTransaction.setCrop(childSurfaceControl, crop); - } else { - frame = null; - } // Align the snapshot with content area. if (offsetX != 0f || offsetY != 0f) { mTransaction.setPosition(childSurfaceControl, - -offsetX * mFrame.width() / mSnapshot.getTaskSize().x, - -offsetY * mFrame.height() / mSnapshot.getTaskSize().y); + -offsetX * mContainerW / mSnapshot.getTaskSize().x, + -offsetY * mContainerH / mSnapshot.getTaskSize().y); } // Scale the mismatch dimensions to fill the target frame. - final float scaleX = (float) mFrame.width() / mSnapshotW; - final float scaleY = (float) mFrame.height() / mSnapshotH; + final float scaleX = (float) mContainerW / mSnapshotW; + final float scaleY = (float) mContainerH / mSnapshotH; mTransaction.setScale(childSurfaceControl, scaleX, scaleY); mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace()); mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer()); - - if (aspectRatioMismatch) { - GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(), - PixelFormat.RGBA_8888, - GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER - | GraphicBuffer.USAGE_SW_WRITE_RARELY); - final Canvas c = background != null ? background.lockCanvas() : null; - if (c == null) { - Log.e(TAG, "Unable to draw snapshot: failed to allocate graphic buffer for " - + mTitle); - mTransaction.clear(); - childSurfaceControl.release(); - return; - } - drawBackgroundAndBars(c, frame); - background.unlockCanvasAndPost(c); - mTransaction.setBuffer(mRootSurface, - HardwareBuffer.createFromGraphicBuffer(background)); - } mTransaction.apply(); childSurfaceControl.release(); } - - /** - * Calculates the snapshot crop in snapshot coordinate space. - * @param insets Content insets or Letterbox insets - * @return crop rect in snapshot coordinate space. - */ - @VisibleForTesting - public Rect calculateSnapshotCrop(@NonNull Rect insets) { - final Rect rect = new Rect(); - rect.set(0, 0, mSnapshotW, mSnapshotH); - - final float scaleX = (float) mSnapshotW / mSnapshot.getTaskSize().x; - final float scaleY = (float) mSnapshotH / mSnapshot.getTaskSize().y; - - // Let's remove all system decorations except the status bar, but only if the task is at - // the very top of the screen. - final boolean isTop = mFrame.top == 0; - rect.inset((int) (insets.left * scaleX), - isTop ? 0 : (int) (insets.top * scaleY), - (int) (insets.right * scaleX), - (int) (insets.bottom * scaleY)); - return rect; - } - - /** - * Calculates the snapshot frame in window coordinate space from crop. - * - * @param crop rect that is in snapshot coordinate space. - */ - @VisibleForTesting - public Rect calculateSnapshotFrame(Rect crop) { - final float scaleX = (float) mSnapshotW / mSnapshot.getTaskSize().x; - final float scaleY = (float) mSnapshotH / mSnapshot.getTaskSize().y; - - // Rescale the frame from snapshot to window coordinate space - final Rect frame = new Rect(0, 0, - (int) (crop.width() / scaleX + 0.5f), - (int) (crop.height() / scaleY + 0.5f) - ); - - // However, we also need to make space for the navigation bar on the left side. - frame.offset(mSystemBarInsets.left, 0); - return frame; - } - - /** - * Draw status bar and navigation bar background. - */ - @VisibleForTesting - public void drawBackgroundAndBars(Canvas c, Rect frame) { - final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight(); - final boolean fillHorizontally = c.getWidth() > frame.right; - final boolean fillVertically = c.getHeight() > frame.bottom; - if (fillHorizontally) { - c.drawRect(frame.right, alpha(mSystemBarBackgroundPainter.mStatusBarColor) == 0xFF - ? statusBarHeight : 0, c.getWidth(), fillVertically - ? frame.bottom : c.getHeight(), sBackgroundPaint); - } - if (fillVertically) { - c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), sBackgroundPaint); - } - mSystemBarBackgroundPainter.drawDecors(c, frame); - } - - /** - * Ask system bar background painter to draw status bar background. - */ - @VisibleForTesting - public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) { - mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame, - mSystemBarBackgroundPainter.getStatusBarColorViewHeight()); - } - - /** - * Ask system bar background painter to draw navigation bar background. - */ - @VisibleForTesting - public void drawNavigationBarBackground(Canvas c) { - mSystemBarBackgroundPainter.drawNavigationBarBackground(c); - } - } - - private static boolean isAspectRatioMatch(Rect frame, int w, int h) { - if (frame.isEmpty()) { - return false; - } - return Math.abs(((float) w / h) - ((float) frame.width() / frame.height())) <= 0.01f; - } - - private static boolean isAspectRatioMatch(Rect frame1, Rect frame2) { - if (frame1.isEmpty() || frame2.isEmpty()) { - return false; - } - return Math.abs( - ((float) frame2.width() / frame2.height()) - - ((float) frame1.width() / frame1.height())) <= 0.01f; } /** @@ -383,28 +208,15 @@ public class SnapshotDrawerUtils { /** * Help method to draw the snapshot on a surface. */ - public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp, + public static void drawSnapshotOnSurface(WindowManager.LayoutParams lp, SurfaceControl rootSurface, TaskSnapshot snapshot, - Rect windowBounds, InsetsState topWindowInsetsState, - boolean releaseAfterDraw) { + Rect windowBounds, boolean releaseAfterDraw) { if (windowBounds.isEmpty()) { Log.e(TAG, "Unable to draw snapshot on an empty windowBounds"); return; } final SnapshotSurface drawSurface = new SnapshotSurface( - rootSurface, snapshot, lp.getTitle()); - final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch() - ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams; - final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; - final ActivityManager.TaskDescription taskDescription = - getOrCreateTaskDescription(runningTaskInfo); - Rect systemBarInsets = null; - if (!Flags.drawSnapshotAspectRatioMatch()) { - drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags, - attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes); - systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState); - } - drawSurface.setFrames(windowBounds, systemBarInsets); + rootSurface, snapshot, windowBounds, lp.getTitle()); drawSurface.drawSnapshot(releaseAfterDraw); } @@ -414,10 +226,8 @@ public class SnapshotDrawerUtils { public static WindowManager.LayoutParams createLayoutParameters(StartingWindowInfo info, CharSequence title, @WindowManager.LayoutParams.WindowType int windowType, int pixelFormat, IBinder token) { - final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch() - ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams; - final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams; - if (attrs == null || mainWindowParams == null) { + final WindowManager.LayoutParams attrs = info.mainWindowLayoutParams; + if (attrs == null) { Log.w(TAG, "unable to create taskSnapshot surface "); return null; } @@ -427,9 +237,9 @@ public class SnapshotDrawerUtils { final int windowFlags = attrs.flags; final int windowPrivateFlags = attrs.privateFlags; - layoutParams.packageName = mainWindowParams.packageName; - layoutParams.windowAnimations = mainWindowParams.windowAnimations; - layoutParams.dimAmount = mainWindowParams.dimAmount; + layoutParams.packageName = attrs.packageName; + layoutParams.windowAnimations = attrs.windowAnimations; + layoutParams.dimAmount = attrs.dimAmount; layoutParams.type = windowType; layoutParams.format = pixelFormat; layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES) @@ -460,14 +270,6 @@ public class SnapshotDrawerUtils { return layoutParams; } - static Rect getSystemBarInsets(Rect frame, @Nullable InsetsState state) { - if (state == null) { - return new Rect(); - } - return state.calculateInsets(frame, WindowInsets.Type.systemBars(), - false /* ignoreVisibility */).toRect(); - } - /** * Helper class to draw the background of the system bars in regions the task snapshot isn't * filling the window. diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index 72df343a2dbe..80ccbddbd1d9 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -27,7 +27,6 @@ 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; @@ -100,20 +99,6 @@ public final class StartingWindowInfo implements Parcelable { public ActivityInfo targetActivityInfo; /** - * InsetsState of TopFullscreenOpaqueWindow - * @hide - */ - @Nullable - public InsetsState topOpaqueWindowInsetsState; - - /** - * LayoutParams of TopFullscreenOpaqueWindow - * @hide - */ - @Nullable - public WindowManager.LayoutParams topOpaqueWindowLayoutParams; - - /** * LayoutParams of MainWindow * @hide */ @@ -263,8 +248,6 @@ public final class StartingWindowInfo implements Parcelable { taskBounds.writeToParcel(dest, flags); dest.writeTypedObject(targetActivityInfo, flags); dest.writeInt(startingWindowTypeParameter); - dest.writeTypedObject(topOpaqueWindowInsetsState, flags); - dest.writeTypedObject(topOpaqueWindowLayoutParams, flags); dest.writeTypedObject(mainWindowLayoutParams, flags); dest.writeInt(splashScreenThemeResId); dest.writeBoolean(isKeyguardOccluded); @@ -280,9 +263,6 @@ public final class StartingWindowInfo implements Parcelable { taskBounds.readFromParcel(source); targetActivityInfo = source.readTypedObject(ActivityInfo.CREATOR); startingWindowTypeParameter = source.readInt(); - topOpaqueWindowInsetsState = source.readTypedObject(InsetsState.CREATOR); - topOpaqueWindowLayoutParams = source.readTypedObject( - WindowManager.LayoutParams.CREATOR); mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR); splashScreenThemeResId = source.readInt(); isKeyguardOccluded = source.readBoolean(); @@ -302,8 +282,6 @@ public final class StartingWindowInfo implements Parcelable { + " topActivityType=" + taskInfo.topActivityType + " preferredStartingWindowType=" + Integer.toHexString(startingWindowTypeParameter) - + " insetsState=" + topOpaqueWindowInsetsState - + " topWindowLayoutParams=" + topOpaqueWindowLayoutParams + " mainWindowLayoutParams=" + mainWindowLayoutParams + " splashScreenThemeResId " + Integer.toHexString(splashScreenThemeResId); } diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java index 1ec05b65861d..11019324acd8 100644 --- a/core/java/android/window/WindowTokenClientController.java +++ b/core/java/android/window/WindowTokenClientController.java @@ -35,6 +35,7 @@ import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; /** * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch @@ -137,7 +138,9 @@ public class WindowTokenClientController { // is initialized later, the SystemUiContext will start reporting from // DisplayContent#registerSystemUiContext, and WindowTokenClientController can report // the Configuration to the correct client. - recordWindowContextToken(client); + if (Flags.trackSystemUiContextBeforeWms()) { + recordWindowContextToken(client); + } return false; } final WindowContextInfo info; diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 1707e61b28e4..3b77b1f65dac 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -16,6 +16,17 @@ flag { } flag { + name: "include_top_transparent_fullscreen_task_in_desktop_heuristic" + namespace: "lse_desktop_experience" + description: "Whether to include any top transparent fullscreen task launched in desktop /n" + "mode in the heuristic for if desktop windowing is showing or not." + bug: "379543275" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_windowing_dynamic_initial_bounds" namespace: "lse_desktop_experience" description: "Enables new initial bounds for desktop windowing which adjust depending on app constraints" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 30668a6c6b1d..9d11d149b0ed 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -243,17 +243,6 @@ flag { } flag { - name: "draw_snapshot_aspect_ratio_match" - namespace: "windowing_frontend" - description: "The aspect ratio should always match when drawing snapshot" - bug: "341020277" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "system_ui_post_animation_end" namespace: "windowing_frontend" description: "Run AnimatorListener#onAnimationEnd on next frame for SystemUI" diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index d0d4af6ea598..ae846441723a 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -127,3 +127,24 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "windowing_sdk" + name: "track_system_ui_context_before_wms" + description: "Keep track of SystemUiContext before WMS is initialized" + bug: "384428048" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "windowing_sdk" + name: "normalize_home_intent" + description: "To ensure home is started in correct intent" + bug: "378505461" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java index 0c2fd4bbd7ae..5d66b3c10197 100644 --- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java +++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java @@ -148,8 +148,8 @@ public class DisplayResolutionTracker { public void registerDisplayListener(DisplayManager.DisplayListener listener) { manager.registerDisplayListener(listener, handler, DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal - .INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE, ActivityThread.currentPackageName()); } diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index b56aadd366d5..c9c4be1e2c93 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -250,23 +250,22 @@ public class BatteryStatsHistory { private static class BatteryHistoryDirectory { private final File mDirectory; private final MonotonicClock mMonotonicClock; - private int mMaxHistoryFiles; + private int mMaxHistorySize; private final List<BatteryHistoryFile> mHistoryFiles = new ArrayList<>(); private final ReentrantLock mLock = new ReentrantLock(); private boolean mCleanupNeeded; - BatteryHistoryDirectory(File directory, MonotonicClock monotonicClock, - int maxHistoryFiles) { + BatteryHistoryDirectory(File directory, MonotonicClock monotonicClock, int maxHistorySize) { mDirectory = directory; mMonotonicClock = monotonicClock; - mMaxHistoryFiles = maxHistoryFiles; - if (mMaxHistoryFiles == 0) { - Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history"); + mMaxHistorySize = maxHistorySize; + if (mMaxHistorySize == 0) { + Slog.w(TAG, "mMaxHistorySize should not be zero when writing history"); } } - void setMaxHistoryFiles(int maxHistoryFiles) { - mMaxHistoryFiles = maxHistoryFiles; + void setMaxHistorySize(int maxHistorySize) { + mMaxHistorySize = maxHistorySize; cleanup(); } @@ -500,13 +499,14 @@ public class BatteryStatsHistory { oldest.atomicFile.delete(); } - // if there are more history files than allowed, delete oldest history files. - // mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and - // can be updated by DeviceConfig at run time. - while (mHistoryFiles.size() > mMaxHistoryFiles) { + // if there is more history stored than allowed, delete oldest history files. + int size = getSize(); + while (size > mMaxHistorySize) { BatteryHistoryFile oldest = mHistoryFiles.get(0); + int length = (int) oldest.atomicFile.getBaseFile().length(); oldest.atomicFile.delete(); mHistoryFiles.remove(0); + size -= length; } } finally { unlock(); @@ -595,19 +595,19 @@ public class BatteryStatsHistory { * Constructor * * @param systemDir typically /data/system - * @param maxHistoryFiles the largest number of history buffer files to keep + * @param maxHistorySize the largest amount of battery history to keep on disk * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps */ public BatteryStatsHistory(Parcel historyBuffer, File systemDir, - int maxHistoryFiles, int maxHistoryBufferSize, + int maxHistorySize, int maxHistoryBufferSize, HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, MonotonicClock monotonicClock, TraceDelegate tracer, EventLogger eventLogger) { - this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator, + this(historyBuffer, systemDir, maxHistorySize, maxHistoryBufferSize, stepDetailsCalculator, clock, monotonicClock, tracer, eventLogger, null); } private BatteryStatsHistory(@Nullable Parcel historyBuffer, @Nullable File systemDir, - int maxHistoryFiles, int maxHistoryBufferSize, + int maxHistorySize, int maxHistoryBufferSize, @NonNull HistoryStepDetailsCalculator stepDetailsCalculator, @NonNull Clock clock, @NonNull MonotonicClock monotonicClock, @NonNull TraceDelegate tracer, @NonNull EventLogger eventLogger, @Nullable BatteryStatsHistory writableHistory) { @@ -634,7 +634,7 @@ public class BatteryStatsHistory { mHistoryDir = writableHistory.mHistoryDir; } else if (systemDir != null) { mHistoryDir = new BatteryHistoryDirectory(new File(systemDir, HISTORY_DIR), - monotonicClock, maxHistoryFiles); + monotonicClock, maxHistorySize); mHistoryDir.load(); BatteryHistoryFile activeFile = mHistoryDir.getLastFile(); if (activeFile == null) { @@ -690,11 +690,11 @@ public class BatteryStatsHistory { } /** - * Changes the maximum number of history files to be kept. + * Changes the maximum amount of history to be kept on disk. */ - public void setMaxHistoryFiles(int maxHistoryFiles) { + public void setMaxHistorySize(int maxHistorySize) { if (mHistoryDir != null) { - mHistoryDir.setMaxHistoryFiles(maxHistoryFiles); + mHistoryDir.setMaxHistorySize(maxHistorySize); } } @@ -1175,6 +1175,13 @@ public class BatteryStatsHistory { } /** + * Returns the maximum storage size allocated to battery history. + */ + public int getMaxHistorySize() { + return mHistoryDir.mMaxHistorySize; + } + + /** * @return the total size of all history files and history buffer. */ public int getHistoryUsedSize() { diff --git a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java index 0e0098ebf074..efdc8ca694b8 100644 --- a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java +++ b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java @@ -61,7 +61,7 @@ public class MetricsLoggerWrapper { return; } int pid = Process.myPid(); - String processName = Application.getProcessName(); + String processName = Process.myProcessName(); Collection<NativeAllocationRegistry.Metrics> metrics = NativeAllocationRegistry.getMetrics(); int nMetrics = metrics.size(); diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 3e2f30118b2a..f14e1f63cdf6 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -236,4 +236,7 @@ interface IStatusBarService /** Shows rear display educational dialog */ void showRearDisplayDialog(int currentBaseState); + + /** Unbundle a categorized notification */ + void unbundleNotification(String key); } diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/LegacyVibrationEffectXmlSerializer.java index ebe34344c6f5..be30750ff281 100644 --- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java +++ b/core/java/com/android/internal/vibrator/persistence/LegacyVibrationEffectXmlSerializer.java @@ -53,7 +53,7 @@ import java.util.List; * * @hide */ -public final class VibrationEffectXmlSerializer { +public final class LegacyVibrationEffectXmlSerializer { /** * Creates a serialized representation of the input {@code vibration}. diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java index cd7dcfdac906..efc7e354995d 100644 --- a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java +++ b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java @@ -35,6 +35,7 @@ import com.android.modules.utils.TypedXmlSerializer; import java.io.IOException; import java.util.Arrays; +import java.util.function.BiConsumer; /** * Serialized representation of a waveform effect created via @@ -144,7 +145,7 @@ final class SerializedAmplitudeStepWaveform implements SerializedSegment { // Read all nested tag that is not a repeating tag as a waveform entry. while (XmlReader.readNextTagWithin(parser, outerDepth) && !TAG_REPEATING.equals(parser.getName())) { - parseWaveformEntry(parser, waveformBuilder); + parseWaveformEntry(parser, waveformBuilder::addDurationAndAmplitude); } // If found a repeating tag, read its content. @@ -162,6 +163,25 @@ final class SerializedAmplitudeStepWaveform implements SerializedSegment { return waveformBuilder.build(); } + static void parseWaveformEntry(TypedXmlPullParser parser, + BiConsumer<Integer, Integer> builder) throws XmlParserException, IOException { + XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENTRY); + XmlValidator.checkTagHasNoUnexpectedAttributes( + parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE); + + String rawAmplitude = parser.getAttributeValue(NAMESPACE, ATTRIBUTE_AMPLITUDE); + int amplitude = VALUE_AMPLITUDE_DEFAULT.equals(rawAmplitude) + ? VibrationEffect.DEFAULT_AMPLITUDE + : XmlReader.readAttributeIntInRange( + parser, ATTRIBUTE_AMPLITUDE, 0, VibrationEffect.MAX_AMPLITUDE); + int durationMs = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_DURATION_MS); + + builder.accept(durationMs, amplitude); + + // Consume tag + XmlReader.readEndTag(parser); + } + private static void parseRepeating(TypedXmlPullParser parser, Builder waveformBuilder) throws XmlParserException, IOException { XmlValidator.checkStartTag(parser, TAG_REPEATING); @@ -172,7 +192,7 @@ final class SerializedAmplitudeStepWaveform implements SerializedSegment { boolean hasEntry = false; int outerDepth = parser.getDepth(); while (XmlReader.readNextTagWithin(parser, outerDepth)) { - parseWaveformEntry(parser, waveformBuilder); + parseWaveformEntry(parser, waveformBuilder::addDurationAndAmplitude); hasEntry = true; } @@ -182,24 +202,5 @@ final class SerializedAmplitudeStepWaveform implements SerializedSegment { // Consume tag XmlReader.readEndTag(parser, TAG_REPEATING, outerDepth); } - - private static void parseWaveformEntry(TypedXmlPullParser parser, Builder waveformBuilder) - throws XmlParserException, IOException { - XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENTRY); - XmlValidator.checkTagHasNoUnexpectedAttributes( - parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE); - - String rawAmplitude = parser.getAttributeValue(NAMESPACE, ATTRIBUTE_AMPLITUDE); - int amplitude = VALUE_AMPLITUDE_DEFAULT.equals(rawAmplitude) - ? VibrationEffect.DEFAULT_AMPLITUDE - : XmlReader.readAttributeIntInRange( - parser, ATTRIBUTE_AMPLITUDE, 0, VibrationEffect.MAX_AMPLITUDE); - int durationMs = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_DURATION_MS); - - waveformBuilder.addDurationAndAmplitude(durationMs, amplitude); - - // Consume tag - XmlReader.readEndTag(parser); - } } } diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedRepeatingEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedRepeatingEffect.java new file mode 100644 index 000000000000..12acc7247b86 --- /dev/null +++ b/core/java/com/android/internal/vibrator/persistence/SerializedRepeatingEffect.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.vibrator.persistence; + +import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREAMBLE; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_REPEATING; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_REPEATING_EFFECT; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENTRY; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.VibrationEffect; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Serialized representation of a repeating effect created via + * {@link VibrationEffect#createRepeatingEffect}. + * + * @hide + */ +public class SerializedRepeatingEffect implements SerializedComposedEffect.SerializedSegment { + + @Nullable + private final SerializedComposedEffect mSerializedPreamble; + @NonNull + private final SerializedComposedEffect mSerializedRepeating; + + SerializedRepeatingEffect(@Nullable SerializedComposedEffect serializedPreamble, + @NonNull SerializedComposedEffect serializedRepeating) { + mSerializedPreamble = serializedPreamble; + mSerializedRepeating = serializedRepeating; + } + + @Override + public void write(@NonNull TypedXmlSerializer serializer) throws IOException { + serializer.startTag(NAMESPACE, TAG_REPEATING_EFFECT); + + if (mSerializedPreamble != null) { + serializer.startTag(NAMESPACE, TAG_PREAMBLE); + mSerializedPreamble.writeContent(serializer); + serializer.endTag(NAMESPACE, TAG_PREAMBLE); + } + + serializer.startTag(NAMESPACE, TAG_REPEATING); + mSerializedRepeating.writeContent(serializer); + serializer.endTag(NAMESPACE, TAG_REPEATING); + + serializer.endTag(NAMESPACE, TAG_REPEATING_EFFECT); + } + + @Override + public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) { + if (mSerializedPreamble != null) { + composition.addEffect( + VibrationEffect.createRepeatingEffect(mSerializedPreamble.deserialize(), + mSerializedRepeating.deserialize())); + return; + } + + composition.addEffect( + VibrationEffect.createRepeatingEffect(mSerializedRepeating.deserialize())); + } + + @Override + public String toString() { + return "SerializedRepeatingEffect{" + + "preamble=" + mSerializedPreamble + + ", repeating=" + mSerializedRepeating + + '}'; + } + + static final class Builder { + private SerializedComposedEffect mPreamble; + private SerializedComposedEffect mRepeating; + + void setPreamble(SerializedComposedEffect effect) { + mPreamble = effect; + } + + void setRepeating(SerializedComposedEffect effect) { + mRepeating = effect; + } + + boolean hasRepeatingSegment() { + return mRepeating != null; + } + + SerializedRepeatingEffect build() { + return new SerializedRepeatingEffect(mPreamble, mRepeating); + } + } + + /** Parser implementation for {@link SerializedRepeatingEffect}. */ + static final class Parser { + + @NonNull + static SerializedRepeatingEffect parseNext(@NonNull TypedXmlPullParser parser, + @XmlConstants.Flags int flags) throws XmlParserException, IOException { + XmlValidator.checkStartTag(parser, TAG_REPEATING_EFFECT); + XmlValidator.checkTagHasNoUnexpectedAttributes(parser); + + Builder builder = new Builder(); + int outerDepth = parser.getDepth(); + + boolean hasNestedTag = XmlReader.readNextTagWithin(parser, outerDepth); + if (hasNestedTag && TAG_PREAMBLE.equals(parser.getName())) { + builder.setPreamble(parseEffect(parser, TAG_PREAMBLE, flags)); + hasNestedTag = XmlReader.readNextTagWithin(parser, outerDepth); + } + + XmlValidator.checkParserCondition(hasNestedTag, + "Missing %s tag in %s", TAG_REPEATING, TAG_REPEATING_EFFECT); + builder.setRepeating(parseEffect(parser, TAG_REPEATING, flags)); + + XmlValidator.checkParserCondition(builder.hasRepeatingSegment(), + "Unexpected %s tag with no repeating segment", TAG_REPEATING_EFFECT); + + // Consume tag + XmlReader.readEndTag(parser, TAG_REPEATING_EFFECT, outerDepth); + + return builder.build(); + } + + private static SerializedComposedEffect parseEffect(TypedXmlPullParser parser, + String tagName, int flags) throws XmlParserException, IOException { + XmlValidator.checkStartTag(parser, tagName); + XmlValidator.checkTagHasNoUnexpectedAttributes(parser); + int vibrationTagDepth = parser.getDepth(); + XmlValidator.checkParserCondition( + XmlReader.readNextTagWithin(parser, vibrationTagDepth), + "Unsupported empty %s tag", tagName); + + SerializedComposedEffect effect; + switch (parser.getName()) { + case TAG_PREDEFINED_EFFECT: + effect = new SerializedComposedEffect( + SerializedPredefinedEffect.Parser.parseNext(parser, flags)); + break; + case TAG_PRIMITIVE_EFFECT: + effect = parsePrimitiveEffects(parser, vibrationTagDepth); + break; + case TAG_WAVEFORM_ENTRY: + effect = parseWaveformEntries(parser, vibrationTagDepth); + break; + case TAG_WAVEFORM_ENVELOPE_EFFECT: + effect = new SerializedComposedEffect( + SerializedWaveformEnvelopeEffect.Parser.parseNext(parser, flags)); + break; + case TAG_BASIC_ENVELOPE_EFFECT: + effect = new SerializedComposedEffect( + SerializedBasicEnvelopeEffect.Parser.parseNext(parser, flags)); + break; + default: + throw new XmlParserException("Unexpected tag " + parser.getName() + + " in vibration tag " + tagName); + } + + // Consume tag + XmlReader.readEndTag(parser, tagName, vibrationTagDepth); + + return effect; + } + + private static SerializedComposedEffect parsePrimitiveEffects(TypedXmlPullParser parser, + int vibrationTagDepth) + throws IOException, XmlParserException { + List<SerializedComposedEffect.SerializedSegment> primitives = new ArrayList<>(); + do { // First primitive tag already open + primitives.add(SerializedCompositionPrimitive.Parser.parseNext(parser)); + } while (XmlReader.readNextTagWithin(parser, vibrationTagDepth)); + return new SerializedComposedEffect(primitives.toArray( + new SerializedComposedEffect.SerializedSegment[ + primitives.size()])); + } + + private static SerializedComposedEffect parseWaveformEntries(TypedXmlPullParser parser, + int vibrationTagDepth) + throws IOException, XmlParserException { + SerializedWaveformEffectEntries.Builder waveformBuilder = + new SerializedWaveformEffectEntries.Builder(); + do { // First waveform-entry tag already open + SerializedWaveformEffectEntries + .Parser.parseWaveformEntry(parser, waveformBuilder); + } while (XmlReader.readNextTagWithin(parser, vibrationTagDepth)); + XmlValidator.checkParserCondition(waveformBuilder.hasNonZeroDuration(), + "Unexpected %s tag with total duration zero", TAG_WAVEFORM_ENTRY); + return new SerializedComposedEffect(waveformBuilder.build()); + } + } +} diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEffectEntries.java b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEffectEntries.java new file mode 100644 index 000000000000..8849e75e7891 --- /dev/null +++ b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEffectEntries.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.vibrator.persistence; + +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_AMPLITUDE; +import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS; +import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENTRY; +import static com.android.internal.vibrator.persistence.XmlConstants.VALUE_AMPLITUDE_DEFAULT; + +import android.annotation.NonNull; +import android.os.VibrationEffect; +import android.util.IntArray; +import android.util.LongArray; + +import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Serialized representation of a list of waveform entries created via + * {@link VibrationEffect#createWaveform(long[], int[], int)}. + * + * @hide + */ +final class SerializedWaveformEffectEntries implements SerializedSegment { + + @NonNull + private final long[] mTimings; + @NonNull + private final int[] mAmplitudes; + + private SerializedWaveformEffectEntries(@NonNull long[] timings, + @NonNull int[] amplitudes) { + mTimings = timings; + mAmplitudes = amplitudes; + } + + @Override + public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) { + composition.addEffect(VibrationEffect.createWaveform(mTimings, mAmplitudes, -1)); + } + + @Override + public void write(@NonNull TypedXmlSerializer serializer) throws IOException { + for (int i = 0; i < mTimings.length; i++) { + serializer.startTag(NAMESPACE, TAG_WAVEFORM_ENTRY); + + if (mAmplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) { + serializer.attribute(NAMESPACE, ATTRIBUTE_AMPLITUDE, VALUE_AMPLITUDE_DEFAULT); + } else { + serializer.attributeInt(NAMESPACE, ATTRIBUTE_AMPLITUDE, mAmplitudes[i]); + } + + serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, mTimings[i]); + serializer.endTag(NAMESPACE, TAG_WAVEFORM_ENTRY); + } + + } + + @Override + public String toString() { + return "SerializedWaveformEffectEntries{" + + "timings=" + Arrays.toString(mTimings) + + ", amplitudes=" + Arrays.toString(mAmplitudes) + + '}'; + } + + /** Builder for {@link SerializedWaveformEffectEntries}. */ + static final class Builder { + private final LongArray mTimings = new LongArray(); + private final IntArray mAmplitudes = new IntArray(); + + void addDurationAndAmplitude(long durationMs, int amplitude) { + mTimings.add(durationMs); + mAmplitudes.add(amplitude); + } + + boolean hasNonZeroDuration() { + for (int i = 0; i < mTimings.size(); i++) { + if (mTimings.get(i) > 0) { + return true; + } + } + return false; + } + + SerializedWaveformEffectEntries build() { + return new SerializedWaveformEffectEntries( + mTimings.toArray(), mAmplitudes.toArray()); + } + } + + /** Parser implementation for the {@link XmlConstants#TAG_WAVEFORM_ENTRY}. */ + static final class Parser { + + /** Parses a single {@link XmlConstants#TAG_WAVEFORM_ENTRY} into the builder. */ + public static void parseWaveformEntry(TypedXmlPullParser parser, Builder waveformBuilder) + throws XmlParserException, IOException { + SerializedAmplitudeStepWaveform.Parser.parseWaveformEntry(parser, + waveformBuilder::addDurationAndAmplitude); + } + } +} diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectSerializer.java new file mode 100644 index 000000000000..df483ecdf881 --- /dev/null +++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectSerializer.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.PersistableBundle; +import android.os.VibrationEffect; +import android.os.vibrator.BasicPwleSegment; +import android.os.vibrator.Flags; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.PwleSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; + +import java.util.List; +import java.util.function.BiConsumer; + +/** + * Serializer implementation for {@link VibrationEffect}. + * + * <p>This serializer does not support effects created with {@link VibrationEffect.WaveformBuilder} + * nor {@link VibrationEffect.Composition#addEffect(VibrationEffect)}. It only supports vibration + * effects defined as: + * + * <ul> + * <li>{@link VibrationEffect#createPredefined(int)} + * <li>{@link VibrationEffect#createWaveform(long[], int[], int)} + * <li>A composition created exclusively via + * {@link VibrationEffect.Composition#addPrimitive(int, float, int)} + * <li>{@link VibrationEffect#createVendorEffect(PersistableBundle)} + * <li>{@link VibrationEffect.WaveformEnvelopeBuilder} + * <li>{@link VibrationEffect.BasicEnvelopeBuilder} + * </ul> + * + * <p>This serializer also supports repeating effects. For repeating waveform effects, it attempts + * to serialize the effect as a single unit. If this fails, it falls back to serializing it as a + * sequence of individual waveform entries. + * + * @hide + */ +public class VibrationEffectSerializer { + private static final String TAG = "VibrationEffectSerializer"; + + /** + * Creates a serialized representation of the input {@code vibration}. + */ + @NonNull + public static XmlSerializedVibration<? extends VibrationEffect> serialize( + @NonNull VibrationEffect vibration, @XmlConstants.Flags int flags) + throws XmlSerializerException { + + if (Flags.vendorVibrationEffects() + && (vibration instanceof VibrationEffect.VendorEffect vendorEffect)) { + return serializeVendorEffect(vendorEffect); + } + + XmlValidator.checkSerializerCondition(vibration instanceof VibrationEffect.Composed, + "Unsupported VibrationEffect type %s", vibration); + + VibrationEffect.Composed composed = (VibrationEffect.Composed) vibration; + XmlValidator.checkSerializerCondition(!composed.getSegments().isEmpty(), + "Unsupported empty VibrationEffect %s", vibration); + + List<VibrationEffectSegment> segments = composed.getSegments(); + int repeatIndex = composed.getRepeatIndex(); + + SerializedComposedEffect serializedEffect; + if (repeatIndex >= 0) { + serializedEffect = trySerializeRepeatingAmplitudeWaveformEffect(segments, repeatIndex); + if (serializedEffect == null) { + serializedEffect = serializeRepeatingEffect(segments, repeatIndex, flags); + } + } else { + serializedEffect = serializeNonRepeatingEffect(segments, flags); + } + + return serializedEffect; + } + + private static SerializedComposedEffect serializeRepeatingEffect( + List<VibrationEffectSegment> segments, int repeatIndex, @XmlConstants.Flags int flags) + throws XmlSerializerException { + + SerializedRepeatingEffect.Builder builder = new SerializedRepeatingEffect.Builder(); + if (repeatIndex > 0) { + List<VibrationEffectSegment> preambleSegments = segments.subList(0, repeatIndex); + builder.setPreamble(serializeEffectEntries(preambleSegments, flags)); + + // Update segments to match the repeating block only, after preamble was consumed. + segments = segments.subList(repeatIndex, segments.size()); + } + + builder.setRepeating(serializeEffectEntries(segments, flags)); + + return new SerializedComposedEffect(builder.build()); + } + + @NonNull + private static SerializedComposedEffect serializeNonRepeatingEffect( + List<VibrationEffectSegment> segments, @XmlConstants.Flags int flags) + throws XmlSerializerException { + SerializedComposedEffect effect = trySerializeNonWaveformEffect(segments, flags); + if (effect == null) { + effect = serializeWaveformEffect(segments); + } + + return effect; + } + + @NonNull + private static SerializedComposedEffect serializeEffectEntries( + List<VibrationEffectSegment> segments, @XmlConstants.Flags int flags) + throws XmlSerializerException { + SerializedComposedEffect effect = trySerializeNonWaveformEffect(segments, flags); + if (effect == null) { + effect = serializeWaveformEffectEntries(segments); + } + + return effect; + } + + @Nullable + private static SerializedComposedEffect trySerializeNonWaveformEffect( + List<VibrationEffectSegment> segments, int flags) throws XmlSerializerException { + VibrationEffectSegment firstSegment = segments.getFirst(); + + if (firstSegment instanceof PrebakedSegment) { + return serializePredefinedEffect(segments, flags); + } + if (firstSegment instanceof PrimitiveSegment) { + return serializePrimitiveEffect(segments); + } + if (firstSegment instanceof PwleSegment) { + return serializeWaveformEnvelopeEffect(segments); + } + if (firstSegment instanceof BasicPwleSegment) { + return serializeBasicEnvelopeEffect(segments); + } + + return null; + } + + private static SerializedComposedEffect serializePredefinedEffect( + List<VibrationEffectSegment> segments, @XmlConstants.Flags int flags) + throws XmlSerializerException { + XmlValidator.checkSerializerCondition(segments.size() == 1, + "Unsupported multiple segments in predefined effect: %s", segments); + return new SerializedComposedEffect(serializePrebakedSegment(segments.getFirst(), flags)); + } + + private static SerializedVendorEffect serializeVendorEffect( + VibrationEffect.VendorEffect effect) { + return new SerializedVendorEffect(effect.getVendorData()); + } + + private static SerializedComposedEffect serializePrimitiveEffect( + List<VibrationEffectSegment> segments) throws XmlSerializerException { + SerializedComposedEffect.SerializedSegment[] primitives = + new SerializedComposedEffect.SerializedSegment[segments.size()]; + for (int i = 0; i < segments.size(); i++) { + primitives[i] = serializePrimitiveSegment(segments.get(i)); + } + + return new SerializedComposedEffect(primitives); + } + + private static SerializedComposedEffect serializeWaveformEnvelopeEffect( + List<VibrationEffectSegment> segments) throws XmlSerializerException { + SerializedWaveformEnvelopeEffect.Builder builder = + new SerializedWaveformEnvelopeEffect.Builder(); + for (int i = 0; i < segments.size(); i++) { + XmlValidator.checkSerializerCondition(segments.get(i) instanceof PwleSegment, + "Unsupported segment for waveform envelope effect %s", segments.get(i)); + PwleSegment segment = (PwleSegment) segments.get(i); + + if (i == 0 && segment.getStartFrequencyHz() != segment.getEndFrequencyHz()) { + // Initial frequency explicitly defined. + builder.setInitialFrequencyHz(segment.getStartFrequencyHz()); + } + + builder.addControlPoint(segment.getEndAmplitude(), segment.getEndFrequencyHz(), + segment.getDuration()); + } + + return new SerializedComposedEffect(builder.build()); + } + + private static SerializedComposedEffect serializeBasicEnvelopeEffect( + List<VibrationEffectSegment> segments) throws XmlSerializerException { + SerializedBasicEnvelopeEffect.Builder builder = new SerializedBasicEnvelopeEffect.Builder(); + for (int i = 0; i < segments.size(); i++) { + XmlValidator.checkSerializerCondition(segments.get(i) instanceof BasicPwleSegment, + "Unsupported segment for basic envelope effect %s", segments.get(i)); + BasicPwleSegment segment = (BasicPwleSegment) segments.get(i); + + if (i == 0 && segment.getStartSharpness() != segment.getEndSharpness()) { + // Initial sharpness explicitly defined. + builder.setInitialSharpness(segment.getStartSharpness()); + } + + builder.addControlPoint(segment.getEndIntensity(), segment.getEndSharpness(), + segment.getDuration()); + } + + return new SerializedComposedEffect(builder.build()); + } + + private static SerializedComposedEffect trySerializeRepeatingAmplitudeWaveformEffect( + List<VibrationEffectSegment> segments, int repeatingIndex) { + SerializedAmplitudeStepWaveform.Builder builder = + new SerializedAmplitudeStepWaveform.Builder(); + + for (int i = 0; i < segments.size(); i++) { + if (repeatingIndex == i) { + builder.setRepeatIndexToCurrentEntry(); + } + try { + serializeStepSegment(segments.get(i), builder::addDurationAndAmplitude); + } catch (XmlSerializerException e) { + return null; + } + } + + return new SerializedComposedEffect(builder.build()); + } + + private static SerializedComposedEffect serializeWaveformEffect( + List<VibrationEffectSegment> segments) throws XmlSerializerException { + SerializedAmplitudeStepWaveform.Builder builder = + new SerializedAmplitudeStepWaveform.Builder(); + for (int i = 0; i < segments.size(); i++) { + serializeStepSegment(segments.get(i), builder::addDurationAndAmplitude); + } + + return new SerializedComposedEffect(builder.build()); + } + + private static SerializedComposedEffect serializeWaveformEffectEntries( + List<VibrationEffectSegment> segments) throws XmlSerializerException { + SerializedWaveformEffectEntries.Builder builder = + new SerializedWaveformEffectEntries.Builder(); + for (int i = 0; i < segments.size(); i++) { + serializeStepSegment(segments.get(i), builder::addDurationAndAmplitude); + } + + return new SerializedComposedEffect(builder.build()); + } + + private static void serializeStepSegment(VibrationEffectSegment segment, + BiConsumer<Long, Integer> builder) throws XmlSerializerException { + XmlValidator.checkSerializerCondition(segment instanceof StepSegment, + "Unsupported segment for waveform effect %s", segment); + + XmlValidator.checkSerializerCondition( + Float.compare(((StepSegment) segment).getFrequencyHz(), 0) == 0, + "Unsupported segment with non-default frequency %f", + ((StepSegment) segment).getFrequencyHz()); + + builder.accept(segment.getDuration(), + toAmplitudeInt(((StepSegment) segment).getAmplitude())); + } + + private static SerializedPredefinedEffect serializePrebakedSegment( + VibrationEffectSegment segment, @XmlConstants.Flags int flags) + throws XmlSerializerException { + XmlValidator.checkSerializerCondition(segment instanceof PrebakedSegment, + "Unsupported segment for predefined effect %s", segment); + + PrebakedSegment prebaked = (PrebakedSegment) segment; + XmlConstants.PredefinedEffectName effectName = XmlConstants.PredefinedEffectName.findById( + prebaked.getEffectId(), flags); + + XmlValidator.checkSerializerCondition(effectName != null, + "Unsupported predefined effect id %s", prebaked.getEffectId()); + + if ((flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) == 0) { + // Only allow effects with default fallback flag if using the public APIs schema. + XmlValidator.checkSerializerCondition( + prebaked.shouldFallback() == PrebakedSegment.DEFAULT_SHOULD_FALLBACK, + "Unsupported predefined effect with should fallback %s", + prebaked.shouldFallback()); + } + + return new SerializedPredefinedEffect(effectName, prebaked.shouldFallback()); + } + + private static SerializedCompositionPrimitive serializePrimitiveSegment( + VibrationEffectSegment segment) throws XmlSerializerException { + XmlValidator.checkSerializerCondition(segment instanceof PrimitiveSegment, + "Unsupported segment for primitive composition %s", segment); + + PrimitiveSegment primitive = (PrimitiveSegment) segment; + XmlConstants.PrimitiveEffectName primitiveName = + XmlConstants.PrimitiveEffectName.findById(primitive.getPrimitiveId()); + + XmlValidator.checkSerializerCondition(primitiveName != null, + "Unsupported primitive effect id %s", primitive.getPrimitiveId()); + + XmlConstants.PrimitiveDelayType delayType = null; + + if (Flags.primitiveCompositionAbsoluteDelay()) { + delayType = XmlConstants.PrimitiveDelayType.findByType(primitive.getDelayType()); + XmlValidator.checkSerializerCondition(delayType != null, + "Unsupported primitive delay type %s", primitive.getDelayType()); + } else { + XmlValidator.checkSerializerCondition( + primitive.getDelayType() == PrimitiveSegment.DEFAULT_DELAY_TYPE, + "Unsupported primitive delay type %s", primitive.getDelayType()); + } + + return new SerializedCompositionPrimitive( + primitiveName, primitive.getScale(), primitive.getDelay(), delayType); + } + + private static int toAmplitudeInt(float amplitude) { + return Float.compare(amplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0 + ? VibrationEffect.DEFAULT_AMPLITUDE + : Math.round(amplitude * VibrationEffect.MAX_AMPLITUDE); + } +} diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java index 314bfe40ee0b..efd75fc17cb7 100644 --- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java +++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java @@ -19,6 +19,7 @@ package com.android.internal.vibrator.persistence; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT; +import static com.android.internal.vibrator.persistence.XmlConstants.TAG_REPEATING_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION_EFFECT; import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT; @@ -120,6 +121,26 @@ import java.util.List; * } * </pre> * + * * Repeating effects + * + * <pre> + * {@code + * <vibration-effect> + * <repeating-effect> + * <preamble> + * <primitive-effect name="click" /> + * </preamble> + * <repeating> + * <basic-envelope-effect> + * <control-point intensity="0.3" sharpness="0.4" durationMs="25" /> + * <control-point intensity="0.0" sharpness="0.5" durationMs="30" /> + * </basic-envelope-effect> + * </repeating> + * </repeating-effect> + * </vibration-effect> + * } + * </pre> + * * @hide */ public class VibrationEffectXmlParser { @@ -191,6 +212,12 @@ public class VibrationEffectXmlParser { SerializedBasicEnvelopeEffect.Parser.parseNext(parser, flags)); break; } // else fall through + case TAG_REPEATING_EFFECT: + if (Flags.normalizedPwleEffects()) { + serializedVibration = new SerializedComposedEffect( + SerializedRepeatingEffect.Parser.parseNext(parser, flags)); + break; + } // else fall through default: throw new XmlParserException("Unexpected tag " + parser.getName() + " in vibration tag " + vibrationTagName); diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java index df262cfecd5a..cc5c7cfb4683 100644 --- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java +++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java @@ -45,8 +45,10 @@ public final class XmlConstants { public static final String TAG_WAVEFORM_ENVELOPE_EFFECT = "waveform-envelope-effect"; public static final String TAG_BASIC_ENVELOPE_EFFECT = "basic-envelope-effect"; public static final String TAG_WAVEFORM_EFFECT = "waveform-effect"; + public static final String TAG_REPEATING_EFFECT = "repeating-effect"; public static final String TAG_WAVEFORM_ENTRY = "waveform-entry"; public static final String TAG_REPEATING = "repeating"; + public static final String TAG_PREAMBLE = "preamble"; public static final String TAG_CONTROL_POINT = "control-point"; public static final String ATTRIBUTE_NAME = "name"; diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java index e95225eede99..8629a1c95202 100644 --- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java +++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java @@ -147,11 +147,6 @@ public final class NotificationProgressDrawable extends Drawable { final Part prevPart = iPart == 0 ? null : mParts.get(iPart - 1); final Part nextPart = iPart + 1 == numParts ? null : mParts.get(iPart + 1); if (part instanceof Segment segment) { - // Update the segment-point gap to 2X upon seeing the first faded segment. - // (Assuming that all segments before are solid, and all segments after are faded.) - if (segment.mFaded) { - segPointGap = mState.mSegPointGap * 2; - } final float segWidth = segment.mFraction * totalWidth; // Advance the start position to account for a point immediately prior. final float startOffset = getSegStartOffset(prevPart, pointRadius, segPointGap, x); @@ -225,7 +220,7 @@ public final class NotificationProgressDrawable extends Drawable { if (nextPart instanceof Segment nextSeg) { if (!seg.mFaded && nextSeg.mFaded) { // @see Segment#mFaded - return hasTrackerIcon ? 0F : segSegGap * 4F; + return hasTrackerIcon ? 0F : segSegGap; } return segSegGap; } @@ -487,11 +482,10 @@ public final class NotificationProgressDrawable extends Drawable { * <p> * <pre> * When mFaded is set to true, a combination of the following is done to the segment: - * 1. The drawing color is mColor with opacity updated to 15%. - * 2. The segment-point gap is 2X the segment-point gap for non-faded segments. - * 3. The gap between faded and non-faded segments is: - * 4X the segment-segment gap, when there is no tracker icon - * 0, when there is tracker icon + * 1. The drawing color is mColor with opacity updated to 40%. + * 2. The gap between faded and non-faded segments is: + * - the segment-segment gap, when there is no tracker icon + * - 0, when there is tracker icon * </pre> * </p> */ @@ -764,7 +758,7 @@ public final class NotificationProgressDrawable extends Drawable { @ColorInt static int getFadedColor(@ColorInt int color) { return Color.argb( - (int) (Color.alpha(color) * 0.25f + 0.5f), + (int) (Color.alpha(color) * 0.4f + 0.5f), Color.red(color), Color.green(color), Color.blue(color)); diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java index 33f93fccff58..60767ed9eb00 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -21,26 +21,20 @@ import android.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.remotecompose.core.operations.ComponentValue; import com.android.internal.widget.remotecompose.core.operations.FloatExpression; +import com.android.internal.widget.remotecompose.core.operations.Header; import com.android.internal.widget.remotecompose.core.operations.IntegerExpression; import com.android.internal.widget.remotecompose.core.operations.NamedVariable; import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; import com.android.internal.widget.remotecompose.core.operations.ShaderData; import com.android.internal.widget.remotecompose.core.operations.TextData; import com.android.internal.widget.remotecompose.core.operations.Theme; -import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.Component; -import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd; -import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation; -import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd; +import com.android.internal.widget.remotecompose.core.operations.layout.Container; +import com.android.internal.widget.remotecompose.core.operations.layout.ContainerEnd; import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation; -import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd; import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent; -import com.android.internal.widget.remotecompose.core.operations.layout.TouchCancelModifierOperation; -import com.android.internal.widget.remotecompose.core.operations.layout.TouchDownModifierOperation; -import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation; -import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; import java.util.ArrayList; @@ -57,7 +51,18 @@ import java.util.Set; public class CoreDocument { private static final boolean DEBUG = false; - private static final int DOCUMENT_API_LEVEL = 2; + + // Semantic version + public static final int MAJOR_VERSION = 0; + public static final int MINOR_VERSION = 3; + public static final int PATCH_VERSION = 0; + + // Internal version level + public static final int DOCUMENT_API_LEVEL = 3; + + // We also keep a more fine-grained BUILD number, exposed as + // ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD + static final float BUILD = 0.0f; @NonNull ArrayList<Operation> mOperations = new ArrayList<>(); @@ -65,8 +70,9 @@ public class CoreDocument { @NonNull RemoteComposeState mRemoteComposeState = new RemoteComposeState(); @VisibleForTesting @NonNull public TimeVariables mTimeVariables = new TimeVariables(); + // Semantic version of the document - @NonNull Version mVersion = new Version(0, 1, 0); + @NonNull Version mVersion = new Version(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION); @Nullable String mContentDescription; // text description of the document (used for accessibility) @@ -551,6 +557,11 @@ public class CoreDocument { mOperations = new ArrayList<Operation>(); buffer.inflateFromBuffer(mOperations); for (Operation op : mOperations) { + if (op instanceof Header) { + // Make sure we parse the version at init time... + Header header = (Header) op; + header.setVersion(this); + } if (op instanceof IntegerExpression) { IntegerExpression expression = (IntegerExpression) op; mIntegerExpressions.put((long) expression.mId, expression); @@ -581,85 +592,55 @@ public class CoreDocument { */ @NonNull private ArrayList<Operation> inflateComponents(@NonNull ArrayList<Operation> operations) { - Component currentComponent = null; - ArrayList<Component> components = new ArrayList<>(); ArrayList<Operation> finalOperationsList = new ArrayList<>(); ArrayList<Operation> ops = finalOperationsList; - ClickModifierOperation currentClickModifier = null; - TouchDownModifierOperation currentTouchDownModifier = null; - TouchUpModifierOperation currentTouchUpModifier = null; - TouchCancelModifierOperation currentTouchCancelModifier = null; - LoopOperation currentLoop = null; - ScrollModifierOperation currentScrollModifier = null; + + ArrayList<Container> containers = new ArrayList<>(); mLastId = -1; for (Operation o : operations) { - if (o instanceof ComponentStartOperation) { - Component component = (Component) o; - component.setParent(currentComponent); - components.add(component); - currentComponent = component; - ops.add(currentComponent); - ops = currentComponent.getList(); - if (component.getComponentId() < mLastId) { - mLastId = component.getComponentId(); + if (o instanceof Container) { + Container container = (Container) o; + if (container instanceof Component) { + Component component = (Component) container; + // Make sure to set the parent when a component is first found, so that + // the inflate when closing the component is in a state where the hierarchy + // is already existing. + if (!containers.isEmpty()) { + Container parentContainer = containers.get(containers.size() - 1); + if (parentContainer instanceof Component) { + component.setParent((Component) parentContainer); + } + } + if (component.getComponentId() < mLastId) { + mLastId = component.getComponentId(); + } } - } else if (o instanceof ComponentEnd) { - if (currentComponent != null) { - currentComponent.inflate(); + containers.add(container); + ops = container.getList(); + } else if (o instanceof ContainerEnd) { + // check if we have a parent container + Container container = null; + // pop the container + if (!containers.isEmpty()) { + container = containers.remove(containers.size() - 1); } - components.remove(components.size() - 1); - if (!components.isEmpty()) { - currentComponent = components.get(components.size() - 1); - ops = currentComponent.getList(); - } else { - ops = finalOperationsList; - } - } else if (o instanceof ClickModifierOperation) { - // TODO: refactor to add container <- component... - currentClickModifier = (ClickModifierOperation) o; - ops = currentClickModifier.getList(); - } else if (o instanceof TouchDownModifierOperation) { - currentTouchDownModifier = (TouchDownModifierOperation) o; - ops = currentTouchDownModifier.getList(); - } else if (o instanceof TouchUpModifierOperation) { - currentTouchUpModifier = (TouchUpModifierOperation) o; - ops = currentTouchUpModifier.getList(); - } else if (o instanceof TouchCancelModifierOperation) { - currentTouchCancelModifier = (TouchCancelModifierOperation) o; - ops = currentTouchCancelModifier.getList(); - } else if (o instanceof ScrollModifierOperation) { - currentScrollModifier = (ScrollModifierOperation) o; - ops = currentScrollModifier.getList(); - } else if (o instanceof OperationsListEnd) { - ops = currentComponent.getList(); - if (currentClickModifier != null) { - ops.add(currentClickModifier); - currentClickModifier = null; - } else if (currentTouchDownModifier != null) { - ops.add(currentTouchDownModifier); - currentTouchDownModifier = null; - } else if (currentTouchUpModifier != null) { - ops.add(currentTouchUpModifier); - currentTouchUpModifier = null; - } else if (currentTouchCancelModifier != null) { - ops.add(currentTouchCancelModifier); - currentTouchCancelModifier = null; - } else if (currentScrollModifier != null) { - ops.add(currentScrollModifier); - currentScrollModifier = null; + Container parentContainer = null; + if (!containers.isEmpty()) { + parentContainer = containers.get(containers.size() - 1); } - } else if (o instanceof LoopOperation) { - currentLoop = (LoopOperation) o; - ops = currentLoop.getList(); - } else if (o instanceof LoopEnd) { - if (currentComponent != null) { - ops = currentComponent.getList(); - ops.add(currentLoop); + if (parentContainer != null) { + ops = parentContainer.getList(); } else { ops = finalOperationsList; } - currentLoop = null; + if (container != null) { + if (container instanceof Component) { + Component component = (Component) container; + component.inflate(); + } + ops.add((Operation) container); + } } else { ops.add(o); } @@ -744,7 +725,7 @@ public class CoreDocument { * @param minorVersion minor version number, increased when adding new features * @param patch patch level, increased upon bugfixes */ - void setVersion(int majorVersion, int minorVersion, int patch) { + public void setVersion(int majorVersion, int minorVersion, int patch) { mVersion = new Version(majorVersion, minorVersion, patch); } @@ -1080,19 +1061,22 @@ public class CoreDocument { } } context.mMode = RemoteContext.ContextMode.PAINT; - for (Operation op : mOperations) { + for (int i = 0; i < mOperations.size(); i++) { + Operation op = mOperations.get(i); // operations will only be executed if no theme is set (ie UNSPECIFIED) // or the theme is equal as the one passed in argument to paint. boolean apply = true; if (theme != Theme.UNSPECIFIED) { + int currentTheme = context.getTheme(); apply = - op instanceof Theme // always apply a theme setter - || context.getTheme() == theme - || context.getTheme() == Theme.UNSPECIFIED; + currentTheme == theme + || currentTheme == Theme.UNSPECIFIED + || op instanceof Theme; // always apply a theme setter } if (apply) { - if (op.isDirty() || op instanceof PaintOperation) { - if (op.isDirty() && op instanceof VariableSupport) { + boolean opIsDirty = op.isDirty(); + if (opIsDirty || op instanceof PaintOperation) { + if (opIsDirty && op instanceof VariableSupport) { op.markNotDirty(); ((VariableSupport) op).updateVariables(context); } @@ -1253,8 +1237,11 @@ public class CoreDocument { private void toNestedString( @NonNull Component base, @NonNull StringBuilder ret, String indent) { for (Operation mOperation : base.mList) { - ret.append(mOperation.toString()); - ret.append("\n"); + for (String line : mOperation.toString().split("\n")) { + ret.append(indent); + ret.append(line); + ret.append("\n"); + } if (mOperation instanceof Component) { toNestedString((Component) mOperation, ret, indent + " "); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java index 04e490fa5214..d9f12cb71d53 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -73,12 +73,10 @@ import com.android.internal.widget.remotecompose.core.operations.Theme; import com.android.internal.widget.remotecompose.core.operations.TouchExpression; import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent; import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation; -import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd; import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart; +import com.android.internal.widget.remotecompose.core.operations.layout.ContainerEnd; import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent; -import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd; import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation; -import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd; import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.TouchCancelModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.TouchDownModifierOperation; @@ -208,7 +206,6 @@ public class Operations { public static final int LAYOUT_STATE = 217; public static final int COMPONENT_START = 2; - public static final int COMPONENT_END = 3; public static final int MODIFIER_WIDTH = 16; public static final int MODIFIER_HEIGHT = 67; @@ -223,7 +220,7 @@ public class Operations { public static final int MODIFIER_TOUCH_UP = 220; public static final int MODIFIER_TOUCH_CANCEL = 225; - public static final int OPERATIONS_LIST_END = 214; + public static final int CONTAINER_END = 214; public static final int MODIFIER_OFFSET = 221; public static final int MODIFIER_ZINDEX = 223; @@ -233,7 +230,6 @@ public class Operations { public static final int MODIFIER_RIPPLE = 229; public static final int LOOP_START = 215; - public static final int LOOP_END = 216; public static final int MODIFIER_VISIBILITY = 211; public static final int HOST_ACTION = 209; @@ -311,12 +307,10 @@ public class Operations { map.put(TEXT_LOOKUP_INT, TextLookupInt::read); map.put(LOOP_START, LoopOperation::read); - map.put(LOOP_END, LoopEnd::read); // Layout map.put(COMPONENT_START, ComponentStart::read); - map.put(COMPONENT_END, ComponentEnd::read); map.put(ANIMATION_SPEC, AnimationSpec::read); map.put(MODIFIER_WIDTH, WidthModifierOperation::read); @@ -338,7 +332,7 @@ public class Operations { map.put(MODIFIER_MARQUEE, MarqueeModifierOperation::read); map.put(MODIFIER_RIPPLE, RippleModifierOperation::read); - map.put(OPERATIONS_LIST_END, OperationsListEnd::read); + map.put(CONTAINER_END, ContainerEnd::read); map.put(HOST_ACTION, HostActionOperation::read); map.put(HOST_NAMED_ACTION, HostNamedActionOperation::read); diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java index 7ecd11826303..4a40a31ef6ab 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java @@ -16,6 +16,7 @@ package com.android.internal.widget.remotecompose.core; import android.annotation.NonNull; +import android.annotation.Nullable; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; @@ -297,4 +298,12 @@ public abstract class PaintContext { public boolean isVisualDebug() { return mContext.isVisualDebug(); } + + /** + * Returns a String from an id + * + * @param textID + * @return the string if found + */ + public abstract @Nullable String getText(int textID); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java index 0ae7a94b948d..fc1e36d45a81 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -75,12 +75,10 @@ import com.android.internal.widget.remotecompose.core.operations.Theme; import com.android.internal.widget.remotecompose.core.operations.TouchExpression; import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent; -import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd; import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart; +import com.android.internal.widget.remotecompose.core.operations.layout.ContainerEnd; import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent; -import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd; import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation; -import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd; import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout; @@ -734,6 +732,22 @@ public class RemoteComposeBuffer { } /** + * Draw the text, with origin at (x,y) along the specified path. + * + * @param textId The text to be drawn + * @param path The path the text should follow for its baseline + * @param hOffset The distance along the path to add to the text's starting position + * @param vOffset The distance above(-) or below(+) the path to position the text + */ + public void addDrawTextOnPath(int textId, Object path, float hOffset, float vOffset) { + int pathId = mRemoteComposeState.dataGetId(path); + if (pathId == -1) { // never been seen before + pathId = addPathData(path); + } + DrawTextOnPath.apply(mBuffer, textId, pathId, hOffset, vOffset); + } + + /** * Draw the text, with origin at (x,y). The origin is interpreted based on the Align setting in * the paint. * @@ -1646,6 +1660,16 @@ public class RemoteComposeBuffer { } /** + * This defines the name of the float given the id + * + * @param id of the float + * @param name name of the float + */ + public void setFloatName(int id, String name) { + NamedVariable.apply(mBuffer, id, NamedVariable.FLOAT_TYPE, name); + } + + /** * Returns a usable component id -- either the one passed in parameter if not -1 or a generated * one. * @@ -1685,7 +1709,7 @@ public class RemoteComposeBuffer { /** Add a component end tag */ public void addComponentEnd() { - ComponentEnd.apply(mBuffer); + ContainerEnd.apply(mBuffer); } /** @@ -1718,7 +1742,7 @@ public class RemoteComposeBuffer { new float[] {notches, notchMax}, null); - OperationsListEnd.apply(mBuffer); + ContainerEnd.apply(mBuffer); } /** @@ -1886,7 +1910,7 @@ public class RemoteComposeBuffer { } public void addLoopEnd() { - LoopEnd.apply(mBuffer); + ContainerEnd.apply(mBuffer); } public void addStateLayout( diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java index 5c3df7e95a1f..cd26198caf2e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java @@ -318,7 +318,8 @@ public class RemoteComposeState implements CollectionsAccess { private void updateListeners(int id) { ArrayList<VariableSupport> v = mVarListeners.get(id); if (v != null && mRemoteContext != null) { - for (VariableSupport c : v) { + for (int i = 0; i < v.size(); i++) { + VariableSupport c = v.get(i); c.markDirty(); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java index b5587ce095a2..63469aabaed5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -65,6 +65,8 @@ public abstract class RemoteContext { public @Nullable Component mLastComponent; public long currentTime = 0L; + private boolean mUseChoreographer = true; + public float getDensity() { return mDensity; } @@ -187,6 +189,40 @@ public abstract class RemoteContext { public abstract void clearNamedIntegerOverride(@NonNull String integerName); /** + * Set the value of a named float. This overrides the float in the document + * + * @param floatName the name of the float to override + * @param value Override the default float + */ + public abstract void setNamedFloatOverride(String floatName, float value); + + /** + * Allows to clear a named Float. + * + * <p>If an override exists, we revert back to the default value in the document. + * + * @param floatName the name of the float to override + */ + public abstract void clearNamedFloatOverride(String floatName); + + /** + * Set the value of a named Object. This overrides the Object in the document + * + * @param dataName the name of the Object to override + * @param value Override the default float + */ + public abstract void setNamedDataOverride(String dataName, Object value); + + /** + * Allows to clear a named Object. + * + * <p>If an override exists, we revert back to the default value in the document. + * + * @param dataName the name of the Object to override + */ + public abstract void clearNamedDataOverride(String dataName); + + /** * Support Collections by registering this collection * * @param id id of the collection @@ -224,6 +260,24 @@ public abstract class RemoteContext { } /** + * Returns true if we should use the choreographter + * + * @return true if we use the choreographer + */ + public boolean useChoreographer() { + return mUseChoreographer; + } + + /** + * Set to true to use the android choreographer + * + * @param value + */ + public void setUseChoreographer(boolean value) { + mUseChoreographer = value; + } + + /** * The context can be used in a few different mode, allowing operations to skip being executed: * - UNSET : all operations will get executed - DATA : only operations dealing with DATA (eg * loading a bitmap) should execute - PAINT : only operations painting should execute diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java index ea917db98e65..cd5b202f5a79 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java +++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java @@ -24,7 +24,6 @@ import java.time.ZoneOffset; /** This generates the standard system variables for time. */ public class TimeVariables { - private static final float BUILD = 0.02f; /** * This class populates all time variables in the system @@ -59,7 +58,9 @@ public class TimeVariables { context.loadFloat(RemoteContext.ID_CALENDAR_MONTH, month); context.loadFloat(RemoteContext.ID_DAY_OF_MONTH, month); context.loadFloat(RemoteContext.ID_WEEK_DAY, day_week); - context.loadFloat(RemoteContext.ID_API_LEVEL, CoreDocument.getDocumentApiLevel() + BUILD); + context.loadFloat( + RemoteContext.ID_API_LEVEL, + CoreDocument.getDocumentApiLevel() + CoreDocument.BUILD); } /** diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java index f839922b25e2..5d0c43723ea1 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java @@ -44,8 +44,11 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport float mOutPanX; float mOutPanY; + String mLastString; + public static final int ANCHOR_TEXT_RTL = 1; public static final int ANCHOR_MONOSPACE_MEASURE = 2; + public static final int MEASURE_EVERY_TIME = 4; public DrawTextAnchored(int textID, float x, float y, float panX, float panY, int flags) { mTextID = textID; @@ -224,7 +227,13 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport ((mFlags & ANCHOR_MONOSPACE_MEASURE) != 0) ? PaintContext.TEXT_MEASURE_MONOSPACE_WIDTH : 0; - context.getTextBounds(mTextID, 0, -1, flags, mBounds); + + String str = context.getText(mTextID); + if (str != mLastString || (mFlags & MEASURE_EVERY_TIME) != 0) { + mLastString = str; + context.getTextBounds(mTextID, 0, -1, flags, mBounds); + } + float x = mOutX + getHorizontalOffset(); float y = Float.isNaN(mOutPanY) ? mOutY : mOutY + getVerticalOffset(); context.drawTextRun(mTextID, 0, -1, 0, 1, x, y, (mFlags & ANCHOR_TEXT_RTL) == 1); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java index c1872fd0fed0..e09745aa8546 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java @@ -151,21 +151,18 @@ public class FloatExpression extends Operation implements VariableSupport { if (Float.isNaN(mLastChange)) { mLastChange = t; } - float lastComputedValue; if (mFloatAnimation != null && !Float.isNaN(mLastCalculatedValue)) { - float f = mFloatAnimation.get(t - mLastChange); - context.loadFloat(mId, f); - lastComputedValue = f; + float lastComputedValue = mFloatAnimation.get(t - mLastChange); if (lastComputedValue != mLastAnimatedValue) { mLastAnimatedValue = lastComputedValue; + context.loadFloat(mId, lastComputedValue); context.needsRepaint(); } } else if (mSpring != null) { - float f = mSpring.get(t - mLastChange); - context.loadFloat(mId, f); - lastComputedValue = f; + float lastComputedValue = mSpring.get(t - mLastChange); if (lastComputedValue != mLastAnimatedValue) { mLastAnimatedValue = lastComputedValue; + context.loadFloat(mId, lastComputedValue); context.needsRepaint(); } } else { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java index 656dc09c396f..4c9602572721 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java @@ -15,11 +15,15 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import static com.android.internal.widget.remotecompose.core.CoreDocument.MAJOR_VERSION; +import static com.android.internal.widget.remotecompose.core.CoreDocument.MINOR_VERSION; +import static com.android.internal.widget.remotecompose.core.CoreDocument.PATCH_VERSION; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.LONG; import android.annotation.NonNull; +import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteComposeOperation; @@ -38,9 +42,6 @@ import java.util.List; public class Header extends Operation implements RemoteComposeOperation { private static final int OP_CODE = Operations.HEADER; private static final String CLASS_NAME = "Header"; - public static final int MAJOR_VERSION = 0; - public static final int MINOR_VERSION = 2; - public static final int PATCH_VERSION = 0; int mMajorVersion; int mMinorVersion; @@ -191,4 +192,8 @@ public class Header extends Operation implements RemoteComposeOperation { // .field(FLOAT, "DENSITY", "Major version") .field(LONG, "CAPABILITIES", "Major version"); } + + public void setVersion(CoreDocument document) { + document.setVersion(mMajorVersion, mMinorVersion, mPatchVersion); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java index 14b72af84e66..3b293bd1b8e0 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java @@ -97,10 +97,10 @@ public class TouchExpression extends Operation implements VariableSupport, Touch /** Stop at a collection points described in percents of the range */ public static final int STOP_NOTCHES_PERCENTS = 4; - /** Stop at a collectiond of point described in abslute cordnates */ + /** Stop at a collection of point described in absolute cordnates */ public static final int STOP_NOTCHES_ABSOLUTE = 5; - /** Jump to the absloute poition of the point */ + /** Jump to the absolute poition of the point */ public static final int STOP_ABSOLUTE_POS = 6; /** @@ -112,9 +112,9 @@ public class TouchExpression extends Operation implements VariableSupport, Touch * @param min the minimum value * @param max the maximum value * @param touchEffects the type of touch mode - * @param velocityId the valocity (not used) - * @param stopMode the behavour on touch oup - * @param stopSpec the paraameters that affect the touch up behavour + * @param velocityId the velocity (not used) + * @param stopMode the behaviour on touch oup + * @param stopSpec the parameters that affect the touch up behaviour * @param easingSpec the easing parameters for coming to a stop */ public TouchExpression( @@ -249,10 +249,10 @@ public class TouchExpression extends Operation implements VariableSupport, Touch } private float getStopPosition(float pos, float slope) { - float target = pos + slope / mMaxAcceleration; + float target = pos + slope / 2f; if (mWrapMode) { pos = wrap(pos); - target = pos += +slope / mMaxAcceleration; + target = pos += +slope / 2f; } else { target = Math.max(Math.min(target, mOutMax), mOutMin); } @@ -268,7 +268,6 @@ public class TouchExpression extends Operation implements VariableSupport, Touch int evenSpacing = (int) mOutStopSpec[0]; float notchMax = (mOutStopSpec.length > 1) ? mOutStopSpec[1] : mOutMax; float step = (notchMax - min) / evenSpacing; - float notch = min + step * (int) (0.5f + (target - mOutMin) / step); if (!mWrapMode) { notch = Math.max(Math.min(notch, mOutMax), min); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java index de43b90840cc..bd68d5a8c180 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java @@ -41,6 +41,16 @@ public class Utils { } /** + * Converts an id encoded in a float to the corresponding long id. + * + * @param value the float if to convert + * @return the float id converted to a long id + */ + public static long longIdFromNan(float value) { + return ((long) idFromNan(value)) + 0x100000000L; + } + + /** * convert a long into an ID * * @param v the long to convert diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java index 652ab2bc1cbc..143223398a2a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java @@ -26,11 +26,13 @@ public class AnimatableValue { int mId = 0; float mValue = 0f; + boolean mAnimateValueChanges = true; boolean mAnimate = false; long mAnimateTargetTime = 0; float mAnimateDuration = 300f; float mTargetRotationX; float mStartRotationX; + long mLastUpdate = 0L; int mMotionEasingType = GeneralEasing.CUBIC_STANDARD; FloatAnimation mMotionEasing; @@ -39,8 +41,10 @@ public class AnimatableValue { * Value to animate * * @param value value + * @param animateValueChanges animate the change of values */ - public AnimatableValue(float value) { + public AnimatableValue(float value, boolean animateValueChanges) { + mAnimateValueChanges = animateValueChanges; if (Utils.isVariable(value)) { mId = Utils.idFromNan(value); mIsVariable = true; @@ -50,6 +54,15 @@ public class AnimatableValue { } /** + * Value to animate. + * + * @param value value + */ + public AnimatableValue(float value) { + this(value, true); + } + + /** * Get the value * * @return the value @@ -69,29 +82,41 @@ public class AnimatableValue { return mValue; } float value = context.getContext().mRemoteComposeState.getFloat(mId); - - if (value != mValue && !mAnimate) { - // animate - mStartRotationX = mValue; - mTargetRotationX = value; - mAnimate = true; - mAnimateTargetTime = System.currentTimeMillis(); - mMotionEasing = - new FloatAnimation( - mMotionEasingType, mAnimateDuration / 1000f, null, 0f, Float.NaN); - mMotionEasing.setTargetValue(1f); - } - if (mAnimate) { - float elapsed = System.currentTimeMillis() - mAnimateTargetTime; - float p = mMotionEasing.get(elapsed / mAnimateDuration); - mValue = (1 - p) * mStartRotationX + p * mTargetRotationX; - if (p >= 1f) { - mAnimate = false; + if (value != mValue) { + long lastUpdate = System.currentTimeMillis(); + long interval = lastUpdate - mLastUpdate; + if (interval > mAnimateDuration && mLastUpdate != 0L) { + mAnimateValueChanges = true; + } else { + mAnimateValueChanges = false; } + mLastUpdate = lastUpdate; + } + if (!mAnimateValueChanges) { + mValue = value; } else { - mValue = mTargetRotationX; + if (value != mValue && !mAnimate) { + // animate + mStartRotationX = mValue; + mTargetRotationX = value; + mAnimate = true; + mAnimateTargetTime = System.currentTimeMillis(); + mMotionEasing = + new FloatAnimation( + mMotionEasingType, mAnimateDuration / 1000f, null, 0f, Float.NaN); + mMotionEasing.setTargetValue(1f); + } + if (mAnimate) { + float elapsed = System.currentTimeMillis() - mAnimateTargetTime; + float p = mMotionEasing.get(elapsed / mAnimateDuration); + mValue = (1 - p) * mStartRotationX + p * mTargetRotationX; + if (p >= 1f) { + mAnimate = false; + } + } else { + mValue = mTargetRotationX; + } } - return mValue; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java index 34b7a2326a21..511858aa2b29 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java @@ -28,7 +28,7 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio import java.util.List; /** Represents the content of a CanvasLayout (i.e. contains the canvas commands) */ -public class CanvasContent extends Component implements ComponentStartOperation { +public class CanvasContent extends Component { public CanvasContent( int componentId, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java index e05bdf2b824d..5f084e938588 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java @@ -42,7 +42,11 @@ import java.util.List; /** Represents a click modifier + actions */ public class ClickModifierOperation extends PaintOperation - implements ModifierOperation, DecoratorComponent, ClickHandler, AccessibleComponent { + implements Container, + ModifierOperation, + DecoratorComponent, + ClickHandler, + AccessibleComponent { private static final int OP_CODE = Operations.MODIFIER_CLICK; long mAnimateRippleStart = 0; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java index 8a77dc3aafa5..eee2aabe1d8b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java @@ -41,7 +41,8 @@ import java.util.ArrayList; import java.util.HashSet; /** Generic Component class */ -public class Component extends PaintOperation implements Measurable, SerializableToString { +public class Component extends PaintOperation + implements Container, Measurable, SerializableToString { private static final boolean DEBUG = false; @@ -68,9 +69,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl private boolean mNeedsBoundsAnimation = false; - /** - * Mark the component as needing a bounds animation pass - */ + /** Mark the component as needing a bounds animation pass */ public void markNeedsBoundsAnimation() { mNeedsBoundsAnimation = true; if (mParent != null && !mParent.mNeedsBoundsAnimation) { @@ -78,9 +77,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl } } - /** - * Clear the bounds animation pass flag - */ + /** Clear the bounds animation pass flag */ public void clearNeedsBoundsAnimation() { mNeedsBoundsAnimation = false; } @@ -426,13 +423,13 @@ public class Component extends PaintOperation implements Measurable, Serializabl /** * Animate the bounds of the component as needed + * * @param context */ public void animatingBounds(@NonNull RemoteContext context) { if (mAnimateMeasure != null) { mAnimateMeasure.apply(context); updateComponentValues(context); - markNeedsBoundsAnimation(); } else { clearNeedsBoundsAnimation(); } @@ -784,7 +781,13 @@ public class Component extends PaintOperation implements Measurable, Serializabl public boolean applyAnimationAsNeeded(@NonNull PaintContext context) { if (context.isAnimationEnabled() && mAnimateMeasure != null) { mAnimateMeasure.paint(context); - context.needsRepaint(); + if (mAnimateMeasure.isDone()) { + mAnimateMeasure = null; + clearNeedsBoundsAnimation(); + needsRepaint(); + } else { + markNeedsBoundsAnimation(); + } return true; } return false; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java deleted file mode 100644 index 5da06634d101..000000000000 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.widget.remotecompose.core.operations.layout; - -import android.annotation.NonNull; - -import com.android.internal.widget.remotecompose.core.Operation; -import com.android.internal.widget.remotecompose.core.Operations; -import com.android.internal.widget.remotecompose.core.RemoteContext; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; - -import java.util.List; - -public class ComponentEnd extends Operation { - - @Override - public void write(@NonNull WireBuffer buffer) { - apply(buffer); - } - - @NonNull - @Override - public String toString() { - return "COMPONENT_END"; - } - - @Override - public void apply(@NonNull RemoteContext context) { - // nothing - } - - @NonNull - @Override - public String deepToString(@NonNull String indent) { - return (indent != null ? indent : "") + toString(); - } - - /** - * The name of the class - * - * @return the name - */ - @NonNull - public static String name() { - return "ComponentEnd"; - } - - /** - * The OP_CODE for this command - * - * @return the opcode - */ - public static int id() { - return Operations.COMPONENT_END; - } - - public static void apply(@NonNull WireBuffer buffer) { - buffer.start(Operations.COMPONENT_END); - } - - public static int size() { - return 1 + 4 + 4 + 4; - } - - /** - * Read this operation and add it to the list of operations - * - * @param buffer the buffer to read - * @param operations the list of operations that will be added to - */ - public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { - operations.add(new ComponentEnd()); - } - - /** - * Populate the documentation with a description of this operation - * - * @param doc to append the description to. - */ - public static void documentation(@NonNull DocumentationBuilder doc) { - doc.operation("Layout Operations", id(), name()) - .description( - "End tag for components / layouts. This operation marks the end" - + "of a component"); - } -} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java index 4349b31d76e3..f009d8801159 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java @@ -26,9 +26,10 @@ import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import java.util.ArrayList; import java.util.List; -public class ComponentStart extends Operation implements ComponentStartOperation { +public class ComponentStart extends Operation implements Container { int mType = DEFAULT; float mX; @@ -37,6 +38,8 @@ public class ComponentStart extends Operation implements ComponentStartOperation float mHeight; int mComponentId; + @NonNull public ArrayList<Operation> mList = new ArrayList<>(); + public int getType() { return mType; } @@ -217,4 +220,10 @@ public class ComponentStart extends Operation implements ComponentStartOperation .field(FLOAT, "WIDTH", "width of the component") .field(FLOAT, "HEIGHT", "height of the component"); } + + @NonNull + @Override + public ArrayList<Operation> getList() { + return mList; + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Container.java index 360e9e93f18d..c678f6c22cef 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Container.java @@ -13,12 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.internal.widget.remotecompose.core.operations.layout; -package com.android.systemui.statusbar.notification +import android.annotation.NonNull; -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider -import org.mockito.kotlin.mock +import com.android.internal.widget.remotecompose.core.Operation; -val Kosmos.visualInterruptionDecisionProvider by - Kosmos.Fixture { mock<VisualInterruptionDecisionProvider>() } +import java.util.ArrayList; + +public interface Container { + @NonNull + ArrayList<Operation> getList(); +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ContainerEnd.java index 12a673d7380f..4290c4bc3c2b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ContainerEnd.java @@ -25,7 +25,7 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio import java.util.List; -public class OperationsListEnd extends Operation { +public class ContainerEnd extends Operation { @Override public void write(@NonNull WireBuffer buffer) { @@ -65,7 +65,7 @@ public class OperationsListEnd extends Operation { * @return the opcode */ public static int id() { - return Operations.OPERATIONS_LIST_END; + return Operations.CONTAINER_END; } public static void apply(@NonNull WireBuffer buffer) { @@ -79,7 +79,7 @@ public class OperationsListEnd extends Operation { * @param operations the list of operations that will be added to */ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { - operations.add(new OperationsListEnd()); + operations.add(new ContainerEnd()); } /** diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java index 9bfbe6a42a37..27172aa7672c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java @@ -28,7 +28,7 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio import java.util.List; /** Represents the content of a LayoutComponent (i.e. the children components) */ -public class LayoutComponentContent extends Component implements ComponentStartOperation { +public class LayoutComponentContent extends Component { public LayoutComponentContent( int componentId, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java index 9fc5da8320ba..6dce6f115572 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java @@ -29,7 +29,7 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Strin import java.util.ArrayList; public abstract class ListActionsOperation extends PaintOperation - implements ModifierOperation, DecoratorComponent { + implements Container, ModifierOperation, DecoratorComponent { String mOperationName; protected float mWidth = 0; @@ -43,6 +43,7 @@ public abstract class ListActionsOperation extends PaintOperation public ArrayList<Operation> mList = new ArrayList<>(); + @NonNull public ArrayList<Operation> getList() { return mList; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java deleted file mode 100644 index 3d389e5badef..000000000000 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.widget.remotecompose.core.operations.layout; - -import android.annotation.NonNull; - -import com.android.internal.widget.remotecompose.core.Operation; -import com.android.internal.widget.remotecompose.core.Operations; -import com.android.internal.widget.remotecompose.core.RemoteContext; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; - -import java.util.List; - -public class LoopEnd extends Operation { - - @Override - public void write(@NonNull WireBuffer buffer) { - apply(buffer); - } - - @NonNull - @Override - public String toString() { - return "LOOP_END"; - } - - @Override - public void apply(@NonNull RemoteContext context) { - // nothing - } - - @NonNull - @Override - public String deepToString(@NonNull String indent) { - return (indent != null ? indent : "") + toString(); - } - - /** - * The name of the class - * - * @return the name - */ - @NonNull - public static String name() { - return "LoopEnd"; - } - - /** - * The OP_CODE for this command - * - * @return the opcode - */ - public static int id() { - return Operations.LOOP_END; - } - - public static void apply(@NonNull WireBuffer buffer) { - buffer.start(id()); - } - - /** - * Read this operation and add it to the list of operations - * - * @param buffer the buffer to read - * @param operations the list of operations that will be added to - */ - public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { - operations.add(new LoopEnd()); - } - - /** - * Populate the documentation with a description of this operation - * - * @param doc to append the description to. - */ - public static void documentation(@NonNull DocumentationBuilder doc) { - doc.operation("Operations", id(), name()).description("End tag for loops"); - } -} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java index ab1e0ac73368..f5954eea04af 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java @@ -32,7 +32,7 @@ import java.util.ArrayList; import java.util.List; /** Represents a loop of operations */ -public class LoopOperation extends PaintOperation implements VariableSupport { +public class LoopOperation extends PaintOperation implements Container, VariableSupport { private static final int OP_CODE = Operations.LOOP_START; @NonNull public ArrayList<Operation> mList = new ArrayList<>(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java index 11fa7ee670dd..baff5ee488a7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java @@ -36,7 +36,7 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Strin import java.util.List; /** Represents the root layout component. Entry point to the component tree layout/paint. */ -public class RootLayoutComponent extends Component implements ComponentStartOperation { +public class RootLayoutComponent extends Component { private int mCurrentId = -1; private boolean mHasTouchListeners = false; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java index 1de956b7e5d7..d3b3e0e775f2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java @@ -323,7 +323,6 @@ public class AnimateMeasure { } if (mP >= 1f && mVp >= 1f) { - mComponent.mAnimateMeasure = null; mComponent.mVisibility = mTarget.getVisibility(); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java index 8076cb10ea0c..a37f35f0c8d8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java @@ -26,7 +26,6 @@ import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.operations.layout.Component; -import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; @@ -34,7 +33,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure. import java.util.List; /** Simple Box layout implementation */ -public class BoxLayout extends LayoutManager implements ComponentStartOperation { +public class BoxLayout extends LayoutManager { public static final int START = 1; public static final int CENTER = 2; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java index 249e84a1c1bc..f68d7b439578 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java @@ -28,7 +28,6 @@ import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.operations.layout.Component; -import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation; import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; @@ -40,7 +39,7 @@ import java.util.List; /** * Simple Column layout implementation - also supports weight and horizontal/vertical positioning */ -public class ColumnLayout extends LayoutManager implements ComponentStartOperation { +public class ColumnLayout extends LayoutManager { public static final int START = 1; public static final int CENTER = 2; public static final int END = 3; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java index a5edaa8de3af..edfd69cbfa96 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java @@ -135,33 +135,26 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl float measuredWidth = Math.min(maxWidth, computeModifierDefinedWidth(context.getContext())); float measuredHeight = Math.min(maxHeight, computeModifierDefinedHeight(context.getContext())); - float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight; - float insetMaxHeight = maxHeight - mPaddingTop - mPaddingBottom; if (mWidthModifier.isIntrinsicMin()) { - maxWidth = intrinsicWidth(context.getContext()); + maxWidth = intrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight; } if (mHeightModifier.isIntrinsicMin()) { - maxHeight = intrinsicHeight(context.getContext()); + maxHeight = intrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom; } + float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight; + float insetMaxHeight = maxHeight - mPaddingTop - mPaddingBottom; + boolean hasHorizontalWrap = mWidthModifier.isWrap(); boolean hasVerticalWrap = mHeightModifier.isWrap(); if (hasHorizontalWrap || hasVerticalWrap) { // TODO: potential npe -- bbade@ mCachedWrapSize.setWidth(0f); mCachedWrapSize.setHeight(0f); - float wrapMaxWidth = insetMaxWidth; - float wrapMaxHeight = insetMaxHeight; - if (hasHorizontalWrap) { - wrapMaxWidth = insetMaxWidth - mPaddingLeft - mPaddingRight; - } - if (hasVerticalWrap) { - wrapMaxHeight = insetMaxHeight - mPaddingTop - mPaddingBottom; - } computeWrapSize( context, - wrapMaxWidth, - wrapMaxHeight, + insetMaxWidth, + insetMaxHeight, mWidthModifier.isWrap(), mHeightModifier.isWrap(), measure, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java index 37b9a688af8b..b688f6e4175a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java @@ -28,7 +28,6 @@ import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.operations.layout.Component; -import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation; import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; @@ -38,7 +37,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.utils.De import java.util.List; /** Simple Row layout implementation - also supports weight and horizontal/vertical positioning */ -public class RowLayout extends LayoutManager implements ComponentStartOperation { +public class RowLayout extends LayoutManager { public static final int START = 1; public static final int CENTER = 2; public static final int END = 3; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java index 61a3ec964142..3044797b17c9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java @@ -26,7 +26,6 @@ import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.operations.layout.Component; -import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation; import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; @@ -43,7 +42,7 @@ import java.util.Map; * <p>States are defined as child layouts. This layout handles interpolating between the different * state in order to provide an automatic transition. */ -public class StateLayout extends LayoutManager implements ComponentStartOperation { +public class StateLayout extends LayoutManager { public int measuredLayoutIndex = 0; public int currentLayoutIndex = 0; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java index 910205e8a7e2..8157ea05ec45 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java @@ -29,7 +29,6 @@ import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.operations.layout.Component; -import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; @@ -39,8 +38,7 @@ import com.android.internal.widget.remotecompose.core.semantics.AccessibleCompon import java.util.List; /** Text component, referencing a text id */ -public class TextLayout extends LayoutManager - implements ComponentStartOperation, VariableSupport, AccessibleComponent { +public class TextLayout extends LayoutManager implements VariableSupport, AccessibleComponent { private static final boolean DEBUG = false; private int mTextId = -1; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java index a5f79ee7e7b7..8950579354b7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java @@ -31,6 +31,7 @@ import com.android.internal.widget.remotecompose.core.operations.TouchExpression import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent; +import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.ListActionsOperation; import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.ScrollDelegate; @@ -212,17 +213,38 @@ public class ScrollModifierOperation extends ListActionsOperation .field(INT, "direction", ""); } + private float getMaxScrollPosition(Component component, int direction) { + if (component instanceof LayoutComponent) { + LayoutComponent layoutComponent = (LayoutComponent) component; + int numChildren = layoutComponent.getChildrenComponents().size(); + if (numChildren > 0) { + Component lastChild = layoutComponent.getChildrenComponents().get(numChildren - 1); + if (direction == 0) { // VERTICAL + return lastChild.getY(); + } else { + return lastChild.getX(); + } + } + } + return 0f; + } + @Override public void layout(RemoteContext context, Component component, float width, float height) { mWidth = width; mHeight = height; - if (mDirection == 0) { // VERTICAL - context.loadFloat(Utils.idFromNan(mMax), mMaxScrollY); - context.loadFloat(Utils.idFromNan(mNotchMax), mContentDimension); - } else { - context.loadFloat(Utils.idFromNan(mMax), mMaxScrollX); - context.loadFloat(Utils.idFromNan(mNotchMax), mContentDimension); + float max = mMaxScrollY; + if (mDirection != 0) { // HORIZONTAL + max = mMaxScrollX; + } + if (mTouchExpression != null) { + float maxScrollPosition = getMaxScrollPosition(component, mDirection); + if (maxScrollPosition > 0) { + max = maxScrollPosition; + } } + context.loadFloat(Utils.idFromNan(mMax), max); + context.loadFloat(Utils.idFromNan(mNotchMax), mContentDimension); } @Override diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index 6eb83f1da410..5de11a19799d 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.player; +import static com.android.internal.widget.remotecompose.core.CoreDocument.MAJOR_VERSION; +import static com.android.internal.widget.remotecompose.core.CoreDocument.MINOR_VERSION; + import android.app.Application; import android.content.Context; import android.content.res.TypedArray; @@ -32,6 +35,7 @@ import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ScrollView; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.remotecompose.accessibility.RemoteComposeTouchHelper; import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -43,8 +47,8 @@ import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCa public class RemoteComposePlayer extends FrameLayout { private RemoteComposeCanvas mInner; - private static final int MAX_SUPPORTED_MAJOR_VERSION = 0; - private static final int MAX_SUPPORTED_MINOR_VERSION = 1; + private static final int MAX_SUPPORTED_MAJOR_VERSION = MAJOR_VERSION; + private static final int MAX_SUPPORTED_MINOR_VERSION = MINOR_VERSION; public RemoteComposePlayer(Context context) { super(context); @@ -259,6 +263,16 @@ public class RemoteComposePlayer extends FrameLayout { return mInner.getDocument().mDocument.getOpsPerFrame(); } + /** + * Set to use the choreographer + * + * @param value + */ + @VisibleForTesting + public void setUseChoreographer(boolean value) { + mInner.setUseChoreographer(value); + } + /** Id action callback interface */ public interface IdActionCallbacks { /** diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java index 0712ea496b57..16e0e054ea4f 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java @@ -16,6 +16,7 @@ package com.android.internal.widget.remotecompose.player.platform; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Bitmap; import android.graphics.BlendMode; import android.graphics.Canvas; @@ -247,6 +248,8 @@ public class AndroidPaintContext extends PaintContext { mCanvas.drawTextOnPath(getText(textId), getPath(pathId, 0, 1), hOffset, vOffset, mPaint); } + private Paint.FontMetrics mCachedFontMetrics; + @Override public void getTextBounds(int textId, int start, int end, int flags, @NonNull float[] bounds) { String str = getText(textId); @@ -254,7 +257,10 @@ public class AndroidPaintContext extends PaintContext { end = str.length(); } - Paint.FontMetrics metrics = mPaint.getFontMetrics(); + if (mCachedFontMetrics == null) { + mCachedFontMetrics = mPaint.getFontMetrics(); + } + mPaint.getFontMetrics(mCachedFontMetrics); mPaint.getTextBounds(str, start, end, mTmpRect); bounds[0] = mTmpRect.left; @@ -266,8 +272,8 @@ public class AndroidPaintContext extends PaintContext { } if ((flags & PaintContext.TEXT_MEASURE_FONT_HEIGHT) != 0) { - bounds[1] = Math.round(metrics.ascent); - bounds[3] = Math.round(metrics.bottom); + bounds[1] = Math.round(mCachedFontMetrics.ascent); + bounds[3] = Math.round(mCachedFontMetrics.bottom); } else { bounds[1] = mTmpRect.top; bounds[3] = mTmpRect.bottom; @@ -415,225 +421,218 @@ public class AndroidPaintContext extends PaintContext { return null; } - /** - * This applies paint changes to the current paint - * - * @param paintData the list change to the paint - */ - @Override - public void applyPaint(@NonNull PaintBundle paintData) { - paintData.applyPaintChange( - (PaintContext) this, - new PaintChanges() { - @Override - public void setTextSize(float size) { - mPaint.setTextSize(size); - } - - @Override - public void setTypeFace(int fontType, int weight, boolean italic) { - int[] type = - new int[] { - Typeface.NORMAL, - Typeface.BOLD, - Typeface.ITALIC, - Typeface.BOLD_ITALIC - }; - - switch (fontType) { - case PaintBundle.FONT_TYPE_DEFAULT: - if (weight == 400 && !italic) { // for normal case - mPaint.setTypeface(Typeface.DEFAULT); - } else { - mPaint.setTypeface( - Typeface.create(Typeface.DEFAULT, weight, italic)); - } - break; - case PaintBundle.FONT_TYPE_SERIF: - if (weight == 400 && !italic) { // for normal case - mPaint.setTypeface(Typeface.SERIF); - } else { - mPaint.setTypeface( - Typeface.create(Typeface.SERIF, weight, italic)); - } - break; - case PaintBundle.FONT_TYPE_SANS_SERIF: - if (weight == 400 && !italic) { // for normal case - mPaint.setTypeface(Typeface.SANS_SERIF); - } else { - mPaint.setTypeface( - Typeface.create(Typeface.SANS_SERIF, weight, italic)); - } - break; - case PaintBundle.FONT_TYPE_MONOSPACE: - if (weight == 400 && !italic) { // for normal case - mPaint.setTypeface(Typeface.MONOSPACE); - } else { - mPaint.setTypeface( - Typeface.create(Typeface.MONOSPACE, weight, italic)); - } - - break; - } - } - - @Override - public void setStrokeWidth(float width) { - mPaint.setStrokeWidth(width); - } - - @Override - public void setColor(int color) { - mPaint.setColor(color); - } - - @Override - public void setStrokeCap(int cap) { - mPaint.setStrokeCap(Paint.Cap.values()[cap]); - } - - @Override - public void setStyle(int style) { - mPaint.setStyle(Paint.Style.values()[style]); - } - - @Override - public void setShader(int shaderId) { - // TODO this stuff should check the shader creation - if (shaderId == 0) { - mPaint.setShader(null); - return; - } - ShaderData data = getShaderData(shaderId); - if (data == null) { - return; - } - RuntimeShader shader = new RuntimeShader(getText(data.getShaderTextId())); - String[] names = data.getUniformFloatNames(); - for (int i = 0; i < names.length; i++) { - String name = names[i]; - float[] val = data.getUniformFloats(name); - shader.setFloatUniform(name, val); - } - names = data.getUniformIntegerNames(); - for (int i = 0; i < names.length; i++) { - String name = names[i]; - int[] val = data.getUniformInts(name); - shader.setIntUniform(name, val); - } - names = data.getUniformBitmapNames(); - for (int i = 0; i < names.length; i++) { - String name = names[i]; - int val = data.getUniformBitmapId(name); - } - mPaint.setShader(shader); - } - - @Override - public void setImageFilterQuality(int quality) { - Utils.log(" quality =" + quality); - mPaint.setFilterBitmap(quality == 1); - } - - @Override - public void setBlendMode(int mode) { - mPaint.setBlendMode(origamiToBlendMode(mode)); - } + PaintChanges mCachedPaintChanges = + new PaintChanges() { + @Override + public void setTextSize(float size) { + mPaint.setTextSize(size); + } + + @Override + public void setTypeFace(int fontType, int weight, boolean italic) { + int[] type = + new int[] { + Typeface.NORMAL, + Typeface.BOLD, + Typeface.ITALIC, + Typeface.BOLD_ITALIC + }; - @Override - public void setAlpha(float a) { - mPaint.setAlpha((int) (255 * a)); + switch (fontType) { + case PaintBundle.FONT_TYPE_DEFAULT: + if (weight == 400 && !italic) { // for normal case + mPaint.setTypeface(Typeface.DEFAULT); + } else { + mPaint.setTypeface( + Typeface.create(Typeface.DEFAULT, weight, italic)); + } + break; + case PaintBundle.FONT_TYPE_SERIF: + if (weight == 400 && !italic) { // for normal case + mPaint.setTypeface(Typeface.SERIF); + } else { + mPaint.setTypeface(Typeface.create(Typeface.SERIF, weight, italic)); + } + break; + case PaintBundle.FONT_TYPE_SANS_SERIF: + if (weight == 400 && !italic) { // for normal case + mPaint.setTypeface(Typeface.SANS_SERIF); + } else { + mPaint.setTypeface( + Typeface.create(Typeface.SANS_SERIF, weight, italic)); + } + break; + case PaintBundle.FONT_TYPE_MONOSPACE: + if (weight == 400 && !italic) { // for normal case + mPaint.setTypeface(Typeface.MONOSPACE); + } else { + mPaint.setTypeface( + Typeface.create(Typeface.MONOSPACE, weight, italic)); + } + + break; } - - @Override - public void setStrokeMiter(float miter) { - mPaint.setStrokeMiter(miter); + } + + @Override + public void setStrokeWidth(float width) { + mPaint.setStrokeWidth(width); + } + + @Override + public void setColor(int color) { + mPaint.setColor(color); + } + + @Override + public void setStrokeCap(int cap) { + mPaint.setStrokeCap(Paint.Cap.values()[cap]); + } + + @Override + public void setStyle(int style) { + mPaint.setStyle(Paint.Style.values()[style]); + } + + @Override + public void setShader(int shaderId) { + // TODO this stuff should check the shader creation + if (shaderId == 0) { + mPaint.setShader(null); + return; } - - @Override - public void setStrokeJoin(int join) { - mPaint.setStrokeJoin(Paint.Join.values()[join]); + ShaderData data = getShaderData(shaderId); + if (data == null) { + return; } - - @Override - public void setFilterBitmap(boolean filter) { - mPaint.setFilterBitmap(filter); + RuntimeShader shader = new RuntimeShader(getText(data.getShaderTextId())); + String[] names = data.getUniformFloatNames(); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + float[] val = data.getUniformFloats(name); + shader.setFloatUniform(name, val); } - - @Override - public void setAntiAlias(boolean aa) { - mPaint.setAntiAlias(aa); + names = data.getUniformIntegerNames(); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + int[] val = data.getUniformInts(name); + shader.setIntUniform(name, val); } - - @Override - public void clear(long mask) { - if ((mask & (1L << PaintBundle.COLOR_FILTER)) != 0) { - mPaint.setColorFilter(null); - } + names = data.getUniformBitmapNames(); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + int val = data.getUniformBitmapId(name); } - - Shader.TileMode[] mTileModes = - new Shader.TileMode[] { - Shader.TileMode.CLAMP, - Shader.TileMode.REPEAT, - Shader.TileMode.MIRROR - }; - - @Override - public void setLinearGradient( - @NonNull int[] colors, - @NonNull float[] stops, - float startX, - float startY, - float endX, - float endY, - int tileMode) { - mPaint.setShader( - new LinearGradient( - startX, - startY, - endX, - endY, - colors, - stops, - mTileModes[tileMode])); + mPaint.setShader(shader); + } + + @Override + public void setImageFilterQuality(int quality) { + Utils.log(" quality =" + quality); + mPaint.setFilterBitmap(quality == 1); + } + + @Override + public void setBlendMode(int mode) { + mPaint.setBlendMode(origamiToBlendMode(mode)); + } + + @Override + public void setAlpha(float a) { + mPaint.setAlpha((int) (255 * a)); + } + + @Override + public void setStrokeMiter(float miter) { + mPaint.setStrokeMiter(miter); + } + + @Override + public void setStrokeJoin(int join) { + mPaint.setStrokeJoin(Paint.Join.values()[join]); + } + + @Override + public void setFilterBitmap(boolean filter) { + mPaint.setFilterBitmap(filter); + } + + @Override + public void setAntiAlias(boolean aa) { + mPaint.setAntiAlias(aa); + } + + @Override + public void clear(long mask) { + if ((mask & (1L << PaintBundle.COLOR_FILTER)) != 0) { + mPaint.setColorFilter(null); } - - @Override - public void setRadialGradient( - @NonNull int[] colors, - @NonNull float[] stops, - float centerX, - float centerY, - float radius, - int tileMode) { - mPaint.setShader( - new RadialGradient( - centerX, - centerY, - radius, - colors, - stops, - mTileModes[tileMode])); + } + + Shader.TileMode[] mTileModes = + new Shader.TileMode[] { + Shader.TileMode.CLAMP, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR + }; + + @Override + public void setLinearGradient( + @NonNull int[] colors, + @NonNull float[] stops, + float startX, + float startY, + float endX, + float endY, + int tileMode) { + mPaint.setShader( + new LinearGradient( + startX, + startY, + endX, + endY, + colors, + stops, + mTileModes[tileMode])); + } + + @Override + public void setRadialGradient( + @NonNull int[] colors, + @NonNull float[] stops, + float centerX, + float centerY, + float radius, + int tileMode) { + mPaint.setShader( + new RadialGradient( + centerX, centerY, radius, colors, stops, mTileModes[tileMode])); + } + + @Override + public void setSweepGradient( + @NonNull int[] colors, + @NonNull float[] stops, + float centerX, + float centerY) { + mPaint.setShader(new SweepGradient(centerX, centerY, colors, stops)); + } + + @Override + public void setColorFilter(int color, int mode) { + PorterDuff.Mode pmode = origamiToPorterDuffMode(mode); + if (pmode != null) { + mPaint.setColorFilter(new PorterDuffColorFilter(color, pmode)); } + } + }; - @Override - public void setSweepGradient( - @NonNull int[] colors, - @NonNull float[] stops, - float centerX, - float centerY) { - mPaint.setShader(new SweepGradient(centerX, centerY, colors, stops)); - } - - @Override - public void setColorFilter(int color, int mode) { - PorterDuff.Mode pmode = origamiToPorterDuffMode(mode); - if (pmode != null) { - mPaint.setColorFilter(new PorterDuffColorFilter(color, pmode)); - } - } - }); + /** + * This applies paint changes to the current paint + * + * @param paintData the list change to the paint + */ + @Override + public void applyPaint(@NonNull PaintBundle paintData) { + paintData.applyPaintChange(this, mCachedPaintChanges); } @Override @@ -774,7 +773,8 @@ public class AndroidPaintContext extends PaintContext { return path; } - private String getText(int id) { + @Override + public @Nullable String getText(int id) { return (String) mContext.mRemoteComposeState.getFromId(id); } diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java index 0fb0a28da1db..9d385ddafe19 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java @@ -21,6 +21,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.TouchListener; import com.android.internal.widget.remotecompose.core.VariableSupport; @@ -40,7 +41,8 @@ import java.util.HashMap; * * <p>This is used to play the RemoteCompose operations on Android. */ -class AndroidRemoteContext extends RemoteContext { +@VisibleForTesting +public class AndroidRemoteContext extends RemoteContext { public void useCanvas(Canvas canvas) { if (mPaintContext == null) { @@ -121,6 +123,40 @@ class AndroidRemoteContext extends RemoteContext { mVarNameHashMap.put(integerName, null); } + @Override + public void setNamedFloatOverride(String floatName, float value) { + if (mVarNameHashMap.get(floatName) != null) { + int id = mVarNameHashMap.get(floatName).mId; + overrideFloat(id, value); + } + } + + @Override + public void clearNamedFloatOverride(String floatName) { + if (mVarNameHashMap.get(floatName) != null) { + int id = mVarNameHashMap.get(floatName).mId; + clearFloatOverride(id); + } + mVarNameHashMap.put(floatName, null); + } + + @Override + public void setNamedDataOverride(String dataName, Object value) { + if (mVarNameHashMap.get(dataName) != null) { + int id = mVarNameHashMap.get(dataName).mId; + overrideData(id, value); + } + } + + @Override + public void clearNamedDataOverride(String dataName) { + if (mVarNameHashMap.get(dataName) != null) { + int id = mVarNameHashMap.get(dataName).mId; + clearDataOverride(id); + } + mVarNameHashMap.put(dataName, null); + } + /** * Override a color to force it to be the color provided * @@ -236,6 +272,10 @@ class AndroidRemoteContext extends RemoteContext { mRemoteComposeState.overrideInteger(id, value); } + public void overrideData(int id, Object value) { + mRemoteComposeState.overrideData(id, value); + } + public void clearDataOverride(int id) { mRemoteComposeState.clearDataOverride(id); } @@ -244,6 +284,10 @@ class AndroidRemoteContext extends RemoteContext { mRemoteComposeState.clearIntegerOverride(id); } + public void clearFloatOverride(int id) { + mRemoteComposeState.clearFloatOverride(id); + } + @Override public String getText(int id) { return (String) mRemoteComposeState.getFromId(id); diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java index c7b1166e113e..da65a9cf5cc9 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java @@ -16,10 +16,12 @@ package com.android.internal.widget.remotecompose.player.platform; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Point; import android.util.AttributeSet; +import android.view.Choreographer; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -44,6 +46,22 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta boolean mHasClickAreas = false; Point mActionDownPoint = new Point(0, 0); AndroidRemoteContext mARContext = new AndroidRemoteContext(); + float mDensity = 1f; + + long mLastFrameDelay = 1; + float mMaxFrameRate = 60f; // frames per seconds + long mMaxFrameDelay = (long) (1000 / mMaxFrameRate); + + private Choreographer mChoreographer; + private Choreographer.FrameCallback mFrameCallback = + new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + mARContext.currentTime = frameTimeNanos / 1000000; + mARContext.setDebug(mDebug); + postInvalidateOnAnimation(); + } + }; public RemoteComposeCanvas(Context context) { super(context); @@ -85,6 +103,9 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta public void setDocument(RemoteComposeDocument value) { mDocument = value; mDocument.initializeContext(mARContext); + mARContext.setAnimationEnabled(true); + mARContext.setDensity(mDensity); + mARContext.setUseChoreographer(true); setContentDescription(mDocument.getDocument().getContentDescription()); updateClickAreas(); requestLayout(); @@ -93,6 +114,11 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta @Override public void onViewAttachedToWindow(View view) { + if (mChoreographer == null) { + mChoreographer = Choreographer.getInstance(); + mChoreographer.postFrameCallback(mFrameCallback); + } + mDensity = getContext().getResources().getDisplayMetrics().density; if (mDocument == null) { return; } @@ -136,6 +162,10 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta @Override public void onViewDetachedFromWindow(View view) { + if (mChoreographer != null) { + mChoreographer.removeFrameCallback(mFrameCallback); + mChoreographer = null; + } removeAllViews(); } @@ -195,6 +225,34 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta } } + public void setLocalFloat(String name, Float content) { + mARContext.setNamedFloatOverride(name, content); + if (mDocument != null) { + mDocument.invalidate(); + } + } + + public void clearLocalFloat(String name) { + mARContext.clearNamedFloatOverride(name); + if (mDocument != null) { + mDocument.invalidate(); + } + } + + public void setLocalBitmap(String name, Bitmap content) { + mARContext.setNamedDataOverride(name, content); + if (mDocument != null) { + mDocument.invalidate(); + } + } + + public void clearLocalBitmap(String name) { + mARContext.clearNamedDataOverride(name); + if (mDocument != null) { + mDocument.invalidate(); + } + } + public int hasSensorListeners(int[] ids) { int count = 0; for (int id = RemoteContext.ID_ACCELERATION_X; id <= RemoteContext.ID_LIGHT; id++) { @@ -236,6 +294,15 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mDocument.getDocument().checkShaders(mARContext, shaderControl); } + /** + * Set to true to use the choreographer + * + * @param value + */ + public void setUseChoreographer(boolean value) { + mARContext.setUseChoreographer(value); + } + public interface ClickCallbacks { void click(int id, String metadata); } @@ -414,12 +481,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta return; } long start = mEvalTime ? System.nanoTime() : 0; - mARContext.setAnimationEnabled(true); - mARContext.currentTime = System.currentTimeMillis(); - mARContext.setDebug(mDebug); - float density = getContext().getResources().getDisplayMetrics().density; mARContext.useCanvas(canvas); - mARContext.setDensity(density); mARContext.mWidth = getWidth(); mARContext.mHeight = getHeight(); mDocument.paint(mARContext, mTheme); @@ -431,8 +493,19 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mTime = System.nanoTime(); } } - if (mDocument.needsRepaint() > 0) { - invalidate(); + int nextFrame = mDocument.needsRepaint(); + if (nextFrame > 0) { + mLastFrameDelay = Math.max(mMaxFrameDelay, nextFrame); + if (mChoreographer != null) { + mChoreographer.postFrameCallbackDelayed(mFrameCallback, mLastFrameDelay); + } + if (!mARContext.useChoreographer()) { + invalidate(); + } + } else { + if (mChoreographer != null) { + mChoreographer.removeFrameCallback(mFrameCallback); + } } if (mEvalTime) { mDuration += System.nanoTime() - start; diff --git a/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/core/java/com/android/server/servicewatcher/ServiceWatcher.java index 831ff67a02a5..38872c996596 100644 --- a/core/java/com/android/server/servicewatcher/ServiceWatcher.java +++ b/core/java/com/android/server/servicewatcher/ServiceWatcher.java @@ -214,11 +214,7 @@ public interface ServiceWatcher { @Override public String toString() { - if (mComponentName == null) { - return "none"; - } else { - return mUid + "/" + mComponentName.flattenToShortString(); - } + return mUid + "/" + mComponentName.flattenToShortString(); } } diff --git a/core/jni/android_hardware_camera2_CameraDevice.cpp b/core/jni/android_hardware_camera2_CameraDevice.cpp index 493c7073416c..04cfed581750 100644 --- a/core/jni/android_hardware_camera2_CameraDevice.cpp +++ b/core/jni/android_hardware_camera2_CameraDevice.cpp @@ -30,6 +30,7 @@ #include <nativehelper/JNIHelp.h> #include "android_os_Parcel.h" #include "core_jni_helpers.h" +#include <android/binder_auto_utils.h> #include <android/binder_parcel_jni.h> #include <android/hardware/camera2/ICameraDeviceUser.h> #include <aidl/android/hardware/common/fmq/MQDescriptor.h> @@ -40,6 +41,7 @@ using namespace android; using ::android::AidlMessageQueue; +using ndk::ScopedAParcel; using ResultMetadataQueue = AidlMessageQueue<int8_t, SynchronizedReadWrite>; class FMQReader { @@ -75,15 +77,16 @@ extern "C" { static jlong CameraDevice_createFMQReader(JNIEnv *env, jclass thiz, jobject resultParcel) { - AParcel *resultAParcel = AParcel_fromJavaParcel(env, resultParcel); - if (resultAParcel == nullptr) { + ScopedAParcel sResultAParcel(AParcel_fromJavaParcel(env, resultParcel)); + if (sResultAParcel.get() == nullptr) { ALOGE("%s: Error creating result parcel", __FUNCTION__); return 0; } - AParcel_setDataPosition(resultAParcel, 0); + + AParcel_setDataPosition(sResultAParcel.get(), 0); MQDescriptor<int8_t, SynchronizedReadWrite> resultMQ; - if (resultMQ.readFromParcel(resultAParcel) != OK) { + if (resultMQ.readFromParcel(sResultAParcel.get()) != OK) { ALOGE("%s: read from result parcel failed", __FUNCTION__); return 0; } diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index dc7253954d44..67c97258a01d 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -490,6 +490,28 @@ jintArray android_os_Process_getExclusiveCores(JNIEnv* env, jobject clazz) { return cpus; } +jlongArray android_os_Process_getSchedAffinity(JNIEnv* env, jobject clazz, jint pid) { + // sched_getaffinity will do memset 0 for the unset bits within set_alloc_size_byte + cpu_set_t cpu_set; + if (sched_getaffinity(pid, sizeof(cpu_set_t), &cpu_set) != 0) { + signalExceptionForError(env, errno, pid); + return nullptr; + } + int cpu_cnt = std::min(CPU_SETSIZE, get_nprocs_conf()); + int masks_len = (int)(CPU_ALLOC_SIZE(cpu_cnt) / sizeof(__CPU_BITTYPE)); + jlongArray masks = env->NewLongArray(masks_len); + if (masks == nullptr) { + jniThrowException(env, "java/lang/OutOfMemoryError", nullptr); + return nullptr; + } + jlong* mask_elements = env->GetLongArrayElements(masks, 0); + for (int i = 0; i < masks_len; i++) { + mask_elements[i] = cpu_set.__bits[i]; + } + env->ReleaseLongArrayElements(masks, mask_elements, 0); + return masks; +} + static void android_os_Process_setCanSelfBackground(JNIEnv* env, jobject clazz, jboolean bgOk) { // Establishes the calling thread as illegal to put into the background. // Typically used only for the system process's main looper. @@ -1370,6 +1392,7 @@ static const JNINativeMethod methods[] = { {"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup}, {"createProcessGroup", "(II)I", (void*)android_os_Process_createProcessGroup}, {"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores}, + {"getSchedAffinity", "(I)[J", (void*)android_os_Process_getSchedAffinity}, {"setArgV0Native", "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0}, {"setUid", "(I)I", (void*)android_os_Process_setUid}, {"setGid", "(I)I", (void*)android_os_Process_setGid}, diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 2e0fe9eb13d9..c901ee1e3f8f 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -105,6 +105,7 @@ message SecureSettingsProto { optional SettingProto accessibility_gesture_targets = 57 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto display_daltonizer_saturation_level = 58 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_key_gesture_targets = 59 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto hct_rect_prompt_status = 60 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; @@ -566,6 +567,8 @@ message SecureSettingsProto { // value. optional SettingProto rtt_calling_mode = 69 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto screen_off_udfps_enabled = 104 [ (android.privacy).dest = DEST_AUTOMATIC ]; + message Screensaver { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -743,5 +746,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 104; + // Next tag = 105; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4a948dd91fb0..df989527efe4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -9164,15 +9164,6 @@ </intent-filter> </receiver> - <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver" - android:exported="true" - android:permission="android.permission.UPDATE_CONFIG"> - <intent-filter> - <action android:name="android.intent.action.UPDATE_CT_LOGS" /> - <data android:scheme="content" android:host="*" android:mimeType="*/*" /> - </intent-filter> - </receiver> - <receiver android:name="com.android.server.updates.LangIdInstallReceiver" android:exported="true" android:permission="android.permission.UPDATE_CONFIG"> @@ -9338,7 +9329,11 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> - <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ProfcollectBGJobService" + <service android:name="com.android.server.profcollect.ProfcollectForwardingService$PeriodicTraceJobService" + android:permission="android.permission.BIND_JOB_SERVICE" > + </service> + + <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ReportProcessJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> diff --git a/core/res/res/values-watch/styles_device_defaults.xml b/core/res/res/values-watch/styles_device_defaults.xml index f3c85a96936f..d8d424ae15c6 100644 --- a/core/res/res/values-watch/styles_device_defaults.xml +++ b/core/res/res/values-watch/styles_device_defaults.xml @@ -85,4 +85,11 @@ <item name="maxHeight">@dimen/progress_bar_height</item> <item name="mirrorForRtl">true</item> </style> + + <style name="Widget.DeviceDefault.ProgressBar" parent="Widget.Material.ProgressBar"> + <!-- Allow determinate option --> + <item name="indeterminateOnly">false</item> + <!-- Use Wear Material3 ring shape as default determinate drawable --> + <item name="progressDrawable">@drawable/progress_ring_watch</item> + </style> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 3d023c3e1d11..565e28e0cc87 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1607,6 +1607,10 @@ brightness value and will repeat for the following ramp if autobrightness is enabled. --> <bool name="config_skipScreenOnBrightnessRamp">false</bool> + <!-- Whether or not to skip a color fade transition to black when the display transitions to + STATE_OFF. Setting this to true will skip the color fade transition. --> + <bool name="config_skipScreenOffTransition">false</bool> + <!-- Allow automatic adjusting of the screen brightness while dozing in low power state. --> <bool name="config_allowAutoBrightnessWhileDozing">false</bool> @@ -7273,4 +7277,7 @@ features. Examples include the search functionality or the app predictor. --> <string name="config_systemVendorIntelligence" translatable="false"></string> + + <!-- Whether the device supports Wi-Fi USD feature. --> + <bool name="config_deviceSupportsWifiUsd">false</bool> </resources> diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml index 9498273e17e0..1b453737ce88 100644 --- a/core/res/res/values/config_battery_stats.xml +++ b/core/res/res/values/config_battery_stats.xml @@ -53,4 +53,6 @@ battery history, in bytes. --> <integer name="config_accumulatedBatteryUsageStatsSpanSize">32768</integer> + <!-- Size of storage allocated to battery history, in bytes --> + <integer name="config_batteryHistoryStorageSize">4194304</integer> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 51bd4cc6cc8a..fb21c7532ea3 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -323,7 +323,7 @@ <dimen name="notification_progress_margin_top">8dp</dimen> <!-- The horizontal margin before and after the notification progress bar. --> - <dimen name="notification_progress_margin_horizontal">2dp</dimen> + <dimen name="notification_progress_margin_horizontal">4dp</dimen> <!-- height of the notification header --> <dimen name="notification_header_height">56dp</dimen> @@ -861,7 +861,7 @@ <!-- The size of the progress tracker height --> <dimen name="notification_progress_tracker_height">20dp</dimen> <!-- The gap between segments in the notification progress bar --> - <dimen name="notification_progress_segSeg_gap">2dp</dimen> + <dimen name="notification_progress_segSeg_gap">4dp</dimen> <!-- The gap between a segment and a point in the notification progress bar --> <dimen name="notification_progress_segPoint_gap">4dp</dimen> <!-- The height of the notification progress bar segments --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6c01994b3c93..73e06f6a2520 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2045,6 +2045,7 @@ <java-symbol type="bool" name="config_unplugTurnsOnScreen" /> <java-symbol type="bool" name="config_usbChargingMessage" /> <java-symbol type="bool" name="config_skipScreenOnBrightnessRamp" /> + <java-symbol type="bool" name="config_skipScreenOffTransition" /> <java-symbol type="bool" name="config_allowAutoBrightnessWhileDozing" /> <java-symbol type="bool" name="config_allowTheaterModeWakeFromUnplug" /> <java-symbol type="bool" name="config_allowTheaterModeWakeFromGesture" /> @@ -5358,6 +5359,7 @@ <java-symbol type="integer" name="config_powerStatsAggregationPeriod" /> <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" /> <java-symbol type="integer" name="config_accumulatedBatteryUsageStatsSpanSize" /> + <java-symbol type="integer" name="config_batteryHistoryStorageSize" /> <!--Dynamic Tokens--> <java-symbol name="materialColorBackground" type="color"/> @@ -5799,5 +5801,7 @@ <java-symbol type="dimen" name="config_shapeCornerRadiusLarge"/> <java-symbol type="dimen" name="config_shapeCornerRadiusXlarge"/> + <!-- Whether the device supports Wi-Fi USD feature. --> + <java-symbol type="bool" name="config_deviceSupportsWifiUsd" /> </resources> diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index 911b7ce22741..10a85bcbf488 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -120,7 +120,8 @@ public class ClientTransactionListenerControllerTest { doReturn(newDisplayInfo).when(mIDisplayManager).getDisplayInfo(123); mDisplayManager.registerDisplayListener(mListener, mHandler, - DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE, null /* packageName */); mController.onDisplayChanged(123); diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index 7a5b3064b3a3..a270848b98a3 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -73,9 +73,13 @@ public class DisplayManagerGlobalTest { public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final long DISPLAY_CHANGE_EVENTS = + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE; + private static final long ALL_DISPLAY_EVENTS = DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DISPLAY_CHANGE_EVENTS | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; @Mock @@ -127,7 +131,7 @@ public class DisplayManagerGlobalTest { final DisplayInfo newDisplayInfo = new DisplayInfo(); newDisplayInfo.rotation++; doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(displayId); - callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); waitForHandler(); Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId)); Mockito.verifyNoMoreInteractions(mDisplayListener); @@ -186,8 +190,8 @@ public class DisplayManagerGlobalTest { mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler, ALL_DISPLAY_EVENTS - & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null); - callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + & ~DISPLAY_CHANGE_EVENTS, null); + callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); waitForHandler(); Mockito.verifyZeroInteractions(mDisplayListener); @@ -257,8 +261,7 @@ public class DisplayManagerGlobalTest { | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null /* packageName */); mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler, - DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, - null /* packageName */); + DISPLAY_CHANGE_EVENTS, null /* packageName */); mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321); waitForHandler(); @@ -304,8 +307,7 @@ public class DisplayManagerGlobalTest { assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, mDisplayManagerGlobal .mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_ADDED, 0)); - assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, - mDisplayManagerGlobal + assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal .mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, 0)); assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, mDisplayManagerGlobal diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java index 310baa371163..ea39db7b0057 100644 --- a/core/tests/coretests/src/android/os/ProcessTest.java +++ b/core/tests/coretests/src/android/os/ProcessTest.java @@ -18,6 +18,7 @@ package android.os; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.platform.test.annotations.IgnoreUnderRavenwood; @@ -26,6 +27,8 @@ import android.platform.test.ravenwood.RavenwoodRule; import org.junit.Rule; import org.junit.Test; +import java.util.Arrays; + @IgnoreUnderRavenwood(blockedBy = Process.class) public class ProcessTest { @Rule @@ -92,4 +95,19 @@ public class ProcessTest { assertTrue(Process.getAdvertisedMem() > 0); assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem()); } + + @Test + public void testGetSchedAffinity() { + long[] tidMasks = Process.getSchedAffinity(Process.myTid()); + long[] pidMasks = Process.getSchedAffinity(Process.myPid()); + checkAffinityMasks(tidMasks); + checkAffinityMasks(pidMasks); + } + + static void checkAffinityMasks(long[] masks) { + assertNotNull(masks); + assertTrue(masks.length > 0); + assertTrue("At least one of the affinity mask should be non-zero but got " + + Arrays.toString(masks), Arrays.stream(masks).anyMatch(value -> value > 0)); + } } diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index 5a4561d7c6ea..f87b6994900f 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -266,6 +266,11 @@ public class ContentCaptureSessionTest { } @Override + void internalNotifySessionFlushEvent(int sessionId) { + throw new UnsupportedOperationException("should not have been called"); + } + + @Override void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId, @NonNull ContentCaptureContext clientContext) { throw new UnsupportedOperationException("should not have been called"); diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java index fdc00ba65255..610758d378de 100644 --- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java +++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java @@ -16,8 +16,6 @@ package android.window; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static org.junit.Assert.assertEquals; @@ -30,15 +28,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager.TaskDescription; -import android.content.ComponentName; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorSpace; -import android.graphics.Point; import android.graphics.Rect; -import android.hardware.HardwareBuffer; -import android.view.Surface; -import android.view.SurfaceControl; import android.view.WindowInsets; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -54,7 +46,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class SnapshotDrawerUtilsTest { - private SnapshotDrawerUtils.SnapshotSurface mSnapshotSurface; + private SnapshotDrawerUtils.SystemBarBackgroundPainter mSystemBarBackgroundPainter; private void setupSurface(int width, int height) { setupSurface(width, height, new Rect(), FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, @@ -70,31 +62,14 @@ public class SnapshotDrawerUtilsTest { // taskBounds assertEquals(width, taskBounds.width()); assertEquals(height, taskBounds.height()); - Point taskSize = new Point(taskBounds.width(), taskBounds.height()); - final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets); TaskDescription taskDescription = createTaskDescription(Color.WHITE, Color.RED, Color.BLUE); - mSnapshotSurface = new SnapshotDrawerUtils.SnapshotSurface( - new SurfaceControl(), snapshot, "Test"); - mSnapshotSurface.initiateSystemBarPainter(windowFlags, 0, 0, - taskDescription, WindowInsets.Type.defaultVisible()); - } - - private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize, - Rect contentInsets) { - final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, - 1, HardwareBuffer.USAGE_CPU_READ_RARELY); - return new TaskSnapshot( - System.currentTimeMillis(), - 0 /* captureTime */, - new ComponentName("", ""), buffer, - ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, - Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */, - false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, - 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */, - 0 /* uiMode */); + mSystemBarBackgroundPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter( + windowFlags, 0 /* windowPrivateFlags */, 0 /* appearance */, + taskDescription, 1f /* scale */, WindowInsets.Type.defaultVisible()); + mSystemBarBackgroundPainter.setInsets(contentInsets); } private static TaskDescription createTaskDescription(int background, @@ -107,134 +82,14 @@ public class SnapshotDrawerUtilsTest { } @Test - public void fillEmptyBackground_fillHorizontally() { - setupSurface(200, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(200); - when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200)); - verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); - } - - @Test - public void fillEmptyBackground_fillVertically() { - setupSurface(100, 200); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(200); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100)); - verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any()); - } - - @Test - public void fillEmptyBackground_fillBoth() { - setupSurface(200, 200); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(200); - when(mockCanvas.getHeight()).thenReturn(200); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); - verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); - verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any()); - } - - @Test - public void fillEmptyBackground_dontFill_sameSize() { - setupSurface(100, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void fillEmptyBackground_dontFill_bitmapLarger() { - setupSurface(100, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void testCalculateSnapshotCrop() { - final Rect contentInsets = new Rect(0, 10, 0, 10); - setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 100, 90), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotCrop_taskNotOnTop() { - final Rect contentInsets = new Rect(0, 10, 0, 10); - final Rect bounds = new Rect(0, 50, 100, 150); - setupSurface(100, 100, contentInsets, 0, bounds); - mSnapshotSurface.setFrames(bounds, contentInsets); - assertEquals(new Rect(0, 10, 100, 90), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotCrop_navBarLeft() { - final Rect contentInsets = new Rect(10, 0, 0, 0); - setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(10, 0, 100, 100), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotCrop_navBarRight() { - final Rect contentInsets = new Rect(0, 10, 10, 0); - setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 90, 100), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotCrop_waterfall() { - final Rect contentInsets = new Rect(5, 10, 5, 10); - setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(5, 0, 95, 90), - mSnapshotSurface.calculateSnapshotCrop(contentInsets)); - } - - @Test - public void testCalculateSnapshotFrame() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 0, 10); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); - assertEquals(new Rect(0, 0, 100, 80), - mSnapshotSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); - } - - @Test - public void testCalculateSnapshotFrame_navBarLeft() { - setupSurface(100, 100); - final Rect insets = new Rect(10, 10, 0, 0); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); - assertEquals(new Rect(10, 0, 100, 90), - mSnapshotSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); - } - - @Test - public void testCalculateSnapshotFrame_waterfall() { - setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100)); - final Rect insets = new Rect(0, 10, 0, 10); - mSnapshotSurface.setFrames(new Rect(5, 0, 95, 100), insets); - assertEquals(new Rect(0, 0, 90, 90), - mSnapshotSurface.calculateSnapshotFrame(new Rect(5, 0, 95, 90))); - } - - @Test public void testDrawStatusBarBackground() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100)); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, new Rect(0, 0, 50, 100)); verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); } @@ -242,11 +97,11 @@ public class SnapshotDrawerUtilsTest { public void testDrawStatusBarBackground_nullFrame() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawStatusBarBackground(mockCanvas, null); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, null /* alreadyDrawnFrame */); verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); } @@ -254,11 +109,11 @@ public class SnapshotDrawerUtilsTest { public void testDrawStatusBarBackground_nope() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100)); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, new Rect(0, 0, 100, 100)); verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); } @@ -267,11 +122,11 @@ public class SnapshotDrawerUtilsTest { final Rect insets = new Rect(0, 10, 0, 10); setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawNavigationBarBackground(mockCanvas); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, null /* alreadyDrawnFrame */); verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any()); } @@ -280,11 +135,11 @@ public class SnapshotDrawerUtilsTest { final Rect insets = new Rect(10, 10, 0, 0); setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawNavigationBarBackground(mockCanvas); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, null /* alreadyDrawnFrame */); verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any()); } @@ -293,11 +148,11 @@ public class SnapshotDrawerUtilsTest { final Rect insets = new Rect(0, 10, 10, 0); setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets); + mSystemBarBackgroundPainter.setInsets(insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); - mSnapshotSurface.drawNavigationBarBackground(mockCanvas); + mSystemBarBackgroundPainter.drawDecors(mockCanvas, null /* alreadyDrawnFrame */); verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any()); } } diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java index bb2fe1bcfc64..84ff40f0dcf0 100644 --- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java @@ -33,11 +33,15 @@ import android.app.ActivityThread; import android.content.res.Configuration; import android.os.IBinder; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.IWindowManager; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -58,6 +62,9 @@ public class WindowTokenClientControllerTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public SetFlagsRule setFlagsRule = new SetFlagsRule(); + @Mock private IWindowManager mWindowManagerService; @Mock @@ -161,6 +168,7 @@ public class WindowTokenClientControllerTest { verify(mWindowManagerService).detachWindowContext(mWindowTokenClient); } + @EnableFlags(Flags.FLAG_TRACK_SYSTEM_UI_CONTEXT_BEFORE_WMS) @Test public void testAttachToDisplayContent_keepTrackWithoutWMS() { // WMS is not initialized diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java index 5f25e9315831..c05888560f10 100644 --- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java @@ -931,6 +931,545 @@ public class VibrationEffectXmlSerializationTest { } @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testRepeating_withWaveformEnvelopeEffect_allSucceed() throws Exception { + VibrationEffect preamble = new VibrationEffect.WaveformEnvelopeBuilder() + .addControlPoint(0.1f, 50f, 10) + .addControlPoint(0.2f, 60f, 20) + .build(); + VibrationEffect repeating = new VibrationEffect.WaveformEnvelopeBuilder() + .setInitialFrequencyHz(70f) + .addControlPoint(0.3f, 80f, 25) + .addControlPoint(0.4f, 90f, 30) + .build(); + VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating); + + String xml = """ + <vibration-effect> + <repeating-effect> + <preamble> + <waveform-envelope-effect> + <control-point amplitude="0.1" frequencyHz="50.0" durationMs="10"/> + <control-point amplitude="0.2" frequencyHz="60.0" durationMs="20"/> + </waveform-envelope-effect> + </preamble> + <repeating> + <waveform-envelope-effect initialFrequencyHz="70.0"> + <control-point amplitude="0.3" frequencyHz="80.0" durationMs="25"/> + <control-point amplitude="0.4" frequencyHz="90.0" durationMs="30"/> + </waveform-envelope-effect> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "0.1", "0.2", "0.3", "0.4", "50.0", "60.0", + "70.0", "80.0", "90.0", "10", "20", "25", "30"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "0.1", "0.2", "0.3", "0.4", "50.0", "60.0", + "70.0", "80.0", "90.0", "10", "20", "25", "30"); + assertHiddenApisRoundTrip(effect); + + effect = VibrationEffect.createRepeatingEffect(repeating); + + xml = """ + <vibration-effect> + <repeating-effect> + <repeating> + <waveform-envelope-effect initialFrequencyHz="70.0"> + <control-point amplitude="0.3" frequencyHz="80.0" durationMs="25"/> + <control-point amplitude="0.4" frequencyHz="90.0" durationMs="30"/> + </waveform-envelope-effect> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "0.3", "0.4", "70.0", "80.0", "90.0", "25", + "30"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "0.3", "0.4", "70.0", "80.0", "90.0", "25", + "30"); + assertHiddenApisRoundTrip(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testRepeating_withBasicEnvelopeEffect_allSucceed() throws Exception { + VibrationEffect preamble = new VibrationEffect.BasicEnvelopeBuilder() + .addControlPoint(0.1f, 0.1f, 10) + .addControlPoint(0.2f, 0.2f, 20) + .addControlPoint(0.0f, 0.2f, 20) + .build(); + VibrationEffect repeating = new VibrationEffect.BasicEnvelopeBuilder() + .setInitialSharpness(0.3f) + .addControlPoint(0.3f, 0.4f, 25) + .addControlPoint(0.4f, 0.6f, 30) + .addControlPoint(0.0f, 0.7f, 35) + .build(); + VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating); + + String xml = """ + <vibration-effect> + <repeating-effect> + <preamble> + <basic-envelope-effect> + <control-point intensity="0.1" sharpness="0.1" durationMs="10" /> + <control-point intensity="0.2" sharpness="0.2" durationMs="20" /> + <control-point intensity="0.0" sharpness="0.2" durationMs="20" /> + </basic-envelope-effect> + </preamble> + <repeating> + <basic-envelope-effect initialSharpness="0.3"> + <control-point intensity="0.3" sharpness="0.4" durationMs="25" /> + <control-point intensity="0.4" sharpness="0.6" durationMs="30" /> + <control-point intensity="0.0" sharpness="0.7" durationMs="35" /> + </basic-envelope-effect> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "0.0", "0.1", "0.2", "0.3", "0.4", "0.1", "0.2", + "0.3", "0.4", "0.6", "0.7", "10", "20", "25", "30", "35"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "0.0", "0.1", "0.2", "0.3", "0.4", "0.1", "0.2", + "0.3", "0.4", "0.6", "0.7", "10", "20", "25", "30", "35"); + assertHiddenApisRoundTrip(effect); + + effect = VibrationEffect.createRepeatingEffect(repeating); + + xml = """ + <vibration-effect> + <repeating-effect> + <repeating> + <basic-envelope-effect initialSharpness="0.3"> + <control-point intensity="0.3" sharpness="0.4" durationMs="25" /> + <control-point intensity="0.4" sharpness="0.6" durationMs="30" /> + <control-point intensity="0.0" sharpness="0.7" durationMs="35" /> + </basic-envelope-effect> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "0.3", "0.4", "0.0", "0.4", "0.6", "0.7", "25", + "30", "35"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "0.3", "0.4", "0.0", "0.4", "0.6", "0.7", "25", + "30", "35"); + assertHiddenApisRoundTrip(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testRepeating_withPredefinedEffects_allSucceed() throws Exception { + for (Map.Entry<String, Integer> entry : createPublicPredefinedEffectsMap().entrySet()) { + VibrationEffect preamble = VibrationEffect.get(entry.getValue()); + VibrationEffect repeating = VibrationEffect.get(entry.getValue()); + VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating); + String xml = String.format(""" + <vibration-effect> + <repeating-effect> + <preamble> + <predefined-effect name="%s"/> + </preamble> + <repeating> + <predefined-effect name="%s"/> + </repeating> + </repeating-effect> + </vibration-effect> + """, + entry.getKey(), entry.getKey()); + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, entry.getKey()); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, entry.getKey()); + assertHiddenApisRoundTrip(effect); + + effect = VibrationEffect.createRepeatingEffect(repeating); + xml = String.format(""" + <vibration-effect> + <repeating-effect> + <repeating> + <predefined-effect name="%s"/> + </repeating> + </repeating-effect> + </vibration-effect> + """, + entry.getKey()); + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, entry.getKey()); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, entry.getKey()); + assertHiddenApisRoundTrip(effect); + } + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testRepeating_withWaveformEntry_allSucceed() throws Exception { + VibrationEffect preamble = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0}, + new int[]{254, 1, 255, 0}, /* repeat= */ -1); + VibrationEffect repeating = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0}, + new int[]{254, 1, 255, 0}, /* repeat= */ -1); + VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating); + + String xml = """ + <vibration-effect> + <repeating-effect> + <preamble> + <waveform-entry durationMs="123" amplitude="254"/> + <waveform-entry durationMs="456" amplitude="1"/> + <waveform-entry durationMs="789" amplitude="255"/> + <waveform-entry durationMs="0" amplitude="0"/> + </preamble> + <repeating> + <waveform-entry durationMs="123" amplitude="254"/> + <waveform-entry durationMs="456" amplitude="1"/> + <waveform-entry durationMs="789" amplitude="255"/> + <waveform-entry durationMs="0" amplitude="0"/> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0"); + assertHiddenApisRoundTrip(effect); + + xml = """ + <vibration-effect> + <repeating-effect> + <repeating> + <waveform-entry durationMs="123" amplitude="254"/> + <waveform-entry durationMs="456" amplitude="1"/> + <waveform-entry durationMs="789" amplitude="255"/> + <waveform-entry durationMs="0" amplitude="0"/> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + effect = VibrationEffect.createRepeatingEffect(repeating); + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0"); + assertHiddenApisRoundTrip(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testRepeating_withPrimitives_allSucceed() throws Exception { + VibrationEffect preamble = VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_TICK, 0.2497f) + .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356) + .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7) + .compose(); + VibrationEffect repeating = VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_TICK, 0.2497f) + .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356) + .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7) + .compose(); + VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating); + + String xml = """ + <vibration-effect> + <repeating-effect> + <preamble> + <primitive-effect name="click" /> + <primitive-effect name="tick" scale="0.2497" /> + <primitive-effect name="low_tick" delayMs="356" /> + <primitive-effect name="spin" scale="0.6364" delayMs="7" /> + </preamble> + <repeating> + <primitive-effect name="click" /> + <primitive-effect name="tick" scale="0.2497" /> + <primitive-effect name="low_tick" delayMs="356" /> + <primitive-effect name="spin" scale="0.6364" delayMs="7" /> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin"); + assertHiddenApisRoundTrip(effect); + + repeating = VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_TICK, 0.2497f) + .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356) + .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7) + .compose(); + effect = VibrationEffect.createRepeatingEffect(repeating); + + xml = """ + <vibration-effect> + <repeating-effect> + <repeating> + <primitive-effect name="click" /> + <primitive-effect name="tick" scale="0.2497" /> + <primitive-effect name="low_tick" delayMs="356" /> + <primitive-effect name="spin" scale="0.6364" delayMs="7" /> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin"); + assertHiddenApisRoundTrip(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testRepeating_withMixedVibrations_allSucceed() throws Exception { + VibrationEffect preamble = new VibrationEffect.WaveformEnvelopeBuilder() + .addControlPoint(0.1f, 50f, 10) + .build(); + VibrationEffect repeating = VibrationEffect.get(VibrationEffect.EFFECT_TICK); + VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating); + String xml = """ + <vibration-effect> + <repeating-effect> + <preamble> + <waveform-envelope-effect> + <control-point amplitude="0.1" frequencyHz="50.0" durationMs="10"/> + </waveform-envelope-effect> + </preamble> + <repeating> + <predefined-effect name="tick"/> + </repeating> + </repeating-effect> + </vibration-effect> + """; + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "0.1", "50.0", "10", "tick"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "0.1", "50.0", "10", "tick"); + assertHiddenApisRoundTrip(effect); + + preamble = VibrationEffect.createWaveform(new long[]{123, 456}, + new int[]{254, 1}, /* repeat= */ -1); + repeating = new VibrationEffect.BasicEnvelopeBuilder() + .addControlPoint(0.3f, 0.4f, 25) + .addControlPoint(0.0f, 0.5f, 30) + .build(); + effect = VibrationEffect.createRepeatingEffect(preamble, repeating); + + xml = """ + <vibration-effect> + <repeating-effect> + <preamble> + <waveform-entry durationMs="123" amplitude="254"/> + <waveform-entry durationMs="456" amplitude="1"/> + </preamble> + <repeating> + <basic-envelope-effect> + <control-point intensity="0.3" sharpness="0.4" durationMs="25" /> + <control-point intensity="0.0" sharpness="0.5" durationMs="30" /> + </basic-envelope-effect> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "123", "456", "254", "1", "0.3", "0.0", "0.4", + "0.5", "25", "30"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "123", "456", "254", "1", "0.3", "0.0", "0.4", + "0.5", "25", "30"); + assertHiddenApisRoundTrip(effect); + + preamble = VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_CLICK) + .compose(); + effect = VibrationEffect.createRepeatingEffect(preamble, repeating); + + xml = """ + <vibration-effect> + <repeating-effect> + <preamble> + <primitive-effect name="click" /> + </preamble> + <repeating> + <basic-envelope-effect> + <control-point intensity="0.3" sharpness="0.4" durationMs="25" /> + <control-point intensity="0.0" sharpness="0.5" durationMs="30" /> + </basic-envelope-effect> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserSucceeds(xml, effect); + assertPublicApisSerializerSucceeds(effect, "click", "0.3", "0.4", "0.0", "0.5", "25", "30"); + assertPublicApisRoundTrip(effect); + + assertHiddenApisParserSucceeds(xml, effect); + assertHiddenApisSerializerSucceeds(effect, "click", "0.3", "0.4", "0.0", "0.5", "25", "30"); + assertHiddenApisRoundTrip(effect); + } + + @Test + @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testRepeating_badXml_throwsException() throws IOException { + // Incomplete XML + assertParseElementFails(""" + <vibration-effect> + <repeating-effect> + <preamble> + <primitive-effect name="click" /> + </preamble> + <repeating> + <primitive-effect name="click" /> + """); + + assertParseElementFails(""" + <vibration-effect> + <repeating-effect> + <primitive-effect name="click" /> + <repeating> + <primitive-effect name="click" /> + </repeating> + </repeating-effect> + </vibration-effect> + """); + + assertParseElementFails(""" + <vibration-effect> + <repeating-effect> + <preamble> + <primitive-effect name="click" /> + </preamble> + <primitive-effect name="click" /> + </repeating-effect> + </vibration-effect> + """); + + // Bad vibration XML + assertParseElementFails(""" + <vibration-effect> + <repeating-effect> + <repeating> + <primitive-effect name="click" /> + </repeating> + <preamble> + <primitive-effect name="click" /> + </preamble> + </repeating-effect> + </vibration-effect> + """); + + assertParseElementFails(""" + <vibration-effect> + <repeating-effect> + <repeating> + <preamble> + <primitive-effect name="click" /> + </preamble> + <primitive-effect name="click" /> + </repeating> + </repeating-effect> + </vibration-effect> + """); + + assertParseElementFails(""" + <vibration-effect> + <repeating-effect> + <preamble> + <primitive-effect name="click" /> + <repeating> + <primitive-effect name="click" /> + </repeating> + </preamble> + </repeating-effect> + </vibration-effect> + """); + + assertParseElementFails(""" + <vibration-effect> + <repeating-effect> + <primitive-effect name="click" /> + <primitive-effect name="click" /> + </repeating-effect> + </vibration-effect> + """); + } + + @Test + @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS) + public void testRepeatingEffect_featureFlagDisabled_allFail() throws Exception { + VibrationEffect repeating = VibrationEffect.startComposition() + .addPrimitive(PRIMITIVE_CLICK) + .addPrimitive(PRIMITIVE_TICK, 0.2497f) + .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356) + .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7) + .compose(); + VibrationEffect effect = VibrationEffect.createRepeatingEffect(repeating); + + String xml = """ + <vibration-effect> + <repeating-effect> + <repeating> + <primitive-effect name="click" /> + <primitive-effect name="tick" scale="0.2497" /> + <primitive-effect name="low_tick" delayMs="356" /> + <primitive-effect name="spin" scale="0.6364" delayMs="7" /> + </repeating> + </repeating-effect> + </vibration-effect> + """; + + assertPublicApisParserFails(xml); + assertPublicApisSerializerFails(effect); + assertHiddenApisParserFails(xml); + assertHiddenApisSerializerFails(effect); + } + + @Test @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void testVendorEffect_allSucceed() throws Exception { PersistableBundle vendorData = new PersistableBundle(); diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt index 29f8d199c1d1..89ca04432fa7 100644 --- a/core/xsd/vibrator/vibration/schema/current.txt +++ b/core/xsd/vibrator/vibration/schema/current.txt @@ -62,17 +62,41 @@ package com.android.internal.vibrator.persistence { enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName tick; } + public class RepeatingEffect { + ctor public RepeatingEffect(); + method public com.android.internal.vibrator.persistence.RepeatingEffectEntry getPreamble(); + method public com.android.internal.vibrator.persistence.RepeatingEffectEntry getRepeating(); + method public void setPreamble(com.android.internal.vibrator.persistence.RepeatingEffectEntry); + method public void setRepeating(com.android.internal.vibrator.persistence.RepeatingEffectEntry); + } + + public class RepeatingEffectEntry { + ctor public RepeatingEffectEntry(); + method public com.android.internal.vibrator.persistence.BasicEnvelopeEffect getBasicEnvelopeEffect_optional(); + method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional(); + method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional(); + method public com.android.internal.vibrator.persistence.WaveformEntry getWaveformEntry_optional(); + method public com.android.internal.vibrator.persistence.WaveformEnvelopeEffect getWaveformEnvelopeEffect_optional(); + method public void setBasicEnvelopeEffect_optional(com.android.internal.vibrator.persistence.BasicEnvelopeEffect); + method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect); + method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect); + method public void setWaveformEntry_optional(com.android.internal.vibrator.persistence.WaveformEntry); + method public void setWaveformEnvelopeEffect_optional(com.android.internal.vibrator.persistence.WaveformEnvelopeEffect); + } + public class VibrationEffect { ctor public VibrationEffect(); method public com.android.internal.vibrator.persistence.BasicEnvelopeEffect getBasicEnvelopeEffect_optional(); method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional(); method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional(); + method public com.android.internal.vibrator.persistence.RepeatingEffect getRepeatingEffect_optional(); method public byte[] getVendorEffect_optional(); method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional(); method public com.android.internal.vibrator.persistence.WaveformEnvelopeEffect getWaveformEnvelopeEffect_optional(); method public void setBasicEnvelopeEffect_optional(com.android.internal.vibrator.persistence.BasicEnvelopeEffect); method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect); method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect); + method public void setRepeatingEffect_optional(com.android.internal.vibrator.persistence.RepeatingEffect); method public void setVendorEffect_optional(byte[]); method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect); method public void setWaveformEnvelopeEffect_optional(com.android.internal.vibrator.persistence.WaveformEnvelopeEffect); diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd index b4df2d187702..57bcde7c97d4 100644 --- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd +++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd @@ -60,6 +60,30 @@ <!-- Basic envelope effect --> <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/> + <!-- Repeating vibration effect --> + <xs:element name="repeating-effect" type="RepeatingEffect"/> + + </xs:choice> + </xs:complexType> + + <xs:complexType name="RepeatingEffect"> + <xs:sequence> + <xs:element name="preamble" maxOccurs="1" minOccurs="0" type="RepeatingEffectEntry" /> + <xs:element name="repeating" maxOccurs="1" minOccurs="1" type="RepeatingEffectEntry" /> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="RepeatingEffectEntry"> + <xs:choice> + <xs:element name="predefined-effect" type="PredefinedEffect" /> + <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect" /> + <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect" /> + <xs:sequence> + <xs:element name="waveform-entry" type="WaveformEntry" /> + </xs:sequence> + <xs:sequence> + <xs:element name="primitive-effect" type="PrimitiveEffect" /> + </xs:sequence> </xs:choice> </xs:complexType> diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd index fba966faa9c9..c11fb667e709 100644 --- a/core/xsd/vibrator/vibration/vibration.xsd +++ b/core/xsd/vibrator/vibration/vibration.xsd @@ -58,9 +58,34 @@ <!-- Basic envelope effect --> <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/> + <!-- Repeating vibration effect --> + <xs:element name="repeating-effect" type="RepeatingEffect"/> + + </xs:choice> + </xs:complexType> + + <xs:complexType name="RepeatingEffect"> + <xs:sequence> + <xs:element name="preamble" maxOccurs="1" minOccurs="0" type="RepeatingEffectEntry" /> + <xs:element name="repeating" maxOccurs="1" minOccurs="1" type="RepeatingEffectEntry" /> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="RepeatingEffectEntry"> + <xs:choice> + <xs:element name="predefined-effect" type="PredefinedEffect" /> + <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect" /> + <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect" /> + <xs:sequence> + <xs:element name="waveform-entry" type="WaveformEntry" /> + </xs:sequence> + <xs:sequence> + <xs:element name="primitive-effect" type="PrimitiveEffect" /> + </xs:sequence> </xs:choice> </xs:complexType> + <xs:complexType name="WaveformEffect"> <xs:sequence> diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt index 087378bef6a1..3da69e854b63 100644 --- a/framework-jarjar-rules.txt +++ b/framework-jarjar-rules.txt @@ -10,3 +10,6 @@ rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1 # For Perfetto proto dependencies rule perfetto.protos.** android.internal.perfetto.protos.@1 + +# For aconfig storage classes +rule android.aconfig.storage.** android.internal.aconfig.storage.@1 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt index bc56637b2a1e..d1dcc9b1d591 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt @@ -18,22 +18,29 @@ package com.android.wm.shell.compatui -import android.app.TaskInfo +import android.app.ActivityManager.RunningTaskInfo import android.content.Context import com.android.internal.R // TODO(b/347289970): Consider replacing with API /** * If the top activity should be exempt from desktop windowing and forced back to fullscreen. - * Currently includes all system ui activities and modal dialogs. However is the top activity is not + * Currently includes all system ui activities and modal dialogs. However if the top activity is not * being displayed, regardless of its configuration, we will not exempt it as to remain in the * desktop windowing environment. */ -fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) = - (isSystemUiTask(context, task) || (task.numActivities > 0 && task.isActivityStackTransparent)) +fun isTopActivityExemptFromDesktopWindowing(context: Context, task: RunningTaskInfo) = + (isSystemUiTask(context, task) || isTransparentTask(task)) && !task.isTopActivityNoDisplay -private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean { +/** + * Returns true if all activities in a tasks stack are transparent. If there are no activities will + * return false. + */ +fun isTransparentTask(task: RunningTaskInfo): Boolean = task.isActivityStackTransparent + && task.numActivities > 0 + +private fun isSystemUiTask(context: Context, task: RunningTaskInfo): Boolean { val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi) return task.baseActivity?.packageName == sysUiPackageName diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index c4abee3bed78..c5b570dd3d57 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -54,7 +54,9 @@ class DesktopRepository( * @property closingTasks task ids for tasks that are going to close, but are currently visible. * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode. - * (top is at index 0). + * @property topTransparentFullscreenTaskId the task id of any current top transparent + * fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is + * closed or sent to back. (top is at index 0). */ private data class DesktopTaskData( val activeTasks: ArraySet<Int> = ArraySet(), @@ -64,6 +66,7 @@ class DesktopRepository( val closingTasks: ArraySet<Int> = ArraySet(), val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), var fullImmersiveTaskId: Int? = null, + var topTransparentFullscreenTaskId: Int? = null, ) { fun deepCopy(): DesktopTaskData = DesktopTaskData( @@ -73,6 +76,7 @@ class DesktopRepository( closingTasks = ArraySet(closingTasks), freeformTasksInZOrder = ArrayList(freeformTasksInZOrder), fullImmersiveTaskId = fullImmersiveTaskId, + topTransparentFullscreenTaskId = topTransparentFullscreenTaskId, ) fun clear() { @@ -82,6 +86,7 @@ class DesktopRepository( closingTasks.clear() freeformTasksInZOrder.clear() fullImmersiveTaskId = null + topTransparentFullscreenTaskId = null } } @@ -322,13 +327,27 @@ class DesktopRepository( fun getTaskInFullImmersiveState(displayId: Int): Int? = desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId + /** Sets the top transparent fullscreen task id for a given display. */ + fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) { + desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = taskId + } + + /** Returns the top transparent fullscreen task id for a given display, or null. */ + fun getTopTransparentFullscreenTaskId(displayId: Int): Int? = + desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId + + /** Clears the top transparent fullscreen task id info for a given display. */ + fun clearTopTransparentFullscreenTaskId(displayId: Int) { + desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = null + } + private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) { visibleTasksListeners.forEach { (listener, executor) -> executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) } } } - /** Gets number of visible tasks on given [displayId] */ + /** Gets number of visible freeform tasks on given [displayId] */ fun getVisibleTaskCount(displayId: Int): Int = desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size ?: 0.also { logD("getVisibleTaskCount=$it") } @@ -526,6 +545,10 @@ class DesktopRepository( ) pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}") pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}") + pw.println( + "${innerPrefix}topTransparentFullscreenTaskId=" + + "${data.topTransparentFullscreenTaskId}" + ) pw.println("${innerPrefix}wallpaperActivityToken=$wallpaperActivityToken") } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 609ac0aac381..a3d3a90fef3e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -21,7 +21,6 @@ import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.KeyguardManager import android.app.PendingIntent -import android.app.TaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -84,6 +83,7 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing +import com.android.wm.shell.compatui.isTransparentTask import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum @@ -308,11 +308,23 @@ class DesktopTasksController( } } - /** Gets number of visible tasks in [displayId]. */ + /** Gets number of visible freeform tasks in [displayId]. */ fun visibleTaskCount(displayId: Int): Int = taskRepository.getVisibleTaskCount(displayId) - /** Returns true if any tasks are visible in Desktop Mode. */ - fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0 + /** + * Returns true if any freeform tasks are visible or if a transparent fullscreen task exists on + * top in Desktop Mode. + */ + fun isDesktopModeShowing(displayId: Int): Boolean { + if ( + DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC + .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() + ) { + return visibleTaskCount(displayId) > 0 || + taskRepository.getTopTransparentFullscreenTaskId(displayId) != null + } + return visibleTaskCount(displayId) > 0 + } /** Moves focused task to desktop mode for given [displayId]. */ fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) { @@ -863,7 +875,11 @@ class DesktopTasksController( } val wct = WindowContainerTransaction() - if (!task.isFreeform) addMoveToDesktopChanges(wct, task, displayId) + if (!task.isFreeform) { + addMoveToDesktopChanges(wct, task, displayId) + } else if (Flags.enableMoveToNextDisplayShortcut()) { + applyFreeformDisplayChange(wct, task, displayId) + } wct.reparent(task.token, displayAreaInfo.token, true /* onTop */) if (Flags.enablePerDisplayDesktopWallpaperActivity()) { @@ -1592,7 +1608,7 @@ class DesktopTasksController( TransitionUtil.isOpeningType(request.type) && taskRepository.isActiveTask(triggerTask.taskId)) - private fun isIncompatibleTask(task: TaskInfo) = + private fun isIncompatibleTask(task: RunningTaskInfo) = DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && isTopActivityExemptFromDesktopWindowing(context, task) @@ -1848,6 +1864,15 @@ class DesktopTasksController( * fullscreen. */ private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + logV("handleIncompatibleTaskLaunch") + if (!isDesktopModeShowing(task.displayId)) return null + // Only update task repository for transparent task. + if ( + DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC + .isTrue() && isTransparentTask(task) + ) { + taskRepository.setTopTransparentFullscreenTaskId(task.displayId, task.taskId) + } // Already fullscreen, no-op. if (task.isFullscreen) return null return WindowContainerTransaction().also { wct -> addMoveToFullscreenChanges(wct, task) } @@ -1909,6 +1934,50 @@ class DesktopTasksController( } } + /** + * Apply changes to move a freeform task from one display to another, which includes handling + * density changes between displays. + */ + private fun applyFreeformDisplayChange( + wct: WindowContainerTransaction, + taskInfo: RunningTaskInfo, + destDisplayId: Int, + ) { + val sourceLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return + val destLayout = displayController.getDisplayLayout(destDisplayId) ?: return + val bounds = taskInfo.configuration.windowConfiguration.bounds + val scaledWidth = bounds.width() * destLayout.densityDpi() / sourceLayout.densityDpi() + val scaledHeight = bounds.height() * destLayout.densityDpi() / sourceLayout.densityDpi() + val sourceWidthMargin = sourceLayout.width() - bounds.width() + val sourceHeightMargin = sourceLayout.height() - bounds.height() + val destWidthMargin = destLayout.width() - scaledWidth + val destHeightMargin = destLayout.height() - scaledHeight + val scaledLeft = + if (sourceWidthMargin != 0) { + bounds.left * destWidthMargin / sourceWidthMargin + } else { + destWidthMargin / 2 + } + val scaledTop = + if (sourceHeightMargin != 0) { + bounds.top * destHeightMargin / sourceHeightMargin + } else { + destHeightMargin / 2 + } + val boundsWithinDisplay = + if (destWidthMargin >= 0 && destHeightMargin >= 0) { + Rect(0, 0, scaledWidth, scaledHeight).apply { + offsetTo( + scaledLeft.coerceIn(0, destWidthMargin), + scaledTop.coerceIn(0, destHeightMargin), + ) + } + } else { + getInitialBounds(destLayout, taskInfo, destDisplayId) + } + wct.setBounds(taskInfo.token, boundsWithinDisplay) + } + private fun getInitialBounds( displayLayout: DisplayLayout, taskInfo: RunningTaskInfo, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 9625b71ad3cb..5c79658b6809 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -75,6 +75,12 @@ class DesktopTasksTransitionObserver( finishTransaction: SurfaceControl.Transaction, ) { // TODO: b/332682201 Update repository state + if ( + DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC + .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() + ) { + updateTopTransparentFullscreenTaskId(info) + } updateWallpaperToken(info) if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { handleBackNavigation(transition, info) @@ -264,4 +270,22 @@ class DesktopTasksTransitionObserver( } } } + + private fun updateTopTransparentFullscreenTaskId(info: TransitionInfo) { + info.changes.forEach { change -> + change.taskInfo?.let { task -> + val desktopRepository = desktopUserRepositories.getProfile(task.userId) + val displayId = task.displayId + // Clear `topTransparentFullscreenTask` information from repository if task + // is closed or sent to back. + if ( + TransitionUtil.isClosingMode(change.mode) && + task.taskId == + desktopRepository.getTopTransparentFullscreenTaskId(displayId) + ) { + desktopRepository.clearTopTransparentFullscreenTaskId(displayId) + } + } + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java index a611fe1db2ce..c4ff87d175a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java @@ -74,7 +74,7 @@ public class DragSession { mInitialDragData = data; mInitialDragFlags = dragFlags; displayLayout = dispLayout; - hideDragSourceTaskId = data.getDescription().getExtras() != null + hideDragSourceTaskId = data != null && data.getDescription().getExtras() != null ? data.getDescription().getExtras().getInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, -1) : -1; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index fd387d1811fb..37296531ee34 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -350,7 +350,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } cancelPhysicsAnimation(); mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */); - mPipScheduler.removePipAfterAnimation(); + mPipScheduler.scheduleRemovePip(); } /** Sets the movement bounds to use to constrain PIP position animations. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 4461a5c6a70c..7f673d2efc68 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -122,34 +122,26 @@ public class PipScheduler { * Schedules exit PiP via expand transition. */ public void scheduleExitPipViaExpand() { - WindowContainerTransaction wct = getExitPipViaExpandTransaction(); - if (wct != null) { - mMainExecutor.execute(() -> { + mMainExecutor.execute(() -> { + if (!mPipTransitionState.isInPip()) return; + WindowContainerTransaction wct = getExitPipViaExpandTransaction(); + if (wct != null) { mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, null /* destinationBounds */); - }); - } - } - - // TODO: Optimize this by running the animation as part of the transition - /** Runs remove PiP animation and schedules remove PiP transition after the animation ends. */ - public void removePipAfterAnimation() { - SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); - PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext, - mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT); - animator.setAnimationEndCallback(this::scheduleRemovePipImmediately); - animator.start(); + } + }); } /** Schedules remove PiP transition. */ - private void scheduleRemovePipImmediately() { - WindowContainerTransaction wct = getRemovePipTransaction(); - if (wct != null) { - mMainExecutor.execute(() -> { + public void scheduleRemovePip() { + mMainExecutor.execute(() -> { + if (!mPipTransitionState.isInPip()) return; + WindowContainerTransaction wct = getRemovePipTransaction(); + if (wct != null) { mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct, null /* destinationBounds */); - }); - } + } + }); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index acb5622b041c..2e38449d4584 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -278,7 +278,8 @@ public class PipTransition extends PipTransitionController implements } if (isRemovePipTransition(info)) { - return removePipImmediately(info, startTransaction, finishTransaction, finishCallback); + mPipTransitionState.setState(PipTransitionState.EXITING_PIP); + return startRemoveAnimation(info, startTransaction, finishTransaction, finishCallback); } return false; } @@ -668,13 +669,18 @@ public class PipTransition extends PipTransitionController implements return true; } - private boolean removePipImmediately(@NonNull TransitionInfo info, + private boolean startRemoveAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - startTransaction.apply(); - finishCallback.onTransitionFinished(null); - mPipTransitionState.setState(PipTransitionState.EXITED_PIP); + TransitionInfo.Change pipChange = getChangeByToken(info, + mPipTransitionState.getPipTaskToken()); + mFinishCallback = finishCallback; + PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(), + startTransaction, PipAlphaAnimator.FADE_OUT); + finishTransaction.setAlpha(pipChange.getLeash(), 0f); + animator.setAnimationEndCallback(this::finishTransition); + animator.start(); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 5dd49f0b5c0f..39ed2061c675 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -302,7 +302,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, mMainExecutor, mMainHandler, mBgExecutor, mRecentTasksOptional, - mLaunchAdjacentController, mWindowDecorViewModel, mSplitState); + mLaunchAdjacentController, mWindowDecorViewModel, mSplitState, + mDesktopTasksController); } @Override 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 d0c21c9ec7c0..246760e361cd 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 @@ -142,6 +142,7 @@ import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.common.split.SplitWindowManager; +import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.shared.TransactionPool; @@ -222,6 +223,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final Optional<RecentTasksController> mRecentTasks; private final LaunchAdjacentController mLaunchAdjacentController; private final Optional<WindowDecorViewModel> mWindowDecorViewModel; + private final Optional<DesktopTasksController> mDesktopTasksController; /** Singleton source of truth for the current state of split screen on this device. */ private final SplitState mSplitState; @@ -358,7 +360,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ShellExecutor bgExecutor, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, - Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) { + Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState, + Optional<DesktopTasksController> desktopTasksController) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -371,6 +374,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLaunchAdjacentController = launchAdjacentController; mWindowDecorViewModel = windowDecorViewModel; mSplitState = splitState; + mDesktopTasksController = desktopTasksController; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); @@ -443,7 +447,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ShellExecutor bgExecutor, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, - Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) { + Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState, + Optional<DesktopTasksController> desktopTasksController) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -465,6 +470,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLaunchAdjacentController = launchAdjacentController; mWindowDecorViewModel = windowDecorViewModel; mSplitState = splitState; + mDesktopTasksController = desktopTasksController; mDisplayController.addDisplayWindowListener(this); transitions.addHandler(this); @@ -2768,11 +2774,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final @WindowManager.TransitionType int type = request.getType(); final boolean isOpening = isOpeningType(type); final boolean inFullscreen = triggerTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; - final boolean inDesktopMode = DesktopModeStatus.canEnterDesktopMode(mContext) - && triggerTask.getWindowingMode() == WINDOWING_MODE_FREEFORM; + final boolean inDesktopMode = mDesktopTasksController.isPresent() + && mDesktopTasksController.get().isDesktopModeShowing(mDisplayId); + final boolean isLaunchingDesktopTask = isOpening && DesktopModeStatus.canEnterDesktopMode( + mContext) && triggerTask.getWindowingMode() == WINDOWING_MODE_FREEFORM; final StageTaskListener stage = getStageOfTask(triggerTask); - if (isOpening && inFullscreen) { + if (inDesktopMode || isLaunchingDesktopTask) { + // Don't handle request when desktop mode is showing (since they don't coexist), or + // when launching a desktop task (defer to DesktopTasksController) + return null; + } else if (isOpening && inFullscreen) { // One task is opening into fullscreen mode, remove the corresponding split record. mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); logExit(EXIT_REASON_FULLSCREEN_REQUEST); @@ -2824,12 +2836,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitTransitions.setDismissTransition(transition, stageType, EXIT_REASON_FULLSCREEN_REQUEST); } - } else if (isOpening && inDesktopMode) { - // If the app being opened is in Desktop mode, set it to full screen and dismiss - // split screen stage. - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); - out.setWindowingMode(triggerTask.token, WINDOWING_MODE_UNDEFINED) - .setBounds(triggerTask.token, null); } else if (isOpening && inFullscreen) { final int activityType = triggerTask.getActivityType(); if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { @@ -2999,7 +3005,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.update(startTransaction, false /* resetImePosition */); } - if (mMixedHandler.isEnteringPip(change, transitType)) { + if (mMixedHandler.isEnteringPip(change, transitType) + && getSplitItemStage(change.getLastParent()) != STAGE_TYPE_UNDEFINED) { pipChange = change; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java index a318bcf97e6e..9d85bea421ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java @@ -59,7 +59,7 @@ public class TvStageCoordinator extends StageCoordinator super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, mainExecutor, mainHandler, bgExecutor, recentTasks, launchAdjacentController, - Optional.empty(), splitState); + Optional.empty(), splitState, Optional.empty()); mTvSplitMenuController = new TvSplitMenuController(context, this, systemWindows, mainHandler); 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 27472493a8bc..0519a5e055be 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 @@ -89,8 +89,6 @@ public class TaskSnapshotWindow { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId); - final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; - final WindowManager.LayoutParams layoutParams = SnapshotDrawerUtils.createLayoutParameters( info, TITLE_FORMAT + taskId, TYPE_APPLICATION_STARTING, snapshot.getHardwareBuffer().getFormat(), appToken); @@ -152,8 +150,8 @@ public class TaskSnapshotWindow { return null; } - SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot, - info.taskBounds, topWindowInsetsState, true /* releaseAfterDraw */); + SnapshotDrawerUtils.drawSnapshotOnSurface(layoutParams, surfaceControl, snapshot, + info.taskBounds, true /* releaseAfterDraw */); snapshotSurface.mHasDrawn = true; snapshotSurface.reportDrawn(); 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 index 2a22d4dd0cb5..34d1011bac0e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java @@ -26,7 +26,6 @@ import android.content.Context; 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; @@ -77,12 +76,11 @@ class WindowlessSnapshotWindowCreator { final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost( mContext, display, wlw, "WindowlessSnapshotWindowCreator"); 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, - windowBounds, topWindowInsetsState, false /* releaseAfterDraw */); + SnapshotDrawerUtils.drawSnapshotOnSurface(lp, wlw.mChildSurface, snapshot, + windowBounds, false /* releaseAfterDraw */); final ActivityManager.TaskDescription taskDescription = SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo); diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt index f4f60d73c25c..ab1ac1a0efa3 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt @@ -44,6 +44,7 @@ import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds import android.tools.flicker.assertors.assertions.AppWindowReturnsToStartBoundsAndPosition import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow +import android.tools.flicker.assertors.assertions.VisibleLayersShownMoreThanOneConsecutiveEntry import android.tools.flicker.config.AssertionTemplates import android.tools.flicker.config.FlickerConfigEntry import android.tools.flicker.config.ScenarioId @@ -262,6 +263,40 @@ class DesktopModeFlickerScenarios { ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) + val SNAP_RESIZE_LEFT_WITH_KEYBOARD = + FlickerConfigEntry( + scenarioId = ScenarioId("SNAP_RESIZE_LEFT_WITH_KEYBOARD"), + extractor = + TaggedScenarioExtractorBuilder() + .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE) + .setTransitionMatcher( + TaggedCujTransitionMatcher(associatedTransitionRequired = false) + ) + .build(), + assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf( + AppWindowCoversLeftHalfScreenAtEnd( + DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO + ) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + ) + + val SNAP_RESIZE_RIGHT_WITH_KEYBOARD = + FlickerConfigEntry( + scenarioId = ScenarioId("SNAP_RESIZE_RIGHT_WITH_KEYBOARD"), + extractor = + TaggedScenarioExtractorBuilder() + .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE) + .setTransitionMatcher( + TaggedCujTransitionMatcher(associatedTransitionRequired = false) + ) + .build(), + assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf( + AppWindowCoversRightHalfScreenAtEnd( + DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO + ) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + ) + val SNAP_RESIZE_LEFT_WITH_DRAG = FlickerConfigEntry( scenarioId = ScenarioId("SNAP_RESIZE_LEFT_WITH_DRAG"), @@ -429,7 +464,9 @@ class DesktopModeFlickerScenarios { } ), assertions = - AssertionTemplates.COMMON_ASSERTIONS + + AssertionTemplates.COMMON_ASSERTIONS.toMutableMap().also { + it.remove(VisibleLayersShownMoreThanOneConsecutiveEntry()) + } + listOf( AppWindowOnTopAtStart(DESKTOP_MODE_APP), AppWindowBecomesInvisible(DESKTOP_MODE_APP), @@ -455,7 +492,9 @@ class DesktopModeFlickerScenarios { } ), assertions = - AssertionTemplates.COMMON_ASSERTIONS + + AssertionTemplates.COMMON_ASSERTIONS.toMutableMap().also { + it.remove(VisibleLayersShownMoreThanOneConsecutiveEntry()) + } + listOf( AppWindowOnTopAtStart(DESKTOP_MODE_APP), AppWindowBecomesInvisible(DESKTOP_MODE_APP), diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt new file mode 100644 index 000000000000..56f1dcbf838c --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP +import com.android.wm.shell.scenarios.MinimizeAppWindows +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Minimize app windows by pressing META + -. + * + * Assert that the app windows gets hidden. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class MinimizeAppsWithKeyboard : MinimizeAppWindows(usingKeyboard = true) { + @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"]) + @Test + override fun minimizeAllAppWindows() = super.minimizeAllAppWindows() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig() + .use(FlickerServiceConfig.DEFAULT) + .use(MINIMIZE_APP) + .use(MINIMIZE_LAST_APP) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithKeyboard.kt new file mode 100644 index 000000000000..b0b78a218bb8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithKeyboard.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_LEFT_WITH_KEYBOARD +import com.android.wm.shell.scenarios.SnapResizeAppWindowWithKeyboardShortcuts +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Snap resize app window using keyboard shortcut META + [. + * + * Assert that the app window fills the left half the display after being snap resized. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SnapResizeAppWindowLeftWithKeyboard : SnapResizeAppWindowWithKeyboardShortcuts(toLeft = true) { + @ExpectedScenarios(["SNAP_RESIZE_LEFT_WITH_KEYBOARD"]) + @Test + override fun snapResizeAppWindowWithKeyboardShortcuts() = + super.snapResizeAppWindowWithKeyboardShortcuts() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(SNAP_RESIZE_LEFT_WITH_KEYBOARD) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithKeyboard.kt new file mode 100644 index 000000000000..b9b918802204 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithKeyboard.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_RIGHT_WITH_KEYBOARD +import com.android.wm.shell.scenarios.SnapResizeAppWindowWithKeyboardShortcuts +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Snap resize app window using keyboard shortcut META + ]. + * + * Assert that the app window fills the right half the display after being snap resized. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class SnapResizeAppWindowRightWithKeyboard : SnapResizeAppWindowWithKeyboardShortcuts( + toLeft = false +) { + @ExpectedScenarios(["SNAP_RESIZE_RIGHT_WITH_KEYBOARD"]) + @Test + override fun snapResizeAppWindowWithKeyboardShortcuts() = + super.snapResizeAppWindowWithKeyboardShortcuts() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(SNAP_RESIZE_RIGHT_WITH_KEYBOARD) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt index 971637b62604..835559cd0936 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt @@ -43,7 +43,10 @@ import org.junit.Test */ @Ignore("Test Base Class") abstract class MinimizeAppWindows -constructor(private val rotation: Rotation = Rotation.ROTATION_0) { +constructor( + private val rotation: Rotation = Rotation.ROTATION_0, + private val usingKeyboard: Boolean = false +) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() private val wmHelper = WindowManagerStateHelper(instrumentation) @@ -68,9 +71,9 @@ constructor(private val rotation: Rotation = Rotation.ROTATION_0) { @Test open fun minimizeAllAppWindows() { - testApp3.minimizeDesktopApp(wmHelper, device) - testApp2.minimizeDesktopApp(wmHelper, device) - testApp1.minimizeDesktopApp(wmHelper, device) + testApp3.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard) + testApp2.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard) + testApp1.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard) } @After diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt new file mode 100644 index 000000000000..068064402ee5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.KeyEventHelper +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Test Base Class") +abstract class SnapResizeAppWindowWithKeyboardShortcuts( + private val toLeft: Boolean = true, + isResizable: Boolean = true +) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val keyEventHelper = KeyEventHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = if (isResizable) { + DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + } else { + DesktopModeAppHelper(NonResizeableAppHelper(instrumentation)) + } + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && + Flags.enableTaskResizingKeyboardShortcuts() && tapl.isTablet) + testApp.enterDesktopMode(wmHelper, device) + } + + @Test + open fun snapResizeAppWindowWithKeyboardShortcuts() { + testApp.snapResizeWithKeyboard( + wmHelper, + instrumentation.context, + keyEventHelper, + toLeft, + ) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt index d52fd4fdf6c7..7157a7f0b38f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt @@ -20,7 +20,6 @@ import android.content.ComponentName import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.R -import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -28,14 +27,13 @@ import org.junit.Test import org.junit.runner.RunWith /** - * Tests for {@link AppCompatUtils}. + * Tests for [@link AppCompatUtils]. * - * Build/Install/Run: - * atest WMShellUnitTests:AppCompatUtilsTest + * Build/Install/Run: atest WMShellUnitTests:AppCompatUtilsTest */ @RunWith(AndroidTestingRunner::class) @SmallTest -class AppCompatUtilsTest : ShellTestCase() { +class AppCompatUtilsTest : CompatUIShellTestCase() { @Test fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() { assertTrue(isTopActivityExemptFromDesktopWindowing(mContext, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 67573dabf2ec..ecf766db0be3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -39,9 +39,6 @@ import android.content.res.Configuration; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.view.InsetsSource; import android.view.InsetsState; @@ -52,7 +49,6 @@ import androidx.test.filters.SmallTest; import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; @@ -70,11 +66,8 @@ import com.android.wm.shell.transition.Transitions; import dagger.Lazy; -import java.util.Optional; - import org.junit.Assert; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -82,25 +75,20 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + /** * Tests for {@link CompatUIController}. * * Build/Install/Run: - * atest WMShellUnitTests:CompatUIControllerTest + * atest WMShellUnitTests:CompatUIControllerTest */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class CompatUIControllerTest extends ShellTestCase { +public class CompatUIControllerTest extends CompatUIShellTestCase { private static final int DISPLAY_ID = 0; private static final int TASK_ID = 12; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - private CompatUIController mController; private ShellInit mShellInit; @Mock diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index e5d1919decf4..2117b062bf57 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -26,8 +26,6 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.graphics.Rect; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.LayoutInflater; @@ -40,7 +38,6 @@ import androidx.test.filters.SmallTest; import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; @@ -49,7 +46,6 @@ import com.android.wm.shell.compatui.api.CompatUIEvent; import junit.framework.Assert; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -66,14 +62,10 @@ import java.util.function.Consumer; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class CompatUILayoutTest extends ShellTestCase { +public class CompatUILayoutTest extends CompatUIShellTestCase { private static final int TASK_ID = 1; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Mock private SyncTransactionQueue mSyncTransactionQueue; @Mock private Consumer<CompatUIEvent> mCallback; @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java new file mode 100644 index 000000000000..5a49d01f2991 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui; + +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.wm.shell.ShellTestCase; + +import org.junit.Rule; + +/** + * Base class for CompatUI tests. + */ +public class CompatUIShellTestCase extends ShellTestCase { + + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java index 8fd7c0ec3099..0b37648faeec 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java @@ -27,8 +27,6 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; -import com.android.wm.shell.ShellTestCase; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,7 +42,7 @@ import java.util.function.IntSupplier; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class CompatUIStatusManagerTest extends ShellTestCase { +public class CompatUIStatusManagerTest extends CompatUIShellTestCase { private FakeCompatUIStatusManagerTest mTestState; private CompatUIStatusManager mStatusManager; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index 1c0175603df9..61b6d803c8be 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -16,7 +16,6 @@ package com.android.wm.shell.compatui; -import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; @@ -40,9 +39,6 @@ import android.app.TaskInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.DisplayInfo; @@ -56,7 +52,6 @@ import androidx.test.filters.SmallTest; import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; @@ -65,7 +60,6 @@ import com.android.wm.shell.compatui.api.CompatUIEvent; import junit.framework.Assert; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -82,13 +76,7 @@ import java.util.function.Consumer; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class CompatUIWindowManagerTest extends ShellTestCase { - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); - - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); +public class CompatUIWindowManagerTest extends CompatUIShellTestCase { private static final int TASK_ID = 1; private static final int TASK_WIDTH = 2000; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java index e8191db13084..e786fef1855c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java @@ -25,8 +25,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.View; @@ -34,10 +32,8 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.wm.shell.R; -import com.android.wm.shell.ShellTestCase; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -51,7 +47,7 @@ import org.mockito.MockitoAnnotations; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class LetterboxEduDialogLayoutTest extends ShellTestCase { +public class LetterboxEduDialogLayoutTest extends CompatUIShellTestCase { @Mock private Runnable mDismissCallback; @@ -60,10 +56,6 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase { private View mDismissButton; private View mDialogContainer; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java index 4c97c76ae122..09fc082a63e3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java @@ -46,9 +46,6 @@ import android.graphics.Rect; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.DisplayCutout; @@ -65,7 +62,6 @@ import androidx.test.filters.SmallTest; import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; @@ -75,7 +71,6 @@ import com.android.wm.shell.transition.Transitions; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -95,7 +90,7 @@ import java.util.function.Consumer; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class LetterboxEduWindowManagerTest extends ShellTestCase { +public class LetterboxEduWindowManagerTest extends CompatUIShellTestCase { private static final int USER_ID_1 = 1; private static final int USER_ID_2 = 2; @@ -128,18 +123,11 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback; @Mock private DockStateReader mDockStateReader; - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - private CompatUIConfiguration mCompatUIConfiguration; private TestShellExecutor mExecutor; private FakeCompatUIStatusManagerTest mCompatUIStatus; private CompatUIStatusManager mCompatUIStatusManager; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java index 0da14d673732..02c099b3cfb2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java @@ -26,8 +26,6 @@ import static org.mockito.Mockito.verify; import android.app.TaskInfo; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; @@ -36,10 +34,8 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.wm.shell.R; -import com.android.wm.shell.ShellTestCase; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -54,7 +50,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class ReachabilityEduLayoutTest extends ShellTestCase { +public class ReachabilityEduLayoutTest extends CompatUIShellTestCase { private ReachabilityEduLayout mLayout; private View mMoveUpButton; @@ -68,10 +64,6 @@ public class ReachabilityEduLayoutTest extends ShellTestCase { @Mock private TaskInfo mTaskInfo; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java index eafb41470cda..fa04e070250e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java @@ -25,14 +25,11 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.content.res.Configuration; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; @@ -40,7 +37,6 @@ import com.android.wm.shell.common.SyncTransactionQueue; import junit.framework.Assert; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -56,7 +52,7 @@ import java.util.function.BiConsumer; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class ReachabilityEduWindowManagerTest extends ShellTestCase { +public class ReachabilityEduWindowManagerTest extends CompatUIShellTestCase { @Mock private SyncTransactionQueue mSyncTransactionQueue; @Mock @@ -71,10 +67,6 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { private TaskInfo mTaskInfo; private ReachabilityEduWindowManager mWindowManager; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java index 6b0c5dd2e1c7..2cded9d9776c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java @@ -26,8 +26,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.View; @@ -36,10 +34,8 @@ import android.widget.CheckBox; import androidx.test.filters.SmallTest; import com.android.wm.shell.R; -import com.android.wm.shell.ShellTestCase; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -55,7 +51,7 @@ import java.util.function.Consumer; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class RestartDialogLayoutTest extends ShellTestCase { +public class RestartDialogLayoutTest extends CompatUIShellTestCase { @Mock private Runnable mDismissCallback; @Mock private Consumer<Boolean> mRestartCallback; @@ -66,10 +62,6 @@ public class RestartDialogLayoutTest extends ShellTestCase { private View mDialogContainer; private CheckBox mDontRepeatCheckBox; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java index cfeef90cb4b6..ebd0f412a0a1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java @@ -22,15 +22,12 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.content.res.Configuration; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.util.Pair; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.transition.Transitions; @@ -38,7 +35,6 @@ import com.android.wm.shell.transition.Transitions; import junit.framework.Assert; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -54,7 +50,7 @@ import java.util.function.Consumer; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class RestartDialogWindowManagerTest extends ShellTestCase { +public class RestartDialogWindowManagerTest extends CompatUIShellTestCase { @Mock private SyncTransactionQueue mSyncTransactionQueue; @@ -66,10 +62,6 @@ public class RestartDialogWindowManagerTest extends ShellTestCase { private RestartDialogWindowManager mWindowManager; private TaskInfo mTaskInfo; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java index e8e68bdbd940..c6532e13f3cc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java @@ -27,8 +27,6 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.content.ComponentName; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.LayoutInflater; @@ -40,7 +38,6 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; @@ -48,7 +45,6 @@ import com.android.wm.shell.common.SyncTransactionQueue; import junit.framework.Assert; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -66,7 +62,7 @@ import java.util.function.BiConsumer; */ @RunWith(AndroidTestingRunner.class) @SmallTest -public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { +public class UserAspectRatioSettingsLayoutTest extends CompatUIShellTestCase { private static final int TASK_ID = 1; @@ -88,10 +84,6 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { private UserAspectRatioSettingsLayout mLayout; private TaskInfo mTaskInfo; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java index 9f86d49b52c4..096e900199ba 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -41,8 +41,6 @@ import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.Pair; @@ -56,7 +54,6 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; @@ -65,7 +62,6 @@ import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import junit.framework.Assert; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -87,7 +83,7 @@ import java.util.function.Supplier; @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest -public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { +public class UserAspectRatioSettingsWindowManagerTest extends CompatUIShellTestCase { private static final int TASK_ID = 1; @@ -112,10 +108,6 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { private TestShellExecutor mExecutor; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Before public void setUp() { MockitoAnnotations.initMocks(this); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index abd707817621..c0ff2f0652b3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -45,6 +45,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_M import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test @@ -77,6 +78,12 @@ class DesktopModeEventLoggerTest : ShellTestCase() { doReturn(DISPLAY_HEIGHT).whenever(displayLayout).height() } + @After + fun tearDown() { + clearInvocations(staticMockMarker(FrameworkStatsLog::class.java)) + clearInvocations(staticMockMarker(EventLogTags::class.java)) + } + @Test fun logSessionEnter_logsEnterReasonWithNewSessionId() { desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index e777ec7b55f6..5629127b8c54 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -56,6 +56,11 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +/** + * Tests for [@link DesktopRepository]. + * + * Build/Install/Run: atest WMShellUnitTests:DesktopRepositoryTest + */ @SmallTest @RunWith(AndroidTestingRunner::class) @ExperimentalCoroutinesApi @@ -978,6 +983,22 @@ class DesktopRepositoryTest : ShellTestCase() { } @Test + fun setTaskIdAsTopTransparentFullscreenTaskId_savesTaskId() { + repo.setTopTransparentFullscreenTaskId(displayId = DEFAULT_DISPLAY, taskId = 1) + + assertThat(repo.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)).isEqualTo(1) + } + + @Test + fun clearTaskIdAsTopTransparentFullscreenTaskId_clearsTaskId() { + repo.setTopTransparentFullscreenTaskId(displayId = DEFAULT_DISPLAY, taskId = 1) + + repo.clearTopTransparentFullscreenTaskId(DEFAULT_DISPLAY) + + assertThat(repo.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)).isNull() + } + + @Test fun setTaskInFullImmersiveState_savedAsInImmersiveState() { assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 0eb88e368054..4f37180baa37 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -83,6 +83,7 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP +import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT import com.android.window.flags.Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY import com.android.wm.shell.MockToken import com.android.wm.shell.R @@ -297,6 +298,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } + whenever(displayLayout.densityDpi()).thenReturn(160) whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }) .thenReturn(Desktop.getDefaultInstance()) doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) } @@ -543,6 +545,18 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + ) + fun isDesktopModeShowing_topTransparentFullscreenTask_returnsTrue() { + val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) + + assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue() + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() { val homeTask = setUpHomeTask(SECOND_DISPLAY) @@ -1746,6 +1760,154 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) + fun moveToNextDisplay_sizeInDpPreserved() { + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + // Two displays have different density + whenever(displayLayout.densityDpi()).thenReturn(320) + whenever(displayLayout.width()).thenReturn(2400) + whenever(displayLayout.height()).thenReturn(1600) + val secondaryLayout = mock(DisplayLayout::class.java) + whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout) + whenever(secondaryLayout.densityDpi()).thenReturn(160) + whenever(secondaryLayout.width()).thenReturn(1280) + whenever(secondaryLayout.height()).thenReturn(720) + + // Place a task with a size of 640x480 at a position where the ratio of the left margin to + // the right margin is 1:3 and the ratio of top margin to the bottom margin is 1:2. + val task = + setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(440, 374, 1080, 854)) + + controller.moveToNextDisplay(task.taskId) + + with(getLatestWct(type = TRANSIT_CHANGE)) { + val taskChange = changes[task.token.asBinder()] + assertThat(taskChange).isNotNull() + // To preserve DP size, pixel size is changed to 320x240. The ratio of the left margin + // to the right margin and the ratio of the top margin to bottom margin are also + // preserved. + assertThat(taskChange!!.configuration.windowConfiguration.bounds) + .isEqualTo(Rect(240, 160, 560, 400)) + } + } + + @Test + @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) + fun moveToNextDisplay_shiftWithinDestinationDisplayBounds() { + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + // Two displays have different density + whenever(displayLayout.densityDpi()).thenReturn(320) + whenever(displayLayout.width()).thenReturn(2400) + whenever(displayLayout.height()).thenReturn(1600) + val secondaryLayout = mock(DisplayLayout::class.java) + whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout) + whenever(secondaryLayout.densityDpi()).thenReturn(160) + whenever(secondaryLayout.width()).thenReturn(1280) + whenever(secondaryLayout.height()).thenReturn(720) + + // Place a task with a size of 640x480 at a position where the bottom-right corner of the + // window is outside the source display bounds. The destination display still has enough + // space to place the window within its bounds. + val task = + setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(2000, 1200, 2640, 1680)) + + controller.moveToNextDisplay(task.taskId) + + with(getLatestWct(type = TRANSIT_CHANGE)) { + val taskChange = changes[task.token.asBinder()] + assertThat(taskChange).isNotNull() + assertThat(taskChange!!.configuration.windowConfiguration.bounds) + .isEqualTo(Rect(960, 480, 1280, 720)) + } + } + + @Test + @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) + fun moveToNextDisplay_maximizedTask() { + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + // Two displays have different density + whenever(displayLayout.densityDpi()).thenReturn(320) + whenever(displayLayout.width()).thenReturn(1280) + whenever(displayLayout.height()).thenReturn(960) + val secondaryLayout = mock(DisplayLayout::class.java) + whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout) + whenever(secondaryLayout.densityDpi()).thenReturn(160) + whenever(secondaryLayout.width()).thenReturn(1280) + whenever(secondaryLayout.height()).thenReturn(720) + + // Place a task with a size equals to display size. + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(0, 0, 1280, 960)) + + controller.moveToNextDisplay(task.taskId) + + with(getLatestWct(type = TRANSIT_CHANGE)) { + val taskChange = changes[task.token.asBinder()] + assertThat(taskChange).isNotNull() + // DP size is preserved. The window is centered in the destination display. + assertThat(taskChange!!.configuration.windowConfiguration.bounds) + .isEqualTo(Rect(320, 120, 960, 600)) + } + } + + @Test + @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) + fun moveToNextDisplay_defaultBoundsWhenDestinationTooSmall() { + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + // Two displays have different density + whenever(displayLayout.densityDpi()).thenReturn(320) + whenever(displayLayout.width()).thenReturn(2400) + whenever(displayLayout.height()).thenReturn(1600) + val secondaryLayout = mock(DisplayLayout::class.java) + whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout) + whenever(secondaryLayout.densityDpi()).thenReturn(160) + whenever(secondaryLayout.width()).thenReturn(640) + whenever(secondaryLayout.height()).thenReturn(480) + whenever(secondaryLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(0, 0, 640, 480) + } + + // A task with a size of 1800x1200 is being placed. To preserve DP size, + // 900x600 pixels are needed, which does not fit in the destination display. + val task = + setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(300, 200, 2100, 1400)) + + controller.moveToNextDisplay(task.taskId) + + with(getLatestWct(type = TRANSIT_CHANGE)) { + val taskChange = changes[task.token.asBinder()] + assertThat(taskChange).isNotNull() + assertThat(taskChange!!.configuration.windowConfiguration.bounds.left).isAtLeast(0) + assertThat(taskChange.configuration.windowConfiguration.bounds.top).isAtLeast(0) + assertThat(taskChange.configuration.windowConfiguration.bounds.right).isAtMost(640) + assertThat(taskChange.configuration.windowConfiguration.bounds.bottom).isAtMost(480) + } + } + + @Test fun getTaskWindowingMode() { val fullscreenTask = setUpFullscreenTask() val freeformTask = setUpFreeformTask() @@ -2473,6 +2635,63 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + ) + fun handleRequest_topActivityTransparentWithDisplay_savedToDesktopRepository() { + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + markTaskVisible(freeformTask) + + val transparentTask = + setUpFreeformTask(displayId = DEFAULT_DISPLAY).apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = false + numActivities = 1 + } + + controller.handleRequest(Binder(), createTransition(transparentTask)) + assertThat(taskRepository.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)) + .isEqualTo(transparentTask.taskId) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + ) + fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_notSavedToDesktopRepository() { + val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + + controller.handleRequest(Binder(), createTransition(task)) + assertThat(taskRepository.getTopTransparentFullscreenTaskId(DEFAULT_DISPLAY)).isNull() + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + ) + fun handleRequest_onlyTopTransparentFullscreenTask_returnSwitchToFreeformWCT() { + val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) + + val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_returnNull() { + val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + + assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() { val freeformTask = setUpFreeformTask() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index c9623bcd5c16..c66d203fd89a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -24,6 +24,7 @@ import android.content.Context import android.content.Intent import android.os.IBinder import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE @@ -63,8 +64,15 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +/** + * Tests for [@link DesktopTasksTransitionObserver]. + * + * Build/Install/Run: atest WMShellUnitTests:DesktopTasksTransitionObserverTest + */ class DesktopTasksTransitionObserverTest { + @JvmField @Rule val setFlagsRule = SetFlagsRule() + @JvmField @Rule val extendedMockitoRule = @@ -245,6 +253,48 @@ class DesktopTasksTransitionObserverTest { wct.assertRemoveAt(index = 0, wallpaperToken) } + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + ) + fun topTransparentTaskClosed_clearTaskIdFromRepository() { + val mockTransition = Mockito.mock(IBinder::class.java) + val topTransparentTask = createTaskInfo(1) + whenever(taskRepository.getTopTransparentFullscreenTaskId(any())) + .thenReturn(topTransparentTask.taskId) + + transitionObserver.onTransitionReady( + transition = mockTransition, + info = createCloseTransition(topTransparentTask), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + ) + fun topTransparentTaskSentToBack_clearTaskIdFromRepository() { + val mockTransition = Mockito.mock(IBinder::class.java) + val topTransparentTask = createTaskInfo(1) + whenever(taskRepository.getTopTransparentFullscreenTaskId(any())) + .thenReturn(topTransparentTask.taskId) + + transitionObserver.onTransitionReady( + transition = mockTransition, + info = createToBackTransition(topTransparentTask), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId) + } + private fun createBackNavigationTransition( task: RunningTaskInfo?, type: Int = TRANSIT_TO_BACK, @@ -301,6 +351,19 @@ class DesktopTasksTransitionObserverTest { } } + private fun createToBackTransition(task: RunningTaskInfo?): TransitionInfo { + return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply { + addChange( + Change(mock(), mock()).apply { + mode = TRANSIT_TO_BACK + parent = null + taskInfo = task + flags = flags + } + ) + } + } + private fun getLatestWct( @WindowManager.TransitionType type: Int = TRANSIT_OPEN, handlerClass: Class<out Transitions.TransitionHandler>? = null, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt index 3d59342f62d8..8ccca07142aa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt @@ -59,6 +59,13 @@ class DragSessionTest : ShellTestCase() { } @Test + fun testNullClipData() { + // Start a new drag session with null data + val session = DragSession(activityTaskManager, displayLayout, null, 0) + assertThat(session.hideDragSourceTaskId).isEqualTo(-1) + } + + @Test fun testGetRunningTask() { // Set up running tasks val runningTasks = listOf( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java index 3fe8c109807a..a8aa25700c7e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java @@ -120,15 +120,22 @@ public class PipSchedulerTest { @Test public void scheduleExitPipViaExpand_nullTaskToken_noop() { setNullPipTaskToken(); + when(mMockPipTransitionState.isInPip()).thenReturn(true); mPipScheduler.scheduleExitPipViaExpand(); - verify(mMockMainExecutor, never()).execute(any()); + verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture()); + assertNotNull(mRunnableArgumentCaptor.getValue()); + mRunnableArgumentCaptor.getValue().run(); + + verify(mMockPipTransitionController, never()) + .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull()); } @Test public void scheduleExitPipViaExpand_exitTransitionCalled() { setMockPipTaskToken(); + when(mMockPipTransitionState.isInPip()).thenReturn(true); mPipScheduler.scheduleExitPipViaExpand(); @@ -142,20 +149,13 @@ public class PipSchedulerTest { @Test public void removePipAfterAnimation() { - //TODO: Update once this is changed to run animation as part of transition setMockPipTaskToken(); + when(mMockPipTransitionState.isInPip()).thenReturn(true); - mPipScheduler.removePipAfterAnimation(); - verify(mMockAlphaAnimator, times(1)) - .setAnimationEndCallback(mRunnableArgumentCaptor.capture()); - assertNotNull(mRunnableArgumentCaptor.getValue()); - verify(mMockAlphaAnimator, times(1)).start(); - - mRunnableArgumentCaptor.getValue().run(); + mPipScheduler.scheduleRemovePip(); verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture()); assertNotNull(mRunnableArgumentCaptor.getValue()); - mRunnableArgumentCaptor.getValue().run(); verify(mMockPipTransitionController, times(1)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index 232ae0750c3a..ada7b4aff37d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -36,6 +36,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitState; +import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.transition.Transitions; @@ -81,11 +82,13 @@ public class SplitTestUtils { ShellExecutor mainExecutor, Handler mainHandler, ShellExecutor bgExecutor, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, - Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) { + Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState, + Optional<DesktopTasksController> desktopTasksController) { super(context, displayId, syncQueue, taskOrganizer, mainStage, sideStage, displayController, imeController, insetsController, splitLayout, transitions, transactionPool, mainExecutor, mainHandler, bgExecutor, - recentTasks, launchAdjacentController, windowDecorViewModel, splitState); + recentTasks, launchAdjacentController, windowDecorViewModel, splitState, + desktopTasksController); // Prepare root task for testing. mRootTask = new TestRunningTaskInfoBuilder().build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 0d612c17c462..ffa8b6089660 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -149,7 +149,7 @@ public class SplitTransitionTests extends ShellTestCase { mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mMainExecutor, mMainHandler, mBgExecutor, Optional.empty(), - mLaunchAdjacentController, Optional.empty(), mSplitState); + mLaunchAdjacentController, Optional.empty(), mSplitState, Optional.empty()); mStageCoordinator.setMixedHandler(mMixedHandler); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index a6aeabd5bd19..9d1df864764f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -143,8 +143,9 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, - mMainExecutor, mMainHandler, mBgExecutor, Optional.empty(), - mLaunchAdjacentController, Optional.empty(), mSplitState)); + mMainExecutor, mMainHandler, mBgExecutor, Optional.empty(), + mLaunchAdjacentController, Optional.empty(), mSplitState, + Optional.empty())); mDividerLeash = new SurfaceControl.Builder().setName("fakeDivider").build(); when(mSplitLayout.getTopLeftBounds()).thenReturn(mBounds1); 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 ce482cdd9944..4b01d841b824 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 @@ -55,7 +55,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; import android.testing.TestableContext; -import android.view.InsetsState; import android.view.Surface; import android.view.WindowManager; import android.view.WindowMetrics; @@ -338,9 +337,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase { windowInfo.appToken = appToken; windowInfo.targetActivityInfo = info; windowInfo.taskInfo = taskInfo; - windowInfo.topOpaqueWindowInsetsState = new InsetsState(); windowInfo.mainWindowLayoutParams = new WindowManager.LayoutParams(); - windowInfo.topOpaqueWindowLayoutParams = new WindowManager.LayoutParams(); return windowInfo; } diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index e6182454ad8a..5955915c9fcd 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -1420,18 +1420,20 @@ void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo Mutex AssetManager::SharedZip::gLock; DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen; -AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen) - : mPath(path), mZipFile(NULL), mModWhen(modWhen), - mResourceTableAsset(NULL), mResourceTable(NULL) -{ - if (kIsDebug) { - ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str()); - } - ALOGV("+++ opening zip '%s'\n", mPath.c_str()); - mZipFile = ZipFileRO::open(mPath.c_str()); - if (mZipFile == NULL) { - ALOGD("failed to open Zip archive '%s'\n", mPath.c_str()); - } +AssetManager::SharedZip::SharedZip(const String8& path, ModDate modWhen) + : mPath(path), + mZipFile(NULL), + mModWhen(modWhen), + mResourceTableAsset(NULL), + mResourceTable(NULL) { + if (kIsDebug) { + ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str()); + } + ALOGV("+++ opening zip '%s'\n", mPath.c_str()); + mZipFile = ZipFileRO::open(mPath.c_str()); + if (mZipFile == NULL) { + ALOGD("failed to open Zip archive '%s'\n", mPath.c_str()); + } } AssetManager::SharedZip::SharedZip(int fd, const String8& path) @@ -1453,7 +1455,7 @@ sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path, bool createIfNotPresent) { AutoMutex _l(gLock); - time_t modWhen = getFileModDate(path.c_str()); + auto modWhen = getFileModDate(path.c_str()); sp<SharedZip> zip = gOpen.valueFor(path).promote(); if (zip != NULL && zip->mModWhen == modWhen) { return zip; @@ -1520,8 +1522,8 @@ ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res) bool AssetManager::SharedZip::isUpToDate() { - time_t modWhen = getFileModDate(mPath.c_str()); - return mModWhen == modWhen; + auto modWhen = getFileModDate(mPath.c_str()); + return mModWhen == modWhen; } void AssetManager::SharedZip::addOverlay(const asset_path& ap) diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h index ce0985b38986..376c881ea376 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -280,21 +280,21 @@ private: ~SharedZip(); private: - SharedZip(const String8& path, time_t modWhen); - SharedZip(int fd, const String8& path); - SharedZip(); // <-- not implemented + SharedZip(const String8& path, ModDate modWhen); + SharedZip(int fd, const String8& path); + SharedZip(); // <-- not implemented - String8 mPath; - ZipFileRO* mZipFile; - time_t mModWhen; + String8 mPath; + ZipFileRO* mZipFile; + ModDate mModWhen; - Asset* mResourceTableAsset; - ResTable* mResourceTable; + Asset* mResourceTableAsset; + ResTable* mResourceTable; - Vector<asset_path> mOverlays; + Vector<asset_path> mOverlays; - static Mutex gLock; - static DefaultKeyedVector<String8, wp<SharedZip> > gOpen; + static Mutex gLock; + static DefaultKeyedVector<String8, wp<SharedZip> > gOpen; }; /* diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index e213fbd22ab0..ac75eb3bb98c 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -25,8 +25,9 @@ #include "android-base/macros.h" #include "android-base/unique_fd.h" #include "androidfw/ConfigDescription.h" -#include "androidfw/StringPiece.h" #include "androidfw/ResourceTypes.h" +#include "androidfw/StringPiece.h" +#include "androidfw/misc.h" #include "utils/ByteOrder.h" namespace android { @@ -213,7 +214,7 @@ class LoadedIdmap { android::base::unique_fd idmap_fd_; std::string_view overlay_apk_path_; std::string_view target_apk_path_; - time_t idmap_last_mod_time_; + ModDate idmap_last_mod_time_; private: DISALLOW_COPY_AND_ASSIGN(LoadedIdmap); diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h index 077609d20d55..c9ba8a01a5e9 100644 --- a/libs/androidfw/include/androidfw/misc.h +++ b/libs/androidfw/include/androidfw/misc.h @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#pragma once -#include <sys/types.h> +#include <time.h> // // Handy utility functions and portability code. // -#ifndef _LIBS_ANDROID_FW_MISC_H -#define _LIBS_ANDROID_FW_MISC_H namespace android { @@ -41,15 +40,35 @@ typedef enum FileType { } FileType; /* get the file's type; follows symlinks */ FileType getFileType(const char* fileName); -/* get the file's modification date; returns -1 w/errno set on failure */ -time_t getFileModDate(const char* fileName); + +// MinGW doesn't support nanosecond resolution in stat() modification time, and given +// that it only matters on the device it's ok to keep it at a seconds level there. +#ifdef _WIN32 +using ModDate = time_t; +inline constexpr ModDate kInvalidModDate = ModDate(-1); +inline constexpr unsigned long long kModDateResolutionNs = 1ull * 1000 * 1000 * 1000; +inline time_t toTimeT(ModDate m) { + return m; +} +#else +using ModDate = timespec; +inline constexpr ModDate kInvalidModDate = {-1, -1}; +inline constexpr unsigned long long kModDateResolutionNs = 1; +inline time_t toTimeT(ModDate m) { + return m.tv_sec; +} +#endif + +/* get the file's modification date; returns kInvalidModDate w/errno set on failure */ +ModDate getFileModDate(const char* fileName); /* same, but also returns -1 if the file has already been deleted */ -time_t getFileModDate(int fd); +ModDate getFileModDate(int fd); // Check if |path| or |fd| resides on a readonly filesystem. bool isReadonlyFilesystem(const char* path); bool isReadonlyFilesystem(int fd); -}; // namespace android +} // namespace android -#endif // _LIBS_ANDROID_FW_MISC_H +// Whoever uses getFileModDate() will need this as well +bool operator==(const timespec& l, const timespec& r); diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp index 93dcaf549a90..32f3624a3aee 100644 --- a/libs/androidfw/misc.cpp +++ b/libs/androidfw/misc.cpp @@ -28,11 +28,13 @@ #include <sys/vfs.h> #endif // __linux__ -#include <cstring> -#include <cstdio> #include <errno.h> #include <sys/stat.h> +#include <cstdio> +#include <cstring> +#include <tuple> + namespace android { /* @@ -73,27 +75,34 @@ FileType getFileType(const char* fileName) } } -/* - * Get a file's modification date. - */ -time_t getFileModDate(const char* fileName) { - struct stat sb; - if (stat(fileName, &sb) < 0) { - return (time_t)-1; - } - return sb.st_mtime; +static ModDate getModDate(const struct stat& st) { +#ifdef _WIN32 + return st.st_mtime; +#elif defined(__APPLE__) + return st.st_mtimespec; +#else + return st.st_mtim; +#endif } -time_t getFileModDate(int fd) { - struct stat sb; - if (fstat(fd, &sb) < 0) { - return (time_t)-1; - } - if (sb.st_nlink <= 0) { - errno = ENOENT; - return (time_t)-1; - } - return sb.st_mtime; +ModDate getFileModDate(const char* fileName) { + struct stat sb; + if (stat(fileName, &sb) < 0) { + return kInvalidModDate; + } + return getModDate(sb); +} + +ModDate getFileModDate(int fd) { + struct stat sb; + if (fstat(fd, &sb) < 0) { + return kInvalidModDate; + } + if (sb.st_nlink <= 0) { + errno = ENOENT; + return kInvalidModDate; + } + return getModDate(sb); } #ifndef __linux__ @@ -124,4 +133,8 @@ bool isReadonlyFilesystem(int fd) { } #endif // __linux__ -}; // namespace android +} // namespace android + +bool operator==(const timespec& l, const timespec& r) { + return std::tie(l.tv_sec, l.tv_nsec) == std::tie(r.tv_sec, l.tv_nsec); +} diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index 60aa7d88925d..cb2e56f5f5e4 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -14,6 +14,9 @@ * limitations under the License. */ +#include <chrono> +#include <thread> + #include "android-base/file.h" #include "androidfw/ApkAssets.h" #include "androidfw/AssetManager2.h" @@ -27,6 +30,7 @@ #include "data/overlayable/R.h" #include "data/system/R.h" +using namespace std::chrono_literals; using ::testing::NotNull; namespace overlay = com::android::overlay; @@ -218,10 +222,13 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { unlink(temp_file.path); ASSERT_FALSE(apk_assets->IsUpToDate()); - sleep(2); + + const auto sleep_duration = + std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull)); + std::this_thread::sleep_for(sleep_duration); base::WriteStringToFile("hello", temp_file.path); - sleep(2); + std::this_thread::sleep_for(sleep_duration); ASSERT_FALSE(apk_assets->IsUpToDate()); } diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 064cac2a6fc6..7d01dfbb446f 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -54,6 +54,9 @@ constexpr bool resample_gainmap_regions() { constexpr bool query_global_priority() { return false; } +constexpr bool early_preload_gl_context() { + return false; +} } // namespace hwui_flags #endif @@ -291,5 +294,10 @@ bool Properties::resampleGainmapRegions() { return sResampleGainmapRegions; } +bool Properties::earlyPreloadGlContext() { + return base::GetBoolProperty(PROPERTY_EARLY_PRELOAD_GL_CONTEXT, + hwui_flags::early_preload_gl_context()); +} + } // namespace uirenderer } // namespace android diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index db930f3904de..280a75a28e65 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -236,6 +236,8 @@ enum DebugLevel { #define PROPERTY_SKIP_EGLMANAGER_TELEMETRY "debug.hwui.skip_eglmanager_telemetry" +#define PROPERTY_EARLY_PRELOAD_GL_CONTEXT "debug.hwui.early_preload_gl_context" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -381,6 +383,7 @@ public: static bool initializeGlAlways(); static bool resampleGainmapRegions(); + static bool earlyPreloadGlContext(); private: static StretchEffectBehavior stretchEffectBehavior; diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index e497ea1f3cb4..76ad2acccf89 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -166,4 +166,11 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "early_preload_gl_context" + namespace: "core_graphics" + description: "Initialize GL context and GraphicBufferAllocater init on renderThread preload. This improves app startup time for apps using GL." + bug: "383612849" }
\ No newline at end of file diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 92c6ad10d1c7..69fe40c755ea 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -25,6 +25,7 @@ #include <private/android/choreographer.h> #include <sys/resource.h> #include <ui/FatVector.h> +#include <ui/GraphicBufferAllocator.h> #include <utils/Condition.h> #include <utils/Log.h> #include <utils/Mutex.h> @@ -518,11 +519,18 @@ bool RenderThread::isCurrent() { void RenderThread::preload() { // EGL driver is always preloaded only if HWUI renders with GL. if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { - std::thread eglInitThread([]() { eglGetDisplay(EGL_DEFAULT_DISPLAY); }); - eglInitThread.detach(); + if (Properties::earlyPreloadGlContext()) { + queue().post([this]() { requireGlContext(); }); + } else { + std::thread eglInitThread([]() { eglGetDisplay(EGL_DEFAULT_DISPLAY); }); + eglInitThread.detach(); + } } else { requireVkContext(); } + if (Properties::earlyPreloadGlContext()) { + queue().post([]() { GraphicBufferAllocator::getInstance(); }); + } HardwareBitmapUploader::initialize(); } diff --git a/location/api/system-current.txt b/location/api/system-current.txt index ba4224137cd4..0c2f3adc2838 100644 --- a/location/api/system-current.txt +++ b/location/api/system-current.txt @@ -35,11 +35,11 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class BeidouSatelliteEphemeris implements android.os.Parcelable { method public int describeContents(); - method @IntRange(from=1, to=63) public int getPrn(); method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel getSatelliteClockModel(); method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime getSatelliteEphemerisTime(); method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth getSatelliteHealth(); method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method @IntRange(from=1, to=63) public int getSvid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.BeidouSatelliteEphemeris> CREATOR; } @@ -104,11 +104,11 @@ package android.location { public static final class BeidouSatelliteEphemeris.Builder { ctor public BeidouSatelliteEphemeris.Builder(); method @NonNull public android.location.BeidouSatelliteEphemeris build(); - method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setPrn(int); method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel); method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime); method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth); method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSvid(int); } public final class CorrelationVector implements android.os.Parcelable { @@ -195,10 +195,10 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GalileoSatelliteEphemeris implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel> getSatelliteClockModels(); - method @IntRange(from=1, to=36) public int getSatelliteCodeNumber(); method @NonNull public android.location.SatelliteEphemerisTime getSatelliteEphemerisTime(); method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth getSatelliteHealth(); method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method @IntRange(from=1, to=36) public int getSvid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GalileoSatelliteEphemeris> CREATOR; } @@ -207,10 +207,10 @@ package android.location { ctor public GalileoSatelliteEphemeris.Builder(); method @NonNull public android.location.GalileoSatelliteEphemeris build(); method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteClockModels(@NonNull java.util.List<android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel>); - method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteCodeNumber(@IntRange(from=1, to=36) int); method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.SatelliteEphemerisTime); method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.GalileoSatelliteEphemeris.GalileoSvHealth); method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSvid(@IntRange(from=1, to=36) int); } public static final class GalileoSatelliteEphemeris.GalileoSatelliteClockModel implements android.os.Parcelable { @@ -243,25 +243,31 @@ package android.location { public static final class GalileoSatelliteEphemeris.GalileoSvHealth implements android.os.Parcelable { method public int describeContents(); - method @IntRange(from=0, to=1) public int getDataValidityStatusE1b(); - method @IntRange(from=0, to=1) public int getDataValidityStatusE5a(); - method @IntRange(from=0, to=1) public int getDataValidityStatusE5b(); - method @IntRange(from=0, to=3) public int getSignalHealthStatusE1b(); - method @IntRange(from=0, to=3) public int getSignalHealthStatusE5a(); - method @IntRange(from=0, to=3) public int getSignalHealthStatusE5b(); + method public int getDataValidityStatusE1b(); + method public int getDataValidityStatusE5a(); + method public int getDataValidityStatusE5b(); + method public int getSignalHealthStatusE1b(); + method public int getSignalHealthStatusE5a(); + method public int getSignalHealthStatusE5b(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GalileoSatelliteEphemeris.GalileoSvHealth> CREATOR; + field public static final int DATA_STATUS_DATA_VALID = 0; // 0x0 + field public static final int DATA_STATUS_WORKING_WITHOUT_GUARANTEE = 1; // 0x1 + field public static final int HEALTH_STATUS_EXTENDED_OPERATION_MODE = 2; // 0x2 + field public static final int HEALTH_STATUS_IN_TEST = 3; // 0x3 + field public static final int HEALTH_STATUS_OK = 0; // 0x0 + field public static final int HEALTH_STATUS_OUT_OF_SERVICE = 1; // 0x1 } public static final class GalileoSatelliteEphemeris.GalileoSvHealth.Builder { ctor public GalileoSatelliteEphemeris.GalileoSvHealth.Builder(); method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth build(); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE1b(@IntRange(from=0, to=1) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5a(@IntRange(from=0, to=1) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5b(@IntRange(from=0, to=1) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE1b(@IntRange(from=0, to=3) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5a(@IntRange(from=0, to=3) int); - method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5b(@IntRange(from=0, to=3) int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE1b(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5a(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5b(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE1b(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5a(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5b(int); } @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GlonassAlmanac implements android.os.Parcelable { @@ -275,17 +281,19 @@ package android.location { public static final class GlonassAlmanac.GlonassSatelliteAlmanac implements android.os.Parcelable { method public int describeContents(); + method @IntRange(from=1, to=1461) public int getCalendarDayNumber(); method @FloatRange(from=-0.067F, to=0.067f) public double getDeltaI(); method @FloatRange(from=-3600.0F, to=3600.0f) public double getDeltaT(); method @FloatRange(from=-0.004F, to=0.004f) public double getDeltaTDot(); method @FloatRange(from=0.0f, to=0.03f) public double getEccentricity(); - method @IntRange(from=0, to=31) public int getFreqChannel(); + method @IntRange(from=0, to=31) public int getFrequencyChannelNumber(); + method public int getHealthState(); method @FloatRange(from=-1.0F, to=1.0f) public double getLambda(); method @FloatRange(from=-1.0F, to=1.0f) public double getOmega(); method @IntRange(from=1, to=25) public int getSlotNumber(); - method @IntRange(from=0, to=1) public int getSvHealth(); method @FloatRange(from=0.0f, to=44100.0f) public double getTLambda(); method @FloatRange(from=-0.0019F, to=0.0019f) public double getTau(); + method public boolean isGlonassM(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassAlmanac.GlonassSatelliteAlmanac> CREATOR; } @@ -293,15 +301,17 @@ package android.location { public static final class GlonassAlmanac.GlonassSatelliteAlmanac.Builder { ctor public GlonassAlmanac.GlonassSatelliteAlmanac.Builder(); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac build(); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setCalendarDayNumber(@IntRange(from=1, to=1461) int); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setDeltaI(@FloatRange(from=-0.067F, to=0.067f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setDeltaT(@FloatRange(from=-3600.0F, to=3600.0f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setDeltaTDot(@FloatRange(from=-0.004F, to=0.004f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setEccentricity(@FloatRange(from=0.0f, to=0.03f) double); - method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setFreqChannel(@IntRange(from=0, to=31) int); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setFrequencyChannelNumber(@IntRange(from=0, to=31) int); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setGlonassM(boolean); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setHealthState(int); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setLambda(@FloatRange(from=-1.0F, to=1.0f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setOmega(@FloatRange(from=-1.0F, to=1.0f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setSlotNumber(@IntRange(from=1, to=25) int); - method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setSvHealth(@IntRange(from=0, to=1) int); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setTLambda(@FloatRange(from=0.0f, to=44100.0f) double); method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setTau(@FloatRange(from=-0.0019F, to=0.0019f) double); } @@ -331,12 +341,17 @@ package android.location { method public int describeContents(); method @IntRange(from=0, to=31) public int getAgeInDays(); method @FloatRange(from=0.0f) public double getFrameTimeSeconds(); - method @IntRange(from=0, to=1) public int getHealthState(); + method public int getHealthState(); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel getSatelliteClockModel(); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel getSatelliteOrbitModel(); method @IntRange(from=1, to=25) public int getSlotNumber(); + method @IntRange(from=0, to=60) public int getUpdateIntervalMinutes(); + method public boolean isGlonassM(); + method public boolean isUpdateIntervalOdd(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassSatelliteEphemeris> CREATOR; + field public static final int HEALTH_STATUS_HEALTHY = 0; // 0x0 + field public static final int HEALTH_STATUS_UNHEALTHY = 1; // 0x1 } public static final class GlonassSatelliteEphemeris.Builder { @@ -344,18 +359,23 @@ package android.location { method @NonNull public android.location.GlonassSatelliteEphemeris build(); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setAgeInDays(@IntRange(from=0, to=31) int); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setFrameTimeSeconds(@FloatRange(from=0.0f) double); - method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setHealthState(@IntRange(from=0, to=1) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setGlonassM(boolean); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setHealthState(int); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel); method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setSlotNumber(@IntRange(from=1, to=25) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setUpdateIntervalMinutes(@IntRange(from=0, to=60) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setUpdateIntervalOdd(boolean); } public static final class GlonassSatelliteEphemeris.GlonassSatelliteClockModel implements android.os.Parcelable { method public int describeContents(); method @FloatRange(from=-0.002F, to=0.002f) public double getClockBias(); method @FloatRange(from=-9.32E-10F, to=9.32E-10f) public double getFrequencyBias(); - method @IntRange(from=0xfffffff9, to=6) public int getFrequencyNumber(); + method @IntRange(from=0xfffffff9, to=6) public int getFrequencyChannelNumber(); + method @FloatRange(from=-1.4E-8F, to=1.4E-8f) public double getGroupDelayDiffSeconds(); method @IntRange(from=0) public long getTimeOfClockSeconds(); + method public boolean isGroupDelayDiffSecondsAvailable(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel> CREATOR; } @@ -365,7 +385,9 @@ package android.location { method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel build(); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setClockBias(@FloatRange(from=-0.002F, to=0.002f) double); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setFrequencyBias(@FloatRange(from=-9.32E-10F, to=9.32E-10f) double); - method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setFrequencyNumber(@IntRange(from=0xfffffff9, to=6) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setFrequencyChannelNumber(@IntRange(from=0xfffffff9, to=6) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setGroupDelayDiffSeconds(@FloatRange(from=-1.4E-8F, to=1.4E-8f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setGroupDelayDiffSecondsAvailable(boolean); method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setTimeOfClockSeconds(@IntRange(from=0) long); } @@ -401,10 +423,11 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GnssAlmanac implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<android.location.GnssAlmanac.GnssSatelliteAlmanac> getGnssSatelliteAlmanacs(); - method @IntRange(from=0) public int getIod(); + method @IntRange(from=0) public int getIoda(); method @IntRange(from=0) public long getIssueDateMillis(); method @IntRange(from=0, to=604800) public int getToaSeconds(); method @IntRange(from=0) public int getWeekNumber(); + method public boolean isCompleteAlmanacProvided(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAlmanac> CREATOR; } @@ -412,8 +435,9 @@ package android.location { public static final class GnssAlmanac.Builder { ctor public GnssAlmanac.Builder(); method @NonNull public android.location.GnssAlmanac build(); + method @NonNull public android.location.GnssAlmanac.Builder setCompleteAlmanacProvided(boolean); method @NonNull public android.location.GnssAlmanac.Builder setGnssSatelliteAlmanacs(@NonNull java.util.List<android.location.GnssAlmanac.GnssSatelliteAlmanac>); - method @NonNull public android.location.GnssAlmanac.Builder setIod(@IntRange(from=0) int); + method @NonNull public android.location.GnssAlmanac.Builder setIoda(@IntRange(from=0) int); method @NonNull public android.location.GnssAlmanac.Builder setIssueDateMillis(@IntRange(from=0) long); method @NonNull public android.location.GnssAlmanac.Builder setToaSeconds(@IntRange(from=0, to=604800) int); method @NonNull public android.location.GnssAlmanac.Builder setWeekNumber(@IntRange(from=0) int); @@ -916,11 +940,11 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GpsSatelliteEphemeris implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsL2Params getGpsL2Params(); - method @IntRange(from=1, to=32) public int getPrn(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel getSatelliteClockModel(); method @NonNull public android.location.SatelliteEphemerisTime getSatelliteEphemerisTime(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth getSatelliteHealth(); method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method @IntRange(from=1, to=32) public int getSvid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GpsSatelliteEphemeris> CREATOR; } @@ -929,11 +953,11 @@ package android.location { ctor public GpsSatelliteEphemeris.Builder(); method @NonNull public android.location.GpsSatelliteEphemeris build(); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setGpsL2Params(@NonNull android.location.GpsSatelliteEphemeris.GpsL2Params); - method @NonNull public android.location.GpsSatelliteEphemeris.Builder setPrn(@IntRange(from=1, to=32) int); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.SatelliteEphemerisTime); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteHealth); method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSvid(@IntRange(from=1, to=32) int); } public static final class GpsSatelliteEphemeris.GpsL2Params implements android.os.Parcelable { @@ -1225,11 +1249,11 @@ package android.location { @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class QzssSatelliteEphemeris implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsL2Params getGpsL2Params(); - method @IntRange(from=183, to=206) public int getPrn(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel getSatelliteClockModel(); method @NonNull public android.location.SatelliteEphemerisTime getSatelliteEphemerisTime(); method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth getSatelliteHealth(); method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method @IntRange(from=183, to=206) public int getSvid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.QzssSatelliteEphemeris> CREATOR; } @@ -1238,11 +1262,11 @@ package android.location { ctor public QzssSatelliteEphemeris.Builder(); method @NonNull public android.location.QzssSatelliteEphemeris build(); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setGpsL2Params(@NonNull android.location.GpsSatelliteEphemeris.GpsL2Params); - method @NonNull public android.location.QzssSatelliteEphemeris.Builder setPrn(@IntRange(from=183, to=206) int); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.SatelliteEphemerisTime); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteHealth); method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSvid(@IntRange(from=183, to=206) int); } @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class RealTimeIntegrityModel implements android.os.Parcelable { diff --git a/location/java/android/location/BeidouSatelliteEphemeris.java b/location/java/android/location/BeidouSatelliteEphemeris.java index 6bd91e3318c3..3382c20964d9 100644 --- a/location/java/android/location/BeidouSatelliteEphemeris.java +++ b/location/java/android/location/BeidouSatelliteEphemeris.java @@ -35,8 +35,8 @@ import com.android.internal.util.Preconditions; @FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) @SystemApi public final class BeidouSatelliteEphemeris implements Parcelable { - /** The PRN number of the Beidou satellite. */ - private final int mPrn; + /** The PRN or satellite ID number for the Beidou satellite. */ + private final int mSvid; /** Satellite clock model. */ private final BeidouSatelliteClockModel mSatelliteClockModel; @@ -51,8 +51,8 @@ public final class BeidouSatelliteEphemeris implements Parcelable { private final BeidouSatelliteEphemerisTime mSatelliteEphemerisTime; private BeidouSatelliteEphemeris(Builder builder) { - // Allow PRN beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mPrn >= 1); + // Allow Svid beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvid >= 1); Preconditions.checkNotNull(builder.mSatelliteClockModel, "SatelliteClockModel cannot be null"); Preconditions.checkNotNull(builder.mSatelliteOrbitModel, @@ -61,17 +61,17 @@ public final class BeidouSatelliteEphemeris implements Parcelable { "SatelliteHealth cannot be null"); Preconditions.checkNotNull(builder.mSatelliteEphemerisTime, "SatelliteEphemerisTime cannot be null"); - mPrn = builder.mPrn; + mSvid = builder.mSvid; mSatelliteClockModel = builder.mSatelliteClockModel; mSatelliteOrbitModel = builder.mSatelliteOrbitModel; mSatelliteHealth = builder.mSatelliteHealth; mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; } - /** Returns the PRN of the satellite. */ + /** Returns the PRN or satellite ID number for the Beidou satellite. */ @IntRange(from = 1, to = 63) - public int getPrn() { - return mPrn; + public int getSvid() { + return mSvid; } /** Returns the satellite clock model. */ @@ -105,7 +105,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable { public BeidouSatelliteEphemeris createFromParcel(Parcel in) { final BeidouSatelliteEphemeris.Builder beidouSatelliteEphemeris = new Builder() - .setPrn(in.readInt()) + .setSvid(in.readInt()) .setSatelliteClockModel( in.readTypedObject(BeidouSatelliteClockModel.CREATOR)) .setSatelliteOrbitModel( @@ -131,7 +131,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mPrn); + parcel.writeInt(mSvid); parcel.writeTypedObject(mSatelliteClockModel, flags); parcel.writeTypedObject(mSatelliteOrbitModel, flags); parcel.writeTypedObject(mSatelliteHealth, flags); @@ -142,7 +142,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable { @NonNull public String toString() { StringBuilder builder = new StringBuilder("BeidouSatelliteEphemeris["); - builder.append("prn = ").append(mPrn); + builder.append("svid = ").append(mSvid); builder.append(", satelliteClockModel = ").append(mSatelliteClockModel); builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); builder.append(", satelliteHealth = ").append(mSatelliteHealth); @@ -153,16 +153,16 @@ public final class BeidouSatelliteEphemeris implements Parcelable { /** Builder for {@link BeidouSatelliteEphemeris} */ public static final class Builder { - private int mPrn; + private int mSvid; private BeidouSatelliteClockModel mSatelliteClockModel; private KeplerianOrbitModel mSatelliteOrbitModel; private BeidouSatelliteHealth mSatelliteHealth; private BeidouSatelliteEphemerisTime mSatelliteEphemerisTime; - /** Sets the PRN of the satellite. */ + /** Sets the PRN or satellite ID number for the Beidou satellite. */ @NonNull - public Builder setPrn(int prn) { - mPrn = prn; + public Builder setSvid(int svid) { + mSvid = svid; return this; } diff --git a/location/java/android/location/GalileoSatelliteEphemeris.java b/location/java/android/location/GalileoSatelliteEphemeris.java index 7dd371176267..08218f4bf83e 100644 --- a/location/java/android/location/GalileoSatelliteEphemeris.java +++ b/location/java/android/location/GalileoSatelliteEphemeris.java @@ -43,8 +43,8 @@ import java.util.List; @SystemApi public final class GalileoSatelliteEphemeris implements Parcelable { - /** Satellite code number. */ - private int mSatelliteCodeNumber; + /** PRN or satellite ID number for the Galileo satellite. */ + private int mSvid; /** Array of satellite clock model. */ @NonNull private final List<GalileoSatelliteClockModel> mSatelliteClockModels; @@ -59,8 +59,8 @@ public final class GalileoSatelliteEphemeris implements Parcelable { @NonNull private final SatelliteEphemerisTime mSatelliteEphemerisTime; private GalileoSatelliteEphemeris(Builder builder) { - // Allow satelliteCodeNumber beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mSatelliteCodeNumber >= 1); + // Allow svid beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvid >= 1); Preconditions.checkNotNull( builder.mSatelliteClockModels, "SatelliteClockModels cannot be null"); Preconditions.checkNotNull( @@ -68,7 +68,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { Preconditions.checkNotNull(builder.mSatelliteHealth, "SatelliteHealth cannot be null"); Preconditions.checkNotNull( builder.mSatelliteEphemerisTime, "SatelliteEphemerisTime cannot be null"); - mSatelliteCodeNumber = builder.mSatelliteCodeNumber; + mSvid = builder.mSvid; final List<GalileoSatelliteClockModel> satelliteClockModels = builder.mSatelliteClockModels; mSatelliteClockModels = Collections.unmodifiableList(new ArrayList<>(satelliteClockModels)); mSatelliteOrbitModel = builder.mSatelliteOrbitModel; @@ -76,10 +76,10 @@ public final class GalileoSatelliteEphemeris implements Parcelable { mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; } - /** Returns the satellite code number. */ + /** Returns the PRN or satellite ID number for the Galileo satellite. */ @IntRange(from = 1, to = 36) - public int getSatelliteCodeNumber() { - return mSatelliteCodeNumber; + public int getSvid() { + return mSvid; } /** Returns the list of satellite clock models. */ @@ -113,7 +113,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { public GalileoSatelliteEphemeris createFromParcel(Parcel in) { final GalileoSatelliteEphemeris.Builder galileoSatelliteEphemeris = new Builder(); - galileoSatelliteEphemeris.setSatelliteCodeNumber(in.readInt()); + galileoSatelliteEphemeris.setSvid(in.readInt()); List<GalileoSatelliteClockModel> satelliteClockModels = new ArrayList<>(); in.readTypedList(satelliteClockModels, GalileoSatelliteClockModel.CREATOR); galileoSatelliteEphemeris.setSatelliteClockModels(satelliteClockModels); @@ -139,7 +139,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mSatelliteCodeNumber); + parcel.writeInt(mSvid); parcel.writeTypedList(mSatelliteClockModels, flags); parcel.writeTypedObject(mSatelliteOrbitModel, flags); parcel.writeTypedObject(mSatelliteHealth, flags); @@ -150,7 +150,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { @NonNull public String toString() { StringBuilder builder = new StringBuilder("GalileoSatelliteEphemeris["); - builder.append("satelliteCodeNumber = ").append(mSatelliteCodeNumber); + builder.append("svid = ").append(mSvid); builder.append(", satelliteClockModels = ").append(mSatelliteClockModels); builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); builder.append(", satelliteHealth = ").append(mSatelliteHealth); @@ -161,17 +161,16 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Builder for {@link GalileoSatelliteEphemeris}. */ public static final class Builder { - private int mSatelliteCodeNumber; + private int mSvid; private List<GalileoSatelliteClockModel> mSatelliteClockModels; private KeplerianOrbitModel mSatelliteOrbitModel; private GalileoSvHealth mSatelliteHealth; private SatelliteEphemerisTime mSatelliteEphemerisTime; - /** Sets the satellite code number. */ + /** Sets the PRN or satellite ID number for the Galileo satellite. */ @NonNull - public Builder setSatelliteCodeNumber( - @IntRange(from = 1, to = 36) int satelliteCodeNumber) { - mSatelliteCodeNumber = satelliteCodeNumber; + public Builder setSvid(@IntRange(from = 1, to = 36) int svid) { + mSvid = svid; return this; } @@ -218,37 +217,107 @@ public final class GalileoSatelliteEphemeris implements Parcelable { * <p>This is defined in Galileo-OS-SIS-ICD 5.1.9.3. */ public static final class GalileoSvHealth implements Parcelable { + + /** + * Galileo data validity status. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({DATA_STATUS_DATA_VALID, DATA_STATUS_WORKING_WITHOUT_GUARANTEE}) + public @interface GalileoDataValidityStatus {} + + /** + * The following enumerations must be in sync with the values declared in + * GalileoHealthDataVaidityType in GalileoSatelliteEphemeris.aidl. + */ + + /** Data validity status is data valid. */ + public static final int DATA_STATUS_DATA_VALID = 0; + + /** Data validity status is working without guarantee. */ + public static final int DATA_STATUS_WORKING_WITHOUT_GUARANTEE = 1; + + /** + * Galileo signal health status. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + HEALTH_STATUS_OK, + HEALTH_STATUS_OUT_OF_SERVICE, + HEALTH_STATUS_EXTENDED_OPERATION_MODE, + HEALTH_STATUS_IN_TEST + }) + public @interface GalileoHealthStatus {} + + /** + * The following enumerations must be in sync with the values declared in + * GalileoHealthStatusType in GalileoSatelliteEphemeris.aidl. + */ + + /** Health status is ok. */ + public static final int HEALTH_STATUS_OK = 0; + + /** Health status is out of service. */ + public static final int HEALTH_STATUS_OUT_OF_SERVICE = 1; + + /** Health status is in extended operation mode. */ + public static final int HEALTH_STATUS_EXTENDED_OPERATION_MODE = 2; + + /** Health status is in test mode. */ + public static final int HEALTH_STATUS_IN_TEST = 3; + /** E1-B data validity status. */ - private int mDataValidityStatusE1b; + private @GalileoDataValidityStatus int mDataValidityStatusE1b; /** E1-B/C signal health status. */ - private int mSignalHealthStatusE1b; + private @GalileoHealthStatus int mSignalHealthStatusE1b; /** E5a data validity status. */ - private int mDataValidityStatusE5a; + private @GalileoDataValidityStatus int mDataValidityStatusE5a; /** E5a signal health status. */ - private int mSignalHealthStatusE5a; + private @GalileoHealthStatus int mSignalHealthStatusE5a; /** E5b data validity status. */ - private int mDataValidityStatusE5b; + private @GalileoDataValidityStatus int mDataValidityStatusE5b; /** E5b signal health status. */ - private int mSignalHealthStatusE5b; + private @GalileoHealthStatus int mSignalHealthStatusE5b; private GalileoSvHealth(Builder builder) { Preconditions.checkArgumentInRange( - builder.mDataValidityStatusE1b, 0, 1, "DataValidityStatusE1b"); + builder.mDataValidityStatusE1b, + DATA_STATUS_DATA_VALID, + DATA_STATUS_WORKING_WITHOUT_GUARANTEE, + "DataValidityStatusE1b"); Preconditions.checkArgumentInRange( - builder.mSignalHealthStatusE1b, 0, 3, "SignalHealthStatusE1b"); + builder.mSignalHealthStatusE1b, + HEALTH_STATUS_OK, + HEALTH_STATUS_IN_TEST, + "SignalHealthStatusE1b"); Preconditions.checkArgumentInRange( - builder.mDataValidityStatusE5a, 0, 1, "DataValidityStatusE5a"); + builder.mDataValidityStatusE5a, + DATA_STATUS_DATA_VALID, + DATA_STATUS_WORKING_WITHOUT_GUARANTEE, + "DataValidityStatusE5a"); Preconditions.checkArgumentInRange( - builder.mSignalHealthStatusE5a, 0, 3, "SignalHealthStatusE5a"); + builder.mSignalHealthStatusE5a, + HEALTH_STATUS_OK, + HEALTH_STATUS_IN_TEST, + "SignalHealthStatusE5a"); Preconditions.checkArgumentInRange( - builder.mDataValidityStatusE5b, 0, 1, "DataValidityStatusE5b"); + builder.mDataValidityStatusE5b, + DATA_STATUS_DATA_VALID, + DATA_STATUS_WORKING_WITHOUT_GUARANTEE, + "DataValidityStatusE5b"); Preconditions.checkArgumentInRange( - builder.mSignalHealthStatusE5b, 0, 3, "SignalHealthStatusE5b"); + builder.mSignalHealthStatusE5b, + HEALTH_STATUS_OK, + HEALTH_STATUS_IN_TEST, + "SignalHealthStatusE5b"); mDataValidityStatusE1b = builder.mDataValidityStatusE1b; mSignalHealthStatusE1b = builder.mSignalHealthStatusE1b; mDataValidityStatusE5a = builder.mDataValidityStatusE5a; @@ -258,37 +327,37 @@ public final class GalileoSatelliteEphemeris implements Parcelable { } /** Returns the E1-B data validity status. */ - @IntRange(from = 0, to = 1) + @GalileoDataValidityStatus public int getDataValidityStatusE1b() { return mDataValidityStatusE1b; } /** Returns the E1-B/C signal health status. */ - @IntRange(from = 0, to = 3) + @GalileoHealthStatus public int getSignalHealthStatusE1b() { return mSignalHealthStatusE1b; } /** Returns the E5a data validity status. */ - @IntRange(from = 0, to = 1) + @GalileoDataValidityStatus public int getDataValidityStatusE5a() { return mDataValidityStatusE5a; } /** Returns the E5a signal health status. */ - @IntRange(from = 0, to = 3) + @GalileoHealthStatus public int getSignalHealthStatusE5a() { return mSignalHealthStatusE5a; } /** Returns the E5b data validity status. */ - @IntRange(from = 0, to = 1) + @GalileoDataValidityStatus public int getDataValidityStatusE5b() { return mDataValidityStatusE5b; } /** Returns the E5b signal health status. */ - @IntRange(from = 0, to = 3) + @GalileoHealthStatus public int getSignalHealthStatusE5b() { return mSignalHealthStatusE5b; } @@ -355,7 +424,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E1-B data validity status. */ @NonNull public Builder setDataValidityStatusE1b( - @IntRange(from = 0, to = 1) int dataValidityStatusE1b) { + @GalileoDataValidityStatus int dataValidityStatusE1b) { mDataValidityStatusE1b = dataValidityStatusE1b; return this; } @@ -363,7 +432,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E1-B/C signal health status. */ @NonNull public Builder setSignalHealthStatusE1b( - @IntRange(from = 0, to = 3) int signalHealthStatusE1b) { + @GalileoHealthStatus int signalHealthStatusE1b) { mSignalHealthStatusE1b = signalHealthStatusE1b; return this; } @@ -371,7 +440,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E5a data validity status. */ @NonNull public Builder setDataValidityStatusE5a( - @IntRange(from = 0, to = 1) int dataValidityStatusE5a) { + @GalileoDataValidityStatus int dataValidityStatusE5a) { mDataValidityStatusE5a = dataValidityStatusE5a; return this; } @@ -379,7 +448,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E5a signal health status. */ @NonNull public Builder setSignalHealthStatusE5a( - @IntRange(from = 0, to = 3) int signalHealthStatusE5a) { + @GalileoHealthStatus int signalHealthStatusE5a) { mSignalHealthStatusE5a = signalHealthStatusE5a; return this; } @@ -387,7 +456,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E5b data validity status. */ @NonNull public Builder setDataValidityStatusE5b( - @IntRange(from = 0, to = 1) int dataValidityStatusE5b) { + @GalileoDataValidityStatus int dataValidityStatusE5b) { mDataValidityStatusE5b = dataValidityStatusE5b; return this; } @@ -395,7 +464,7 @@ public final class GalileoSatelliteEphemeris implements Parcelable { /** Sets the E5b signal health status. */ @NonNull public Builder setSignalHealthStatusE5b( - @IntRange(from = 0, to = 3) int signalHealthStatusE5b) { + @GalileoHealthStatus int signalHealthStatusE5b) { mSignalHealthStatusE5b = signalHealthStatusE5b; return this; } diff --git a/location/java/android/location/GlonassAlmanac.java b/location/java/android/location/GlonassAlmanac.java index 861dc5d257c4..37657435b98a 100644 --- a/location/java/android/location/GlonassAlmanac.java +++ b/location/java/android/location/GlonassAlmanac.java @@ -21,6 +21,7 @@ import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.location.GlonassSatelliteEphemeris.GlonassHealthStatus; import android.location.flags.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -121,11 +122,17 @@ public final class GlonassAlmanac implements Parcelable { /** Slot number. */ private final int mSlotNumber; - /** Satellite health information (0=healthy, 1=unhealthy). */ - private final int mSvHealth; + /** Satellite health status. */ + private final @GlonassHealthStatus int mHealthState; /** Frequency channel number. */ - private final int mFreqChannel; + private final int mFrequencyChannelNumber; + + /** Calendar day number within the four-year period beginning since the leap year. */ + private final int mCalendarDayNumber; + + /** Flag to indicates if the satellite is a GLONASS-M satellitee. */ + private final boolean mGlonassM; /** Coarse value of satellite time correction to GLONASS time in seconds. */ private final double mTau; @@ -148,15 +155,18 @@ public final class GlonassAlmanac implements Parcelable { /** Eccentricity. */ private final double mEccentricity; - /** Argument of perigee in radians. */ + /** Argument of perigee in semi-circles. */ private final double mOmega; private GlonassSatelliteAlmanac(Builder builder) { // Allow slotNumber beyond the range to support potential future extensibility. Preconditions.checkArgument(builder.mSlotNumber >= 1); - // Allow svHealth beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mSvHealth >= 0); - Preconditions.checkArgumentInRange(builder.mFreqChannel, 0, 31, "FreqChannel"); + // Allow healthState beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mHealthState >= 0); + Preconditions.checkArgumentInRange( + builder.mFrequencyChannelNumber, 0, 31, "FrequencyChannelNumber"); + Preconditions.checkArgumentInRange( + builder.mCalendarDayNumber, 1, 1461, "CalendarDayNumber"); Preconditions.checkArgumentInRange(builder.mTau, -1.9e-3f, 1.9e-3f, "Tau"); Preconditions.checkArgumentInRange(builder.mTLambda, 0.0f, 44100.0f, "TLambda"); Preconditions.checkArgumentInRange(builder.mLambda, -1.0f, 1.0f, "Lambda"); @@ -166,8 +176,10 @@ public final class GlonassAlmanac implements Parcelable { Preconditions.checkArgumentInRange(builder.mEccentricity, 0.0f, 0.03f, "Eccentricity"); Preconditions.checkArgumentInRange(builder.mOmega, -1.0f, 1.0f, "Omega"); mSlotNumber = builder.mSlotNumber; - mSvHealth = builder.mSvHealth; - mFreqChannel = builder.mFreqChannel; + mHealthState = builder.mHealthState; + mFrequencyChannelNumber = builder.mFrequencyChannelNumber; + mCalendarDayNumber = builder.mCalendarDayNumber; + mGlonassM = builder.mGlonassM; mTau = builder.mTau; mTLambda = builder.mTLambda; mLambda = builder.mLambda; @@ -184,16 +196,29 @@ public final class GlonassAlmanac implements Parcelable { return mSlotNumber; } - /** Returns the Satellite health information (0=healthy, 1=unhealthy). */ - @IntRange(from = 0, to = 1) - public int getSvHealth() { - return mSvHealth; + /** Returns the satellite health status. */ + public @GlonassHealthStatus int getHealthState() { + return mHealthState; } /** Returns the frequency channel number. */ @IntRange(from = 0, to = 31) - public int getFreqChannel() { - return mFreqChannel; + public int getFrequencyChannelNumber() { + return mFrequencyChannelNumber; + } + + /** + * Returns the calendar day number within the four-year period beginning since the leap + * year. + */ + @IntRange(from = 1, to = 1461) + public int getCalendarDayNumber() { + return mCalendarDayNumber; + } + + /** Returns true if the satellite is a GLONASS-M satellitee, false otherwise. */ + public boolean isGlonassM() { + return mGlonassM; } /** Returns the coarse value of satellite time correction to GLONASS time in seconds. */ @@ -241,7 +266,7 @@ public final class GlonassAlmanac implements Parcelable { return mEccentricity; } - /** Returns the argument of perigee in radians. */ + /** Returns the Argument of perigee in semi-circles. */ @FloatRange(from = -1.0f, to = 1.0f) public double getOmega() { return mOmega; @@ -255,8 +280,10 @@ public final class GlonassAlmanac implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mSlotNumber); - dest.writeInt(mSvHealth); - dest.writeInt(mFreqChannel); + dest.writeInt(mHealthState); + dest.writeInt(mFrequencyChannelNumber); + dest.writeInt(mCalendarDayNumber); + dest.writeBoolean(mGlonassM); dest.writeDouble(mTau); dest.writeDouble(mTLambda); dest.writeDouble(mLambda); @@ -273,8 +300,10 @@ public final class GlonassAlmanac implements Parcelable { public GlonassSatelliteAlmanac createFromParcel(@NonNull Parcel source) { return new GlonassSatelliteAlmanac.Builder() .setSlotNumber(source.readInt()) - .setSvHealth(source.readInt()) - .setFreqChannel(source.readInt()) + .setHealthState(source.readInt()) + .setFrequencyChannelNumber(source.readInt()) + .setCalendarDayNumber(source.readInt()) + .setGlonassM(source.readBoolean()) .setTau(source.readDouble()) .setTLambda(source.readDouble()) .setLambda(source.readDouble()) @@ -297,8 +326,10 @@ public final class GlonassAlmanac implements Parcelable { public String toString() { StringBuilder builder = new StringBuilder("GlonassSatelliteAlmanac["); builder.append("slotNumber = ").append(mSlotNumber); - builder.append(", svHealth = ").append(mSvHealth); - builder.append(", freqChannel = ").append(mFreqChannel); + builder.append(", healthState = ").append(mHealthState); + builder.append(", frequencyChannelNumber = ").append(mFrequencyChannelNumber); + builder.append(", calendarDayNumber = ").append(mCalendarDayNumber); + builder.append(", glonassM = ").append(mGlonassM); builder.append(", tau = ").append(mTau); builder.append(", tLambda = ").append(mTLambda); builder.append(", lambda = ").append(mLambda); @@ -314,8 +345,10 @@ public final class GlonassAlmanac implements Parcelable { /** Builder for {@link GlonassSatelliteAlmanac}. */ public static final class Builder { private int mSlotNumber; - private int mSvHealth; - private int mFreqChannel; + private int mHealthState; + private int mFrequencyChannelNumber; + private int mCalendarDayNumber; + private boolean mGlonassM; private double mTau; private double mTLambda; private double mLambda; @@ -332,17 +365,36 @@ public final class GlonassAlmanac implements Parcelable { return this; } - /** Sets the Satellite health information (0=healthy, 1=unhealthy). */ + /** Sets the satellite health status. */ @NonNull - public Builder setSvHealth(@IntRange(from = 0, to = 1) int svHealth) { - mSvHealth = svHealth; + public Builder setHealthState(@GlonassHealthStatus int healthState) { + mHealthState = healthState; return this; } /** Sets the frequency channel number. */ @NonNull - public Builder setFreqChannel(@IntRange(from = 0, to = 31) int freqChannel) { - mFreqChannel = freqChannel; + public Builder setFrequencyChannelNumber( + @IntRange(from = 0, to = 31) int frequencyChannelNumber) { + mFrequencyChannelNumber = frequencyChannelNumber; + return this; + } + + /** + * Sets the calendar day number within the four-year period beginning since the leap + * year. + */ + @NonNull + public Builder setCalendarDayNumber( + @IntRange(from = 1, to = 1461) int calendarDayNumber) { + mCalendarDayNumber = calendarDayNumber; + return this; + } + + /** Sets to true if the satellite is a GLONASS-M satellitee, false otherwise. */ + @NonNull + public Builder setGlonassM(boolean isGlonassM) { + this.mGlonassM = isGlonassM; return this; } @@ -401,7 +453,7 @@ public final class GlonassAlmanac implements Parcelable { return this; } - /** Sets the argument of perigee in radians. */ + /** Sets the Argument of perigee in semi-circles. */ @NonNull public Builder setOmega(@FloatRange(from = -1.0f, to = 1.0f) double omega) { mOmega = omega; diff --git a/location/java/android/location/GlonassSatelliteEphemeris.java b/location/java/android/location/GlonassSatelliteEphemeris.java index 77a6ebb50cfb..bee6047f71c8 100644 --- a/location/java/android/location/GlonassSatelliteEphemeris.java +++ b/location/java/android/location/GlonassSatelliteEphemeris.java @@ -18,6 +18,7 @@ package android.location; import android.annotation.FlaggedApi; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -27,6 +28,9 @@ import android.os.Parcelable; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A class contains ephemeris parameters specific to Glonass satellites. * @@ -38,11 +42,31 @@ import com.android.internal.util.Preconditions; @SystemApi public final class GlonassSatelliteEphemeris implements Parcelable { + /** + * Glonass signal health status. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({HEALTH_STATUS_HEALTHY, HEALTH_STATUS_UNHEALTHY}) + public @interface GlonassHealthStatus {} + + /** + * The following enumerations must be in sync with the values declared in + * GlonassSatelliteEphemeris.aidl + */ + + /** Health status is healthy. */ + public static final int HEALTH_STATUS_HEALTHY = 0; + + /** Health status is unhealthy. */ + public static final int HEALTH_STATUS_UNHEALTHY = 1; + /** L1/Satellite system (R), satellite number (slot number in sat. constellation). */ private final int mSlotNumber; - /** Health state (0=healthy, 1=unhealthy). */ - private final int mHealthState; + /** Health state. */ + private final @GlonassHealthStatus int mHealthState; /** Message frame time in seconds of the UTC week (tk+nd*86400). */ private final double mFrameTimeSeconds; @@ -50,6 +74,15 @@ public final class GlonassSatelliteEphemeris implements Parcelable { /** Age of current information in days (E). */ private final int mAgeInDays; + /** Update and validity interval in minutes (P1) */ + private final int mUpdateIntervalMinutes; + + /** Flag to indicate if the update interval is odd or even (P2). */ + private final boolean mUpdateIntervalOdd; + + /** Flag to indicates if the satellite is a Glonass-M satellitee (M). */ + private final boolean mGlonassM; + /** Satellite clock model. */ @NonNull private final GlonassSatelliteClockModel mSatelliteClockModel; @@ -63,6 +96,8 @@ public final class GlonassSatelliteEphemeris implements Parcelable { Preconditions.checkArgument(builder.mHealthState >= 0); Preconditions.checkArgument(builder.mFrameTimeSeconds >= 0.0f); Preconditions.checkArgumentInRange(builder.mAgeInDays, 0, 31, "AgeInDays"); + Preconditions.checkArgumentInRange( + builder.mUpdateIntervalMinutes, 0, 60, "UpdateIntervalMinutes"); Preconditions.checkNotNull( builder.mSatelliteClockModel, "SatelliteClockModel cannot be null"); Preconditions.checkNotNull( @@ -71,6 +106,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { mHealthState = builder.mHealthState; mFrameTimeSeconds = builder.mFrameTimeSeconds; mAgeInDays = builder.mAgeInDays; + mUpdateIntervalMinutes = builder.mUpdateIntervalMinutes; + mUpdateIntervalOdd = builder.mUpdateIntervalOdd; + mGlonassM = builder.mGlonassM; mSatelliteClockModel = builder.mSatelliteClockModel; mSatelliteOrbitModel = builder.mSatelliteOrbitModel; } @@ -83,9 +121,8 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return mSlotNumber; } - /** Returns the health state (0=healthy, 1=unhealthy). */ - @IntRange(from = 0, to = 1) - public int getHealthState() { + /** Returns the health state. */ + public @GlonassHealthStatus int getHealthState() { return mHealthState; } @@ -101,6 +138,22 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return mAgeInDays; } + /** Returns the update interval in minutes (P1). */ + @IntRange(from = 0, to = 60) + public int getUpdateIntervalMinutes() { + return mUpdateIntervalMinutes; + } + + /** Returns true if the update interval (P2) is odd, false otherwise (P2). */ + public boolean isUpdateIntervalOdd() { + return mUpdateIntervalOdd; + } + + /** Returns true if the satellite is a Glonass-M satellitee (M), false otherwise. */ + public boolean isGlonassM() { + return mGlonassM; + } + /** Returns the satellite clock model. */ @NonNull public GlonassSatelliteClockModel getSatelliteClockModel() { @@ -124,6 +177,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { dest.writeInt(mHealthState); dest.writeDouble(mFrameTimeSeconds); dest.writeInt(mAgeInDays); + dest.writeInt(mUpdateIntervalMinutes); + dest.writeBoolean(mUpdateIntervalOdd); + dest.writeBoolean(mGlonassM); dest.writeTypedObject(mSatelliteClockModel, flags); dest.writeTypedObject(mSatelliteOrbitModel, flags); } @@ -137,6 +193,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { .setHealthState(source.readInt()) .setFrameTimeSeconds(source.readDouble()) .setAgeInDays(source.readInt()) + .setUpdateIntervalMinutes(source.readInt()) + .setUpdateIntervalOdd(source.readBoolean()) + .setGlonassM(source.readBoolean()) .setSatelliteClockModel( source.readTypedObject(GlonassSatelliteClockModel.CREATOR)) .setSatelliteOrbitModel( @@ -158,6 +217,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { builder.append(", healthState = ").append(mHealthState); builder.append(", frameTimeSeconds = ").append(mFrameTimeSeconds); builder.append(", ageInDays = ").append(mAgeInDays); + builder.append(", updateIntervalMinutes = ").append(mUpdateIntervalMinutes); + builder.append(", isUpdateIntervalOdd = ").append(mUpdateIntervalOdd); + builder.append(", isGlonassM = ").append(mGlonassM); builder.append(", satelliteClockModel = ").append(mSatelliteClockModel); builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); builder.append("]"); @@ -170,6 +232,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { private int mHealthState; private double mFrameTimeSeconds; private int mAgeInDays; + private int mUpdateIntervalMinutes; + private boolean mUpdateIntervalOdd; + private boolean mGlonassM; private GlonassSatelliteClockModel mSatelliteClockModel; private GlonassSatelliteOrbitModel mSatelliteOrbitModel; @@ -182,9 +247,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return this; } - /** Sets the health state (0=healthy, 1=unhealthy). */ + /** Sets the health state. */ @NonNull - public Builder setHealthState(@IntRange(from = 0, to = 1) int healthState) { + public Builder setHealthState(@GlonassHealthStatus int healthState) { mHealthState = healthState; return this; } @@ -219,6 +284,28 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return this; } + /** Sets the update interval in minutes (P1). */ + @NonNull + public Builder setUpdateIntervalMinutes( + @IntRange(from = 0, to = 60) int updateIntervalMinutes) { + mUpdateIntervalMinutes = updateIntervalMinutes; + return this; + } + + /** Sets to true if the update interval (P2) is odd, false otherwise. */ + @NonNull + public Builder setUpdateIntervalOdd(boolean isUpdateIntervalOdd) { + mUpdateIntervalOdd = isUpdateIntervalOdd; + return this; + } + + /** Sets to true if the satellite is a Glonass-M satellitee (M), false otherwise. */ + @NonNull + public Builder setGlonassM(boolean isGlonassM) { + mGlonassM = isGlonassM; + return this; + } + /** Builds a {@link GlonassSatelliteEphemeris}. */ @NonNull public GlonassSatelliteEphemeris build() { @@ -246,19 +333,36 @@ public final class GlonassSatelliteEphemeris implements Parcelable { /** Frequency bias (+GammaN). */ private final double mFrequencyBias; - /** Frequency number. */ - private final int mFrequencyNumber; + /** Frequency channel number. */ + private final int mFrequencyChannelNumber; + + /* L1/L2 group delay difference in seconds (DeltaTau). */ + private final double mGroupDelayDiffSeconds; + + /** + * Whether the L1/L2 group delay difference in seconds (DeltaTau) is available. + * + * <p>It is set to true if available, otherwise false. + */ + private final boolean mGroupDelayDiffSecondsAvailable; private GlonassSatelliteClockModel(Builder builder) { Preconditions.checkArgument(builder.mTimeOfClockSeconds >= 0); Preconditions.checkArgumentInRange(builder.mClockBias, -0.002f, 0.002f, "ClockBias"); Preconditions.checkArgumentInRange( builder.mFrequencyBias, -9.32e-10f, 9.32e-10f, "FrequencyBias"); - Preconditions.checkArgumentInRange(builder.mFrequencyNumber, -7, 6, "FrequencyNumber"); + Preconditions.checkArgumentInRange( + builder.mFrequencyChannelNumber, -7, 6, "FrequencyChannelNumber"); + if (builder.mGroupDelayDiffSecondsAvailable) { + Preconditions.checkArgumentInRange( + builder.mGroupDelayDiffSeconds, -1.4e-8f, 1.4e-8f, "GroupDelayDiffSeconds"); + } mTimeOfClockSeconds = builder.mTimeOfClockSeconds; mClockBias = builder.mClockBias; mFrequencyBias = builder.mFrequencyBias; - mFrequencyNumber = builder.mFrequencyNumber; + mFrequencyChannelNumber = builder.mFrequencyChannelNumber; + mGroupDelayDiffSeconds = builder.mGroupDelayDiffSeconds; + mGroupDelayDiffSecondsAvailable = builder.mGroupDelayDiffSecondsAvailable; } /** Returns the time of clock in seconds (UTC). */ @@ -279,10 +383,21 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return mFrequencyBias; } - /** Returns the frequency number. */ + /** Returns the Frequency channel number. */ @IntRange(from = -7, to = 6) - public int getFrequencyNumber() { - return mFrequencyNumber; + public int getFrequencyChannelNumber() { + return mFrequencyChannelNumber; + } + + /** Returns the L1/L2 group delay difference in seconds (DeltaTau). */ + @FloatRange(from = -1.4e-8f, to = 1.4e-8f) + public double getGroupDelayDiffSeconds() { + return mGroupDelayDiffSeconds; + } + + /** Returns whether the L1/L2 group delay difference in seconds (DeltaTau) is available. */ + public boolean isGroupDelayDiffSecondsAvailable() { + return mGroupDelayDiffSecondsAvailable; } @Override @@ -295,7 +410,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { dest.writeLong(mTimeOfClockSeconds); dest.writeDouble(mClockBias); dest.writeDouble(mFrequencyBias); - dest.writeInt(mFrequencyNumber); + dest.writeInt(mFrequencyChannelNumber); + dest.writeDouble(mGroupDelayDiffSeconds); + dest.writeBoolean(mGroupDelayDiffSecondsAvailable); } public static final @NonNull Parcelable.Creator<GlonassSatelliteClockModel> CREATOR = @@ -306,7 +423,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { .setTimeOfClockSeconds(source.readLong()) .setClockBias(source.readDouble()) .setFrequencyBias(source.readDouble()) - .setFrequencyNumber(source.readInt()) + .setFrequencyChannelNumber(source.readInt()) + .setGroupDelayDiffSeconds(source.readDouble()) + .setGroupDelayDiffSecondsAvailable(source.readBoolean()) .build(); } @@ -323,7 +442,10 @@ public final class GlonassSatelliteEphemeris implements Parcelable { builder.append("timeOfClockSeconds = ").append(mTimeOfClockSeconds); builder.append(", clockBias = ").append(mClockBias); builder.append(", frequencyBias = ").append(mFrequencyBias); - builder.append(", frequencyNumber = ").append(mFrequencyNumber); + builder.append(", frequencyChannelNumber = ").append(mFrequencyChannelNumber); + if (mGroupDelayDiffSecondsAvailable) { + builder.append(", groupDelayDiffSeconds = ").append(mGroupDelayDiffSeconds); + } builder.append("]"); return builder.toString(); } @@ -333,7 +455,9 @@ public final class GlonassSatelliteEphemeris implements Parcelable { private long mTimeOfClockSeconds; private double mClockBias; private double mFrequencyBias; - private int mFrequencyNumber; + private double mGroupDelayDiffSeconds; + private int mFrequencyChannelNumber; + private boolean mGroupDelayDiffSecondsAvailable; /** Sets the time of clock in seconds (UTC). */ @NonNull @@ -357,10 +481,27 @@ public final class GlonassSatelliteEphemeris implements Parcelable { return this; } - /** Sets the frequency number. */ + /** Sets the Frequency channel number. */ + @NonNull + public Builder setFrequencyChannelNumber( + @IntRange(from = -7, to = 6) int frequencyChannelNumber) { + mFrequencyChannelNumber = frequencyChannelNumber; + return this; + } + + /** Sets the L1/L2 group delay difference in seconds (DeltaTau). */ + @NonNull + public Builder setGroupDelayDiffSeconds( + @FloatRange(from = -1.4e-8f, to = 1.4e-8f) double groupDelayDiffSeconds) { + mGroupDelayDiffSeconds = groupDelayDiffSeconds; + return this; + } + + /** Sets whether the L1/L2 group delay difference in seconds (DeltaTau) is available. */ @NonNull - public Builder setFrequencyNumber(@IntRange(from = -7, to = 6) int frequencyNumber) { - mFrequencyNumber = frequencyNumber; + public Builder setGroupDelayDiffSecondsAvailable( + boolean isGroupDelayDiffSecondsAvailable) { + mGroupDelayDiffSecondsAvailable = isGroupDelayDiffSecondsAvailable; return this; } diff --git a/location/java/android/location/GnssAlmanac.java b/location/java/android/location/GnssAlmanac.java index 6466e45a965e..c16ad91f130a 100644 --- a/location/java/android/location/GnssAlmanac.java +++ b/location/java/android/location/GnssAlmanac.java @@ -59,7 +59,7 @@ public final class GnssAlmanac implements Parcelable { * * <p>This is unused for GPS/QZSS/Baidou. */ - private final int mIod; + private final int mIoda; /** * Almanac reference week number. @@ -75,20 +75,27 @@ public final class GnssAlmanac implements Parcelable { /** Almanac reference time in seconds. */ private final int mToaSeconds; + /** + * Flag to indicate if the satelliteAlmanacs contains complete GNSS + * constellation indicated by svid. + */ + private final boolean mCompleteAlmanacProvided; + /** The list of GnssSatelliteAlmanacs. */ @NonNull private final List<GnssSatelliteAlmanac> mGnssSatelliteAlmanacs; private GnssAlmanac(Builder builder) { Preconditions.checkArgument(builder.mIssueDateMillis >= 0); - Preconditions.checkArgument(builder.mIod >= 0); + Preconditions.checkArgument(builder.mIoda >= 0); Preconditions.checkArgument(builder.mWeekNumber >= 0); Preconditions.checkArgumentInRange(builder.mToaSeconds, 0, 604800, "ToaSeconds"); Preconditions.checkNotNull( builder.mGnssSatelliteAlmanacs, "GnssSatelliteAlmanacs cannot be null"); mIssueDateMillis = builder.mIssueDateMillis; - mIod = builder.mIod; + mIoda = builder.mIoda; mWeekNumber = builder.mWeekNumber; mToaSeconds = builder.mToaSeconds; + mCompleteAlmanacProvided = builder.mCompleteAlmanacProvided; mGnssSatelliteAlmanacs = Collections.unmodifiableList(new ArrayList<>(builder.mGnssSatelliteAlmanacs)); } @@ -101,8 +108,8 @@ public final class GnssAlmanac implements Parcelable { /** Returns the almanac issue of data. */ @IntRange(from = 0) - public int getIod() { - return mIod; + public int getIoda() { + return mIoda; } /** @@ -125,6 +132,14 @@ public final class GnssAlmanac implements Parcelable { return mToaSeconds; } + /** + * Returns the flag to indicate if the satelliteAlmanacs contains complete GNSS + * constellation indicated by svid. + */ + public boolean isCompleteAlmanacProvided() { + return mCompleteAlmanacProvided; + } + /** Returns the list of GnssSatelliteAlmanacs. */ @NonNull public List<GnssSatelliteAlmanac> getGnssSatelliteAlmanacs() { @@ -139,9 +154,10 @@ public final class GnssAlmanac implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeLong(mIssueDateMillis); - dest.writeInt(mIod); + dest.writeInt(mIoda); dest.writeInt(mWeekNumber); dest.writeInt(mToaSeconds); + dest.writeBoolean(mCompleteAlmanacProvided); dest.writeTypedList(mGnssSatelliteAlmanacs); } @@ -151,9 +167,10 @@ public final class GnssAlmanac implements Parcelable { public GnssAlmanac createFromParcel(Parcel in) { GnssAlmanac.Builder gnssAlmanac = new GnssAlmanac.Builder(); gnssAlmanac.setIssueDateMillis(in.readLong()); - gnssAlmanac.setIod(in.readInt()); + gnssAlmanac.setIoda(in.readInt()); gnssAlmanac.setWeekNumber(in.readInt()); gnssAlmanac.setToaSeconds(in.readInt()); + gnssAlmanac.setCompleteAlmanacProvided(in.readBoolean()); List<GnssSatelliteAlmanac> satelliteAlmanacs = new ArrayList<>(); in.readTypedList(satelliteAlmanacs, GnssSatelliteAlmanac.CREATOR); gnssAlmanac.setGnssSatelliteAlmanacs(satelliteAlmanacs); @@ -170,9 +187,10 @@ public final class GnssAlmanac implements Parcelable { public String toString() { StringBuilder builder = new StringBuilder("GnssAlmanac["); builder.append("issueDateMillis=").append(mIssueDateMillis); - builder.append(", iod=").append(mIod); + builder.append(", ioda=").append(mIoda); builder.append(", weekNumber=").append(mWeekNumber); builder.append(", toaSeconds=").append(mToaSeconds); + builder.append(", completeAlmanacProvided=").append(mCompleteAlmanacProvided); builder.append(", satelliteAlmanacs=").append(mGnssSatelliteAlmanacs); builder.append("]"); return builder.toString(); @@ -181,9 +199,10 @@ public final class GnssAlmanac implements Parcelable { /** Builder for {@link GnssAlmanac}. */ public static final class Builder { private long mIssueDateMillis; - private int mIod; + private int mIoda; private int mWeekNumber; private int mToaSeconds; + private boolean mCompleteAlmanacProvided; private List<GnssSatelliteAlmanac> mGnssSatelliteAlmanacs; /** Sets the almanac issue date in milliseconds (UTC). */ @@ -195,8 +214,8 @@ public final class GnssAlmanac implements Parcelable { /** Sets the almanac issue of data. */ @NonNull - public Builder setIod(@IntRange(from = 0) int iod) { - mIod = iod; + public Builder setIoda(@IntRange(from = 0) int ioda) { + mIoda = ioda; return this; } @@ -222,6 +241,16 @@ public final class GnssAlmanac implements Parcelable { return this; } + /** + * Sets to true if the satelliteAlmanacs contains complete GNSS + * constellation indicated by svid, false otherwise. + */ + @NonNull + public Builder setCompleteAlmanacProvided(boolean isCompleteAlmanacProvided) { + this.mCompleteAlmanacProvided = isCompleteAlmanacProvided; + return this; + } + /** Sets the list of GnssSatelliteAlmanacs. */ @NonNull public Builder setGnssSatelliteAlmanacs( @@ -249,7 +278,7 @@ public final class GnssAlmanac implements Parcelable { * <p>For Galileo, this is defined in Galileo-OS-SIS-ICD-v2.1 section 5.1.10. */ public static final class GnssSatelliteAlmanac implements Parcelable { - /** The PRN number of the GNSS satellite. */ + /** The PRN or satellite ID number for the GNSS satellite. */ private final int mSvid; /** @@ -332,7 +361,7 @@ public final class GnssAlmanac implements Parcelable { mAf1 = builder.mAf1; } - /** Returns the PRN number of the GNSS satellite. */ + /** Returns the PRN or satellite ID number of the GNSS satellite. */ @IntRange(from = 1) public int getSvid() { return mSvid; @@ -503,7 +532,7 @@ public final class GnssAlmanac implements Parcelable { private double mAf0; private double mAf1; - /** Sets the PRN number of the GNSS satellite. */ + /** Sets the PRN or satellite ID number of the GNSS satellite. */ @NonNull public Builder setSvid(@IntRange(from = 1) int svid) { mSvid = svid; diff --git a/location/java/android/location/GpsSatelliteEphemeris.java b/location/java/android/location/GpsSatelliteEphemeris.java index ec6bc59dc69c..0abdc30d2f19 100644 --- a/location/java/android/location/GpsSatelliteEphemeris.java +++ b/location/java/android/location/GpsSatelliteEphemeris.java @@ -37,8 +37,8 @@ import com.android.internal.util.Preconditions; @FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) @SystemApi public final class GpsSatelliteEphemeris implements Parcelable { - /** Satellite PRN */ - private final int mPrn; + /** PRN or satellite ID number for the GPS satellite. */ + private final int mSvid; /** L2 parameters. */ @NonNull private final GpsL2Params mGpsL2Params; @@ -56,8 +56,8 @@ public final class GpsSatelliteEphemeris implements Parcelable { @NonNull private final SatelliteEphemerisTime mSatelliteEphemerisTime; private GpsSatelliteEphemeris(Builder builder) { - // Allow PRN beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mPrn >= 1); + // Allow svid beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvid >= 1); Preconditions.checkNotNull(builder.mGpsL2Params, "GPSL2Params cannot be null"); Preconditions.checkNotNull(builder.mSatelliteClockModel, "SatelliteClockModel cannot be null"); @@ -67,7 +67,7 @@ public final class GpsSatelliteEphemeris implements Parcelable { "SatelliteHealth cannot be null"); Preconditions.checkNotNull(builder.mSatelliteEphemerisTime, "SatelliteEphemerisTime cannot be null"); - mPrn = builder.mPrn; + mSvid = builder.mSvid; mGpsL2Params = builder.mGpsL2Params; mSatelliteClockModel = builder.mSatelliteClockModel; mSatelliteOrbitModel = builder.mSatelliteOrbitModel; @@ -75,10 +75,10 @@ public final class GpsSatelliteEphemeris implements Parcelable { mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; } - /** Returns the PRN of the satellite. */ + /** Returns the svid of the satellite. */ @IntRange(from = 1, to = 32) - public int getPrn() { - return mPrn; + public int getSvid() { + return mSvid; } /** Returns the L2 parameters of the satellite. */ @@ -118,7 +118,7 @@ public final class GpsSatelliteEphemeris implements Parcelable { public GpsSatelliteEphemeris createFromParcel(Parcel in) { final GpsSatelliteEphemeris.Builder gpsSatelliteEphemeris = new Builder() - .setPrn(in.readInt()) + .setSvid(in.readInt()) .setGpsL2Params(in.readTypedObject(GpsL2Params.CREATOR)) .setSatelliteClockModel( in.readTypedObject(GpsSatelliteClockModel.CREATOR)) @@ -144,7 +144,7 @@ public final class GpsSatelliteEphemeris implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mPrn); + parcel.writeInt(mSvid); parcel.writeTypedObject(mGpsL2Params, flags); parcel.writeTypedObject(mSatelliteClockModel, flags); parcel.writeTypedObject(mSatelliteOrbitModel, flags); @@ -156,7 +156,7 @@ public final class GpsSatelliteEphemeris implements Parcelable { @NonNull public String toString() { StringBuilder builder = new StringBuilder("GpsSatelliteEphemeris["); - builder.append("prn = ").append(mPrn); + builder.append("Svid = ").append(mSvid); builder.append(", gpsL2Params = ").append(mGpsL2Params); builder.append(", satelliteClockModel = ").append(mSatelliteClockModel); builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); @@ -168,17 +168,17 @@ public final class GpsSatelliteEphemeris implements Parcelable { /** Builder for {@link GpsSatelliteEphemeris} */ public static final class Builder { - private int mPrn = 0; + private int mSvid = 0; private GpsL2Params mGpsL2Params; private GpsSatelliteClockModel mSatelliteClockModel; private KeplerianOrbitModel mSatelliteOrbitModel; private GpsSatelliteHealth mSatelliteHealth; private SatelliteEphemerisTime mSatelliteEphemerisTime; - /** Sets the PRN of the satellite. */ + /** Sets the PRN or satellite ID number for the GPS satellite.. */ @NonNull - public Builder setPrn(@IntRange(from = 1, to = 32) int prn) { - mPrn = prn; + public Builder setSvid(@IntRange(from = 1, to = 32) int svid) { + mSvid = svid; return this; } diff --git a/location/java/android/location/QzssSatelliteEphemeris.java b/location/java/android/location/QzssSatelliteEphemeris.java index 96203d9588c8..dd9f408f53be 100644 --- a/location/java/android/location/QzssSatelliteEphemeris.java +++ b/location/java/android/location/QzssSatelliteEphemeris.java @@ -39,8 +39,8 @@ import com.android.internal.util.Preconditions; @FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) @SystemApi public final class QzssSatelliteEphemeris implements Parcelable { - /** Satellite PRN. */ - private final int mPrn; + /** PRN or satellite ID number for the Qzss satellite. */ + private final int mSvid; /** L2 parameters. */ @NonNull private final GpsL2Params mGpsL2Params; @@ -57,10 +57,10 @@ public final class QzssSatelliteEphemeris implements Parcelable { /** Ephemeris time. */ @NonNull private final SatelliteEphemerisTime mSatelliteEphemerisTime; - /** Returns the PRN of the satellite. */ + /** Returns the PRN or satellite ID number for the Qzss satellite. */ @IntRange(from = 183, to = 206) - public int getPrn() { - return mPrn; + public int getSvid() { + return mSvid; } /** Returns the L2 parameters of the satellite. */ @@ -95,7 +95,7 @@ public final class QzssSatelliteEphemeris implements Parcelable { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mPrn); + parcel.writeInt(mSvid); parcel.writeTypedObject(mGpsL2Params, flags); parcel.writeTypedObject(mSatelliteClockModel, flags); parcel.writeTypedObject(mSatelliteOrbitModel, flags); @@ -104,8 +104,8 @@ public final class QzssSatelliteEphemeris implements Parcelable { } private QzssSatelliteEphemeris(Builder builder) { - // Allow PRN beyond the range to support potential future extensibility. - Preconditions.checkArgument(builder.mPrn >= 1); + // Allow Svid beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvid >= 1); Preconditions.checkNotNull(builder.mGpsL2Params, "GpsL2Params cannot be null"); Preconditions.checkNotNull(builder.mSatelliteClockModel, "SatelliteClockModel cannot be null"); @@ -115,7 +115,7 @@ public final class QzssSatelliteEphemeris implements Parcelable { "SatelliteHealth cannot be null"); Preconditions.checkNotNull(builder.mSatelliteEphemerisTime, "SatelliteEphemerisTime cannot be null"); - mPrn = builder.mPrn; + mSvid = builder.mSvid; mGpsL2Params = builder.mGpsL2Params; mSatelliteClockModel = builder.mSatelliteClockModel; mSatelliteOrbitModel = builder.mSatelliteOrbitModel; @@ -130,7 +130,7 @@ public final class QzssSatelliteEphemeris implements Parcelable { public QzssSatelliteEphemeris createFromParcel(Parcel in) { final QzssSatelliteEphemeris.Builder qzssSatelliteEphemeris = new Builder() - .setPrn(in.readInt()) + .setSvid(in.readInt()) .setGpsL2Params(in.readTypedObject(GpsL2Params.CREATOR)) .setSatelliteClockModel( in.readTypedObject(GpsSatelliteClockModel.CREATOR)) @@ -158,7 +158,7 @@ public final class QzssSatelliteEphemeris implements Parcelable { @NonNull public String toString() { StringBuilder builder = new StringBuilder("QzssSatelliteEphemeris["); - builder.append("prn=").append(mPrn); + builder.append("Svid=").append(mSvid); builder.append(", gpsL2Params=").append(mGpsL2Params); builder.append(", satelliteClockModel=").append(mSatelliteClockModel); builder.append(", satelliteOrbitModel=").append(mSatelliteOrbitModel); @@ -170,17 +170,17 @@ public final class QzssSatelliteEphemeris implements Parcelable { /** Builder for {@link QzssSatelliteEphemeris}. */ public static final class Builder { - private int mPrn; + private int mSvid; private GpsL2Params mGpsL2Params; private GpsSatelliteClockModel mSatelliteClockModel; private KeplerianOrbitModel mSatelliteOrbitModel; private GpsSatelliteHealth mSatelliteHealth; private SatelliteEphemerisTime mSatelliteEphemerisTime; - /** Sets the PRN of the satellite. */ + /** Sets the PRN or satellite ID number for the Qzss satellite. */ @NonNull - public Builder setPrn(@IntRange(from = 183, to = 206) int prn) { - mPrn = prn; + public Builder setSvid(@IntRange(from = 183, to = 206) int svid) { + mSvid = svid; return this; } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 04fa90514dc9..52eae43f7db9 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -8466,9 +8466,13 @@ public class AudioManager { /** * @hide * Registers a callback for notification of audio server state changes. - * @param executor {@link Executor} to handle the callbacks - * @param stateCallback the callback to receive the audio server state changes - * To remove the callabck, pass a null reference for both executor and stateCallback. + * @param executor {@link Executor} to handle the callbacks. Must be non null. + * @param stateCallback the callback to receive the audio server state changes. + * Must be non null. To remove the callabck, + * call {@link #clearAudioServerStateCallback()} + * @throws IllegalArgumentException If a null argument is specified. + * @throws IllegalStateException If a callback is already registered + * * */ @SystemApi public void setAudioServerStateCallback(@NonNull Executor executor, diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java index d6fe68253be6..486063ed5f6b 100644 --- a/media/java/android/media/Image.java +++ b/media/java/android/media/Image.java @@ -180,6 +180,15 @@ public abstract class Image implements AutoCloseable { * a semi-planar format, the Cb plane can also be treated as an interleaved Cb/Cr plane. * </td> * </tr> + * <tr> + * <td>{@link android.graphics.ImageFormat#YCBCR_P210 YCBCR_P210}</td> + * <td>3</td> + * <td>P210 is a 4:2:2 YCbCr semiplanar format comprised of a WxH Y plane + * followed by a WxH Cb and Cr planes. Each sample is represented by a 16-bit + * little-endian value, with the lower 6 bits set to zero. Since this is guaranteed to be + * a semi-planar format, the Cb plane can also be treated as an interleaved Cb/Cr plane. + * </td> + * </tr> * </table> * * @see android.graphics.ImageFormat diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index c7678067f0b5..7c6ba838b24f 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -56,6 +56,7 @@ class ImageUtils { case ImageFormat.YUV_420_888: case ImageFormat.NV21: case ImageFormat.YCBCR_P010: + case ImageFormat.YCBCR_P210: return 3; case ImageFormat.NV16: return 2; @@ -95,6 +96,7 @@ class ImageUtils { switch(hardwareBufferFormat) { case HardwareBuffer.YCBCR_420_888: case HardwareBuffer.YCBCR_P010: + case HardwareBuffer.YCBCR_P210: return 3; case HardwareBuffer.RGBA_8888: case HardwareBuffer.RGBX_8888: @@ -281,6 +283,7 @@ class ImageUtils { case PixelFormat.RGBA_8888: case PixelFormat.RGBX_8888: case PixelFormat.RGBA_1010102: + case ImageFormat.YCBCR_P210: estimatedBytePerPixel = 4.0; break; default: @@ -335,6 +338,12 @@ class ImageUtils { return new Size(image.getWidth(), image.getHeight()); case ImageFormat.PRIVATE: return new Size(0, 0); + case ImageFormat.YCBCR_P210: + if (planeIdx == 0) { + return new Size(image.getWidth(), image.getHeight()); + } else { + return new Size(image.getWidth() / 2, image.getHeight()); + } default: if (Log.isLoggable(IMAGEUTILS_LOG_TAG, Log.VERBOSE)) { Log.v(IMAGEUTILS_LOG_TAG, "getEffectivePlaneSizeForImage() uses" diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 82e950365850..36f62da651db 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -6158,11 +6158,14 @@ final public class MediaCodec { buffer.limit(buffer.position() + Utils.divUp(bitDepth, 8) + (mHeight / vert - 1) * rowInc + (mWidth / horiz - 1) * colInc); mPlanes[ix] = new MediaPlane(buffer.slice(), rowInc, colInc); - if ((mFormat == ImageFormat.YUV_420_888 || mFormat == ImageFormat.YCBCR_P010) + if ((mFormat == ImageFormat.YUV_420_888 || mFormat == ImageFormat.YCBCR_P010 + || mFormat == ImageFormat.YCBCR_P210) && ix == 1) { cbPlaneOffset = planeOffset; } else if ((mFormat == ImageFormat.YUV_420_888 - || mFormat == ImageFormat.YCBCR_P010) && ix == 2) { + || mFormat == ImageFormat.YCBCR_P010 + || mFormat == ImageFormat.YCBCR_P210) + && ix == 2) { crPlaneOffset = planeOffset; } } @@ -6172,7 +6175,7 @@ final public class MediaCodec { } // Validate chroma semiplanerness. - if (mFormat == ImageFormat.YCBCR_P010) { + if (mFormat == ImageFormat.YCBCR_P010 || mFormat == ImageFormat.YCBCR_P210) { if (crPlaneOffset != cbPlaneOffset + planeOffsetInc) { throw new UnsupportedOperationException("Invalid plane offsets" + " cbPlaneOffset: " + cbPlaneOffset + " crPlaneOffset: " + crPlaneOffset); diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 09f40e005b4c..60584d9c6f72 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -358,7 +358,9 @@ public abstract class MediaRoute2ProviderService extends Service { * @return a {@link MediaStreams} instance that holds the media streams to route as part of the * newly created routing session. May be null if system media capture failed, in which case * you can ignore the return value, as you will receive a call to {@link #onReleaseSession} - * where you can clean up this session + * where you can clean up this session. {@link AudioRecord#startRecording()} must be called + * immediately on {@link MediaStreams#getAudioRecord()} after calling this method, in order + * to start streaming audio to the receiver. * @hide */ // TODO: b/362507305 - Unhide once the implementation and CTS are in place. @@ -458,7 +460,6 @@ public abstract class MediaRoute2ProviderService extends Service { if (uid != Process.INVALID_UID) { audioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid); } - AudioMix mix = new AudioMix.Builder(audioMixingRuleBuilder.build()) .setFormat(audioFormat) @@ -471,7 +472,11 @@ public abstract class MediaRoute2ProviderService extends Service { Log.e(TAG, "Couldn't fetch the audio manager."); return; } - audioManager.registerAudioPolicy(audioPolicy); + int audioPolicyResult = audioManager.registerAudioPolicy(audioPolicy); + if (audioPolicyResult != AudioManager.SUCCESS) { + Log.e(TAG, "Failed to register the audio policy."); + return; + } var audioRecord = audioPolicy.createAudioRecordSink(mix); if (audioRecord == null) { Log.e(TAG, "Audio record creation failed."); @@ -540,17 +545,19 @@ public abstract class MediaRoute2ProviderService extends Service { } /** Releases any system media routing resources associated with the given {@code sessionId}. */ - private void maybeReleaseMediaStreams(String sessionId) { + private boolean maybeReleaseMediaStreams(String sessionId) { if (!Flags.enableMirroringInMediaRouter2()) { - return; + return false; } synchronized (mSessionLock) { var streams = mOngoingMediaStreams.remove(sessionId); if (streams != null) { releaseAudioStream(streams.mAudioPolicy, streams.mAudioRecord); // TODO: b/380431086: Release the video stream once implemented. + return true; } } + return false; } // We cannot reach the code that requires MODIFY_AUDIO_ROUTING without holding it. @@ -1019,12 +1026,12 @@ public abstract class MediaRoute2ProviderService extends Service { if (!checkCallerIsSystem()) { return; } - if (!checkSessionIdIsValid(sessionId, "releaseSession")) { - return; - } // We proactively release the system media routing once the system requests it, to // ensure it happens immediately. - maybeReleaseMediaStreams(sessionId); + if (!maybeReleaseMediaStreams(sessionId) + && !checkSessionIdIsValid(sessionId, "releaseSession")) { + return; + } addRequestId(requestId); mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession, diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index 4f7132ad9ab2..f7f10df5786a 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -324,19 +324,6 @@ public final class MediaProjection { } /** - * Stops projection. - * @hide - */ - public void stop(@StopReason int stopReason) { - try { - Log.d(TAG, "Content Recording: stopping projection"); - mImpl.stop(stopReason); - } catch (RemoteException e) { - Log.e(TAG, "Unable to stop projection", e); - } - } - - /** * Get the underlying IMediaProjection. * @hide */ diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp index fbebbdcb8761..e8f8644a4503 100644 --- a/media/jni/android_media_Utils.cpp +++ b/media/jni/android_media_Utils.cpp @@ -17,13 +17,14 @@ // #define LOG_NDEBUG 0 #define LOG_TAG "AndroidMediaUtils" +#include "android_media_Utils.h" + +#include <aidl/android/hardware/graphics/common/PixelFormat.h> #include <aidl/android/hardware/graphics/common/PlaneLayoutComponentType.h> #include <ui/GraphicBufferMapper.h> #include <ui/GraphicTypes.h> #include <utils/Log.h> -#include "android_media_Utils.h" - #define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) ) // Must be in sync with the value in HeicCompositeStream.cpp @@ -33,6 +34,8 @@ namespace android { // -----------Utility functions used by ImageReader/Writer JNI----------------- +using AidlPixelFormat = aidl::android::hardware::graphics::common::PixelFormat; + enum { IMAGE_MAX_NUM_PLANES = 3, }; @@ -74,6 +77,7 @@ bool isPossiblyYUV(PixelFormat format) { case HAL_PIXEL_FORMAT_BLOB: case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED: case HAL_PIXEL_FORMAT_YCBCR_P010: + case static_cast<int>(AidlPixelFormat::YCBCR_P210): return false; case HAL_PIXEL_FORMAT_YV12: @@ -105,6 +109,7 @@ bool isPossibly10BitYUV(PixelFormat format) { return false; case HAL_PIXEL_FORMAT_YCBCR_P010: + case static_cast<int>(AidlPixelFormat::YCBCR_P210): default: return true; } @@ -340,6 +345,47 @@ status_t getLockedImageInfo(LockedImage* buffer, int idx, cb = buffer->data + ySize; cr = cb + 2; + pData = (idx == 0) ? buffer->data : (idx == 1) ? cb : cr; + dataSize = (idx == 0) ? ySize : cSize; + rStride = buffer->stride * 2; + break; + case static_cast<int>(AidlPixelFormat::YCBCR_P210): + if (buffer->height % 2 != 0) { + ALOGE("YCBCR_P210: height (%d) should be a multiple of 2", buffer->height); + return BAD_VALUE; + } + + if (buffer->width <= 0) { + ALOGE("YCBCR_P210: width (%d) should be a > 0", buffer->width); + return BAD_VALUE; + } + + if (buffer->height <= 0) { + ALOGE("YCBCR_210: height (%d) should be a > 0", buffer->height); + return BAD_VALUE; + } + if (buffer->dataCb && buffer->dataCr) { + pData = (idx == 0) ? buffer->data : (idx == 1) ? buffer->dataCb : buffer->dataCr; + // only map until last pixel + if (idx == 0) { + pStride = 2; + rStride = buffer->stride; + dataSize = buffer->stride * (buffer->height - 1) + buffer->width * 2; + } else { + pStride = buffer->chromaStep; + rStride = buffer->chromaStride; + dataSize = buffer->chromaStride * (buffer->height - 1) + + buffer->chromaStep * (buffer->width / 2); + } + break; + } + + ySize = (buffer->stride * 2) * buffer->height; + cSize = ySize; + pStride = (idx == 0) ? 2 : 4; + cb = buffer->data + ySize; + cr = cb + 2; + pData = (idx == 0) ? buffer->data : (idx == 1) ? cb : cr; dataSize = (idx == 0) ? ySize : cSize; rStride = buffer->stride * 2; @@ -544,6 +590,80 @@ static status_t extractP010Gralloc4PlaneLayout( return OK; } +static status_t extractP210Gralloc4PlaneLayout(sp<GraphicBuffer> buffer, void *pData, int format, + LockedImage *outputImage) { + using aidl::android::hardware::graphics::common::PlaneLayoutComponent; + using aidl::android::hardware::graphics::common::PlaneLayoutComponentType; + + GraphicBufferMapper &mapper = GraphicBufferMapper::get(); + std::vector<ui::PlaneLayout> planeLayouts; + status_t res = mapper.getPlaneLayouts(buffer->handle, &planeLayouts); + if (res != OK) { + return res; + } + constexpr int64_t Y_PLANE_COMPONENTS = int64_t(PlaneLayoutComponentType::Y); + constexpr int64_t CBCR_PLANE_COMPONENTS = + int64_t(PlaneLayoutComponentType::CB) | int64_t(PlaneLayoutComponentType::CR); + uint8_t *dataY = nullptr; + uint8_t *dataCb = nullptr; + uint8_t *dataCr = nullptr; + uint32_t strideY = 0; + uint32_t strideCbCr = 0; + for (const ui::PlaneLayout &layout : planeLayouts) { + ALOGV("gralloc4 plane: %s", layout.toString().c_str()); + int64_t components = 0; + for (const PlaneLayoutComponent &component : layout.components) { + if (component.sizeInBits != 10) { + return BAD_VALUE; + } + components |= component.type.value; + } + if (components == Y_PLANE_COMPONENTS) { + if (layout.sampleIncrementInBits != 16) { + return BAD_VALUE; + } + if (layout.components[0].offsetInBits != 6) { + return BAD_VALUE; + } + dataY = (uint8_t *)pData + layout.offsetInBytes; + strideY = layout.strideInBytes; + } else if (components == CBCR_PLANE_COMPONENTS) { + if (layout.sampleIncrementInBits != 32) { + return BAD_VALUE; + } + for (const PlaneLayoutComponent &component : layout.components) { + if (component.type.value == int64_t(PlaneLayoutComponentType::CB) && + component.offsetInBits != 6) { + return BAD_VALUE; + } + if (component.type.value == int64_t(PlaneLayoutComponentType::CR) && + component.offsetInBits != 22) { + return BAD_VALUE; + } + } + dataCb = (uint8_t *)pData + layout.offsetInBytes; + dataCr = (uint8_t *)pData + layout.offsetInBytes + 2; + strideCbCr = layout.strideInBytes; + } else { + return BAD_VALUE; + } + } + + outputImage->data = dataY; + outputImage->width = buffer->getWidth(); + outputImage->height = buffer->getHeight(); + outputImage->format = format; + outputImage->flexFormat = + static_cast<int>(AidlPixelFormat::YCBCR_P210); + outputImage->stride = strideY; + + outputImage->dataCb = dataCb; + outputImage->dataCr = dataCr; + outputImage->chromaStride = strideCbCr; + outputImage->chromaStep = 4; + return OK; +} + status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage, const Rect& rect, int fenceFd, LockedImage* outputImage) { ALOGV("%s: Try to lock the GraphicBuffer", __FUNCTION__); @@ -581,10 +701,17 @@ status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage, ALOGE("Lock buffer failed!"); return res; } - if (isPossibly10BitYUV(format) - && OK == extractP010Gralloc4PlaneLayout(buffer, pData, format, outputImage)) { - ALOGV("%s: Successfully locked the P010 image", __FUNCTION__); - return OK; + if (isPossibly10BitYUV(format)) { + if (format == HAL_PIXEL_FORMAT_YCBCR_P010 && + OK == extractP010Gralloc4PlaneLayout(buffer, pData, format, outputImage)) { + ALOGV("%s: Successfully locked the P010 image", __FUNCTION__); + return OK; + } else if ((format == + static_cast<int>(AidlPixelFormat::YCBCR_P210)) && + OK == extractP210Gralloc4PlaneLayout(buffer, pData, format, outputImage)) { + ALOGV("%s: Successfully locked the P210 image", __FUNCTION__); + return OK; + } } } diff --git a/nfc-non-updatable/Android.bp b/nfc-non-updatable/Android.bp new file mode 100644 index 000000000000..ff987bb84b17 --- /dev/null +++ b/nfc-non-updatable/Android.bp @@ -0,0 +1,23 @@ +package { + default_team: "trendy_team_fwk_nfc", + // 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"], +} + +filegroup { + name: "framework-nfc-non-updatable-sources", + path: "java", + srcs: [ + "java/android/nfc/NfcServiceManager.java", + "java/android/nfc/cardemulation/ApduServiceInfo.aidl", + "java/android/nfc/cardemulation/ApduServiceInfo.java", + "java/android/nfc/cardemulation/NfcFServiceInfo.aidl", + "java/android/nfc/cardemulation/NfcFServiceInfo.java", + "java/android/nfc/cardemulation/AidGroup.aidl", + "java/android/nfc/cardemulation/AidGroup.java", + ], +} diff --git a/nfc-non-updatable/OWNERS b/nfc-non-updatable/OWNERS new file mode 100644 index 000000000000..f46dccd97974 --- /dev/null +++ b/nfc-non-updatable/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 48448 +include platform/packages/apps/Nfc:/OWNERS
\ No newline at end of file diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig index ee287aba709f..6b14a1ed3990 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc-non-updatable/flags/flags.aconfig @@ -189,3 +189,11 @@ flag { description: "App can check its tag intent preference status" bug: "335916336" } + +flag { + name: "nfc_apdu_service_info_constructor" + is_exported: true + namespace: "nfc" + description: "Expose constructor for ApduServiceInfo" + bug: "380892385" +} diff --git a/nfc/java/android/nfc/NfcServiceManager.java b/nfc-non-updatable/java/android/nfc/NfcServiceManager.java index 5582f1154cad..5582f1154cad 100644 --- a/nfc/java/android/nfc/NfcServiceManager.java +++ b/nfc-non-updatable/java/android/nfc/NfcServiceManager.java diff --git a/nfc/java/android/nfc/cardemulation/AidGroup.aidl b/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.aidl index 56d6fa559677..56d6fa559677 100644 --- a/nfc/java/android/nfc/cardemulation/AidGroup.aidl +++ b/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.aidl diff --git a/nfc/java/android/nfc/cardemulation/AidGroup.java b/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.java index ae3e333051d7..ae3e333051d7 100644 --- a/nfc/java/android/nfc/cardemulation/AidGroup.java +++ b/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.java diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.aidl index a62fdd6a6c5c..a62fdd6a6c5c 100644 --- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl +++ b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.aidl diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java index 7f64dbea0be3..93d6eb73dcae 100644 --- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -177,13 +177,30 @@ public final class ApduServiceInfo implements Parcelable { private boolean mWantsRoleHolderPriority; /** + * Constructor of {@link ApduServiceInfo}. + * @param info App component info + * @param onHost whether service is on host or not (secure element) + * @param description The description of service + * @param staticAidGroups static AID groups + * @param dynamicAidGroups dynamic AID groups + * @param requiresUnlock whether this service should only be started + * when the device is unlocked + * @param bannerResource The id of the service banner specified in XML + * @param uid The uid of the package the service belongs to + * @param settingsActivityName Settings Activity for this service + * @param offHost Off-host reader name + * @param staticOffHost Off-host reader name from manifest file + * * @hide */ @UnsupportedAppUsage - public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, - ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups, + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_APDU_SERVICE_INFO_CONSTRUCTOR) + public ApduServiceInfo(@NonNull ResolveInfo info, boolean onHost, @NonNull String description, + @NonNull List<AidGroup> staticAidGroups, @NonNull List<AidGroup> dynamicAidGroups, boolean requiresUnlock, int bannerResource, int uid, - String settingsActivityName, String offHost, String staticOffHost) { + @NonNull String settingsActivityName, @NonNull String offHost, + @NonNull String staticOffHost) { this(info, onHost, description, staticAidGroups, dynamicAidGroups, requiresUnlock, bannerResource, uid, settingsActivityName, offHost, staticOffHost, false); @@ -193,7 +210,7 @@ public final class ApduServiceInfo implements Parcelable { * @hide */ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, - ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups, + List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups, boolean requiresUnlock, int bannerResource, int uid, String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) { diff --git a/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.aidl index 56b98ebd90fa..56b98ebd90fa 100644 --- a/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl +++ b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.aidl diff --git a/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.java index 33bc16978721..33bc16978721 100644 --- a/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java +++ b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.java diff --git a/nfc/Android.bp b/nfc/Android.bp index c33665aef41d..0fdb3bd38db8 100644 --- a/nfc/Android.bp +++ b/nfc/Android.bp @@ -9,28 +9,16 @@ package { } filegroup { - name: "framework-nfc-non-updatable-sources", - path: "java", - srcs: [ - "java/android/nfc/NfcServiceManager.java", - "java/android/nfc/cardemulation/ApduServiceInfo.aidl", - "java/android/nfc/cardemulation/ApduServiceInfo.java", - "java/android/nfc/cardemulation/NfcFServiceInfo.aidl", - "java/android/nfc/cardemulation/NfcFServiceInfo.java", - "java/android/nfc/cardemulation/AidGroup.aidl", - "java/android/nfc/cardemulation/AidGroup.java", - ], -} - -filegroup { name: "framework-nfc-updatable-sources", path: "java", srcs: [ "java/**/*.java", "java/**/*.aidl", ], - exclude_srcs: [ - ":framework-nfc-non-updatable-sources", + visibility: [ + "//frameworks/base:__subpackages__", + "//packages/apps/Nfc:__subpackages__", + "//packages/modules/Nfc:__subpackages__", ], } @@ -68,8 +56,7 @@ java_sdk_library { ], impl_library_visibility: [ "//frameworks/base:__subpackages__", - "//cts/hostsidetests/multidevices/nfc:__subpackages__", - "//cts/tests/tests/nfc", + "//cts:__subpackages__", "//packages/apps/Nfc:__subpackages__", "//packages/modules/Nfc:__subpackages__", ], diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 0ee81cbb7a73..c8c479a4d2ad 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -211,7 +211,7 @@ package android.nfc.cardemulation { method public boolean isDefaultServiceForCategory(android.content.ComponentName, String); method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported(); method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>); - method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener); + method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventCallback); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean); method public boolean removeAidsForService(android.content.ComponentName, String); @@ -221,7 +221,7 @@ package android.nfc.cardemulation { method public boolean setPreferredService(android.app.Activity, android.content.ComponentName); method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean); method public boolean supportsAidPrefixRegistration(); - method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventListener(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener); + method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventCallback(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventCallback); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName); method public boolean unsetPreferredService(android.app.Activity); field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; @@ -244,7 +244,7 @@ package android.nfc.cardemulation { field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0 } - @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventListener { + @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventCallback { method @FlaggedApi("android.nfc.nfc_event_listener") public default void onAidConflictOccurred(@NonNull String); method @FlaggedApi("android.nfc.nfc_event_listener") public default void onAidNotRouted(@NonNull String); method @FlaggedApi("android.nfc.nfc_event_listener") public default void onInternalErrorReported(int); diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl index bb9fe959dc06..00ceaa9801d8 100644 --- a/nfc/java/android/nfc/INfcCardEmulation.aidl +++ b/nfc/java/android/nfc/INfcCardEmulation.aidl @@ -17,7 +17,7 @@ package android.nfc; import android.content.ComponentName; -import android.nfc.INfcEventListener; +import android.nfc.INfcEventCallback; import android.nfc.cardemulation.AidGroup; import android.nfc.cardemulation.ApduServiceInfo; @@ -60,6 +60,6 @@ interface INfcCardEmulation List<String> getRoutingStatus(); void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc); - void registerNfcEventListener(in INfcEventListener listener); - void unregisterNfcEventListener(in INfcEventListener listener); + void registerNfcEventCallback(in INfcEventCallback listener); + void unregisterNfcEventCallback(in INfcEventCallback listener); } diff --git a/nfc/java/android/nfc/INfcEventListener.aidl b/nfc/java/android/nfc/INfcEventCallback.aidl index 774d8f875192..af1fa2fb2456 100644 --- a/nfc/java/android/nfc/INfcEventListener.aidl +++ b/nfc/java/android/nfc/INfcEventCallback.aidl @@ -5,7 +5,7 @@ import android.nfc.ComponentNameAndUser; /** * @hide */ -oneway interface INfcEventListener { +oneway interface INfcEventCallback { void onPreferredServiceChanged(in ComponentNameAndUser ComponentNameAndUser); void onObserveModeStateChanged(boolean isEnabled); void onAidConflictOccurred(in String aid); diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index 89ce4239cd4d..63397c21b036 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -1789,6 +1789,11 @@ public final class NfcAdapter { * @param listenTechnology Flags indicating listen technologies. * @throws UnsupportedOperationException if FEATURE_NFC, * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable. + * + * NOTE: This API overrides all technology flags regardless of the current device state, + * it is incompatible with enableReaderMode() API and the others that either update + * or assume any techlology flag set by the OS. + * Please use with care. */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH) diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index baae05b4ea03..fee9c5bfa328 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -39,7 +39,7 @@ import android.nfc.ComponentNameAndUser; import android.nfc.Constants; import android.nfc.Flags; import android.nfc.INfcCardEmulation; -import android.nfc.INfcEventListener; +import android.nfc.INfcEventCallback; import android.nfc.NfcAdapter; import android.os.Build; import android.os.RemoteException; @@ -1304,7 +1304,7 @@ public final class CardEmulation { /** Listener for preferred service state changes. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) - public interface NfcEventListener { + public interface NfcEventCallback { /** * This method is called when this package gains or loses preferred Nfc service status, * either the Default Wallet Role holder (see {@link @@ -1380,10 +1380,10 @@ public final class CardEmulation { default void onInternalErrorReported(@NfcInternalErrorType int errorType) {} } - private final ArrayMap<NfcEventListener, Executor> mNfcEventListeners = new ArrayMap<>(); + private final ArrayMap<NfcEventCallback, Executor> mNfcEventCallbacks = new ArrayMap<>(); - final INfcEventListener mINfcEventListener = - new INfcEventListener.Stub() { + final INfcEventCallback mINfcEventCallback = + new INfcEventCallback.Stub() { public void onPreferredServiceChanged(ComponentNameAndUser componentNameAndUser) { if (!android.nfc.Flags.nfcEventListener()) { return; @@ -1443,12 +1443,12 @@ public final class CardEmulation { } interface ListenerCall { - void invoke(NfcEventListener listener); + void invoke(NfcEventCallback listener); } private void callListeners(ListenerCall listenerCall) { - synchronized (mNfcEventListeners) { - mNfcEventListeners.forEach( + synchronized (mNfcEventCallbacks) { + mNfcEventCallbacks.forEach( (listener, executor) -> { executor.execute(() -> listenerCall.invoke(listener)); }); @@ -1463,34 +1463,34 @@ public final class CardEmulation { * @param listener The listener to register */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) - public void registerNfcEventListener( - @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventListener listener) { + public void registerNfcEventCallback( + @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventCallback listener) { if (!android.nfc.Flags.nfcEventListener()) { return; } - synchronized (mNfcEventListeners) { - mNfcEventListeners.put(listener, executor); - if (mNfcEventListeners.size() == 1) { - callService(() -> sService.registerNfcEventListener(mINfcEventListener)); + synchronized (mNfcEventCallbacks) { + mNfcEventCallbacks.put(listener, executor); + if (mNfcEventCallbacks.size() == 1) { + callService(() -> sService.registerNfcEventCallback(mINfcEventCallback)); } } } /** * Unregister a preferred service listener that was previously registered with {@link - * #registerNfcEventListener(Executor, NfcEventListener)} + * #registerNfcEventCallback(Executor, NfcEventCallback)} * * @param listener The previously registered listener to unregister */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER) - public void unregisterNfcEventListener(@NonNull NfcEventListener listener) { + public void unregisterNfcEventCallback(@NonNull NfcEventCallback listener) { if (!android.nfc.Flags.nfcEventListener()) { return; } - synchronized (mNfcEventListeners) { - mNfcEventListeners.remove(listener); - if (mNfcEventListeners.size() == 0) { - callService(() -> sService.unregisterNfcEventListener(mINfcEventListener)); + synchronized (mNfcEventCallbacks) { + mNfcEventCallbacks.remove(listener); + if (mNfcEventCallbacks.size() == 0) { + callService(() -> sService.unregisterNfcEventCallback(mINfcEventCallback)); } } } diff --git a/nfc/tests/Android.bp b/nfc/tests/Android.bp index b6090e853158..17fb810c626b 100644 --- a/nfc/tests/Android.bp +++ b/nfc/tests/Android.bp @@ -29,7 +29,6 @@ android_test { "androidx.test.rules", "androidx.test.runner", "androidx.test.ext.junit", - "framework-nfc.impl", "mockito-target-extended-minus-junit4", "frameworks-base-testutils", "truth", @@ -40,16 +39,25 @@ android_test { "testables", ], libs: [ + "androidx.annotation_annotation", + "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage + "framework-permission-s.stubs.module_lib", + "framework-permission.stubs.module_lib", "android.test.base.stubs.system", "android.test.mock.stubs.system", "android.test.runner.stubs.system", + "framework-nfc.impl", ], jni_libs: [ // Required for ExtendedMockito "libdexmakerjvmtiagent", "libstaticjvmtiagent", ], - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + ":framework-nfc-updatable-sources", + ":framework-nfc-non-updatable-sources", + ], platform_apis: true, certificate: "platform", test_suites: [ diff --git a/nfc/tests/src/android/nfc/NfcAntennaInfoTest.java b/nfc/tests/src/android/nfc/NfcAntennaInfoTest.java new file mode 100644 index 000000000000..c24816d85517 --- /dev/null +++ b/nfc/tests/src/android/nfc/NfcAntennaInfoTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nfc; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NfcAntennaInfoTest { + private NfcAntennaInfo mNfcAntennaInfo; + + + @Before + public void setUp() { + AvailableNfcAntenna availableNfcAntenna = mock(AvailableNfcAntenna.class); + List<AvailableNfcAntenna> antennas = new ArrayList<>(); + antennas.add(availableNfcAntenna); + mNfcAntennaInfo = new NfcAntennaInfo(1, 1, false, antennas); + } + + @After + public void tearDown() { + } + + @Test + public void testGetDeviceHeight() { + int height = mNfcAntennaInfo.getDeviceHeight(); + assertThat(height).isEqualTo(1); + } + + @Test + public void testGetDeviceWidth() { + int width = mNfcAntennaInfo.getDeviceWidth(); + assertThat(width).isEqualTo(1); + } + + @Test + public void testIsDeviceFoldable() { + boolean foldable = mNfcAntennaInfo.isDeviceFoldable(); + assertThat(foldable).isFalse(); + } + + @Test + public void testGetAvailableNfcAntennas() { + List<AvailableNfcAntenna> antennas = mNfcAntennaInfo.getAvailableNfcAntennas(); + assertThat(antennas).isNotNull(); + assertThat(antennas.size()).isEqualTo(1); + } + +} diff --git a/nfc/tests/src/android/nfc/tech/NfcATest.java b/nfc/tests/src/android/nfc/tech/NfcATest.java new file mode 100644 index 000000000000..40076ebd0a0a --- /dev/null +++ b/nfc/tests/src/android/nfc/tech/NfcATest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.nfc.tech; + +import static android.nfc.tech.NfcA.EXTRA_ATQA; +import static android.nfc.tech.NfcA.EXTRA_SAK; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.nfc.ErrorCodes; +import android.nfc.INfcTag; +import android.nfc.Tag; +import android.nfc.TransceiveResult; +import android.os.Bundle; +import android.os.RemoteException; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; + +public class NfcATest { + @Mock + private Tag mMockTag; + @Mock + private INfcTag mMockTagService; + @Mock + private Bundle mMockBundle; + private NfcA mNfcA; + private final byte[] mSampleArray = new byte[] {1, 2, 3}; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + when(mMockBundle.getShort(EXTRA_SAK)).thenReturn((short) 1); + when(mMockBundle.getByteArray(EXTRA_ATQA)).thenReturn(mSampleArray); + when(mMockTag.getTechExtras(TagTechnology.NFC_A)).thenReturn(mMockBundle); + + mNfcA = new NfcA(mMockTag); + } + + @Test + public void testGetNfcAWithTech() { + Tag mockTag = mock(Tag.class); + when(mockTag.getTechExtras(TagTechnology.NFC_A)).thenReturn(mMockBundle); + when(mockTag.hasTech(TagTechnology.NFC_A)).thenReturn(true); + + assertNotNull(NfcA.get(mockTag)); + verify(mockTag).getTechExtras(TagTechnology.NFC_A); + verify(mockTag).hasTech(TagTechnology.NFC_A); + } + + @Test + public void testGetNfcAWithoutTech() { + when(mMockTag.hasTech(TagTechnology.NFC_A)).thenReturn(false); + assertNull(NfcA.get(mMockTag)); + } + + @Test + public void testGetAtga() { + assertNotNull(mNfcA.getAtqa()); + } + + @Test + public void testGetSak() { + assertEquals((short) 1, mNfcA.getSak()); + } + + @Test + public void testTransceive() throws IOException, RemoteException { + TransceiveResult mockTransceiveResult = mock(TransceiveResult.class); + when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_A); + when(mMockTag.getTagService()).thenReturn(mMockTagService); + when(mMockTag.getServiceHandle()).thenReturn(1); + when(mMockTagService.transceive(1, mSampleArray, true)) + .thenReturn(mockTransceiveResult); + when(mockTransceiveResult.getResponseOrThrow()).thenReturn(mSampleArray); + + mNfcA.transceive(mSampleArray); + verify(mMockTag).getTagService(); + verify(mMockTag).getServiceHandle(); + } + + @Test + public void testGetMaxTransceiveLength() throws RemoteException { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_A)).thenReturn(1); + + mNfcA.getMaxTransceiveLength(); + verify(mMockTag).getTagService(); + } + + @Test + public void testSetTimeout() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.setTimeout(TagTechnology.NFC_A, 1000)).thenReturn( + ErrorCodes.SUCCESS); + + mNfcA.setTimeout(1000); + verify(mMockTag).getTagService(); + verify(mMockTagService).setTimeout(TagTechnology.NFC_A, 1000); + } catch (Exception e) { + fail("Unexpected exception during valid setTimeout: " + e.getMessage()); + } + } + + @Test + public void testSetTimeoutInvalidTimeout() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.setTimeout(TagTechnology.NFC_A, -1)).thenReturn( + ErrorCodes.ERROR_TIMEOUT); + + assertThrows(IllegalArgumentException.class, () -> mNfcA.setTimeout(-1)); + } catch (Exception e) { + fail("Unexpected exception during invalid setTimeout: " + e.getMessage()); + } + } + + @Test + public void testSetTimeoutRemoteException() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.setTimeout(TagTechnology.NFC_A, 1000)).thenThrow( + new RemoteException()); + + mNfcA.setTimeout(1000); // Should not throw an exception but log it + verify(mMockTag).getTagService(); + verify(mMockTagService).setTimeout(TagTechnology.NFC_A, 1000); + } catch (Exception e) { + fail("Unexpected exception during RemoteException in setTimeout: " + e.getMessage()); + } + + } + + @Test + public void testGetTimeout() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.getTimeout(TagTechnology.NFC_A)).thenReturn(2000); + + assertEquals(2000, mNfcA.getTimeout()); + verify(mMockTag).getTagService(); + verify(mMockTagService).getTimeout(TagTechnology.NFC_A); + } catch (Exception e) { + fail("Unexpected exception during valid getTimeout: " + e.getMessage()); + } + } + + @Test + public void testGetTimeoutRemoteException() { + when(mMockTag.getTagService()).thenReturn(mMockTagService); + try { + when(mMockTagService.getTimeout(TagTechnology.NFC_A)).thenThrow(new RemoteException()); + + assertEquals(0, mNfcA.getTimeout()); + } catch (Exception e) { + fail("Unexpected exception during RemoteException in getTimeout: " + e.getMessage()); + } + } +} diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java index 8b8ab587e84a..31e1eb36ad8d 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java @@ -767,6 +767,70 @@ public class PackageWatchdog { } /** + * Indicates that the result of a mitigation executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation} is unknown. + */ + public static final int MITIGATION_RESULT_UNKNOWN = + ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN; + + /** + * Indicates that a mitigation was successfully triggered or executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation}. + */ + public static final int MITIGATION_RESULT_SUCCESS = + ObserverMitigationResult.MITIGATION_RESULT_SUCCESS; + + /** + * Indicates that a mitigation executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation} was skipped. + */ + public static final int MITIGATION_RESULT_SKIPPED = + ObserverMitigationResult.MITIGATION_RESULT_SKIPPED; + + /** + * Indicates that a mitigation executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed, + * but the failure is potentially retryable. + */ + public static final int MITIGATION_RESULT_FAILURE_RETRYABLE = + ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE; + + /** + * Indicates that a mitigation executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed, + * and the failure is not retryable. + */ + public static final int MITIGATION_RESULT_FAILURE_NON_RETRYABLE = + ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE; + + /** + * Possible return values of the for mitigations executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} and + * {@link PackageHealthObserver#onExecuteBootLoopMitigation}. + * @hide + */ + @Retention(SOURCE) + @IntDef(prefix = "MITIGATION_RESULT_", value = { + ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN, + ObserverMitigationResult.MITIGATION_RESULT_SUCCESS, + ObserverMitigationResult.MITIGATION_RESULT_SKIPPED, + ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE, + ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE, + }) + public @interface ObserverMitigationResult { + int MITIGATION_RESULT_UNKNOWN = 0; + int MITIGATION_RESULT_SUCCESS = 1; + int MITIGATION_RESULT_SKIPPED = 2; + int MITIGATION_RESULT_FAILURE_RETRYABLE = 3; + int MITIGATION_RESULT_FAILURE_NON_RETRYABLE = 4; + } + + /** * The minimum value that can be returned by any observer. * It represents that no mitigations were available. */ @@ -852,13 +916,20 @@ public class PackageWatchdog { * health check. * * @param versionedPackage the package that is failing. This may be null if a native - * service is crashing. - * @param failureReason the type of failure that is occurring. + * service is crashing. + * @param failureReason the type of failure that is occurring. * @param mitigationCount the number of times mitigation has been called for this package - * (including this time). - * @return {@code true} if action was executed successfully, {@code false} otherwise + * (including this time). + * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful, + * {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be + * retried, + * {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and + * cannot be retried, + * {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown, + * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped. */ - boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage versionedPackage, + @ObserverMitigationResult int onExecuteHealthCheckMitigation( + @Nullable VersionedPackage versionedPackage, @FailureReasons int failureReason, int mitigationCount); @@ -885,10 +956,16 @@ public class PackageWatchdog { * @param mitigationCount the number of times mitigation has been attempted for this * boot loop (including this time). * - * @return {@code true} if action was executed successfully, {@code false} otherwise + * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful, + * {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be + * retried, + * {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and + * cannot be retried, + * {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown, + * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped. */ - default boolean onExecuteBootLoopMitigation(int mitigationCount) { - return false; + default @ObserverMitigationResult int onExecuteBootLoopMitigation(int mitigationCount) { + return ObserverMitigationResult.MITIGATION_RESULT_SKIPPED; } // TODO(b/120598832): Ensure uniqueness? diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java index bad6ab7c1dd4..bb9e96238e1c 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java @@ -16,6 +16,8 @@ package com.android.server; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent; import android.annotation.IntDef; @@ -728,10 +730,10 @@ public class RescueParty { } @Override - public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, + public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, @FailureReasons int failureReason, int mitigationCount) { if (isDisabled()) { - return false; + return MITIGATION_RESULT_SKIPPED; } Slog.i(TAG, "Executing remediation." + " failedPackage: " @@ -753,9 +755,9 @@ public class RescueParty { } executeRescueLevel(mContext, failedPackage == null ? null : failedPackage.getPackageName(), level); - return true; + return MITIGATION_RESULT_SUCCESS; } else { - return false; + return MITIGATION_RESULT_SKIPPED; } } @@ -796,9 +798,9 @@ public class RescueParty { } @Override - public boolean onExecuteBootLoopMitigation(int mitigationCount) { + public int onExecuteBootLoopMitigation(int mitigationCount) { if (isDisabled()) { - return false; + return MITIGATION_RESULT_SKIPPED; } boolean mayPerformReboot = !shouldThrottleReboot(); final int level; @@ -813,7 +815,7 @@ public class RescueParty { level = getRescueLevel(mitigationCount, mayPerformReboot); } executeRescueLevel(mContext, /*failedPackage=*/ null, level); - return true; + return MITIGATION_RESULT_SUCCESS; } @Override diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java index c80a1a4ea187..c75f3aa60ac4 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -16,6 +16,8 @@ package com.android.server.rollback; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent; import android.annotation.AnyThread; @@ -172,7 +174,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } @Override - public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, + public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, @FailureReasons int rollbackReason, int mitigationCount) { Slog.i(TAG, "Executing remediation." + " failedPackage: " @@ -183,7 +185,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason)); - return true; + return MITIGATION_RESULT_SUCCESS; } List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel( @@ -198,7 +200,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } else { if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { mHandler.post(() -> rollbackAll(rollbackReason)); - return true; + return MITIGATION_RESULT_SUCCESS; } RollbackInfo rollback = getAvailableRollback(failedPackage); @@ -210,7 +212,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } // Assume rollbacks executed successfully - return true; + return MITIGATION_RESULT_SUCCESS; } @Override @@ -226,15 +228,15 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } @Override - public boolean onExecuteBootLoopMitigation(int mitigationCount) { + public int onExecuteBootLoopMitigation(int mitigationCount) { if (Flags.recoverabilityDetection()) { List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); triggerLeastImpactLevelRollback(availableRollbacks, PackageWatchdog.FAILURE_REASON_BOOT_LOOP); - return true; + return MITIGATION_RESULT_SUCCESS; } - return false; + return MITIGATION_RESULT_SKIPPED; } @Override diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java index 4fea9372971d..ffae5176cebf 100644 --- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java @@ -752,6 +752,70 @@ public class PackageWatchdog { return mPackagesExemptFromImpactLevelThreshold; } + /** + * Indicates that the result of a mitigation executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation} is unknown. + */ + public static final int MITIGATION_RESULT_UNKNOWN = + ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN; + + /** + * Indicates that a mitigation was successfully triggered or executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation}. + */ + public static final int MITIGATION_RESULT_SUCCESS = + ObserverMitigationResult.MITIGATION_RESULT_SUCCESS; + + /** + * Indicates that a mitigation executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation} was skipped. + */ + public static final int MITIGATION_RESULT_SKIPPED = + ObserverMitigationResult.MITIGATION_RESULT_SKIPPED; + + /** + * Indicates that a mitigation executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed, + * but the failure is potentially retryable. + */ + public static final int MITIGATION_RESULT_FAILURE_RETRYABLE = + ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE; + + /** + * Indicates that a mitigation executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or + * {@link PackageHealthObserver#onExecuteBootLoopMitigation} failed, + * and the failure is not retryable. + */ + public static final int MITIGATION_RESULT_FAILURE_NON_RETRYABLE = + ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE; + + /** + * Possible return values of the for mitigations executed during + * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} and + * {@link PackageHealthObserver#onExecuteBootLoopMitigation}. + * @hide + */ + @Retention(SOURCE) + @IntDef(prefix = "MITIGATION_RESULT_", value = { + ObserverMitigationResult.MITIGATION_RESULT_UNKNOWN, + ObserverMitigationResult.MITIGATION_RESULT_SUCCESS, + ObserverMitigationResult.MITIGATION_RESULT_SKIPPED, + ObserverMitigationResult.MITIGATION_RESULT_FAILURE_RETRYABLE, + ObserverMitigationResult.MITIGATION_RESULT_FAILURE_NON_RETRYABLE, + }) + public @interface ObserverMitigationResult { + int MITIGATION_RESULT_UNKNOWN = 0; + int MITIGATION_RESULT_SUCCESS = 1; + int MITIGATION_RESULT_SKIPPED = 2; + int MITIGATION_RESULT_FAILURE_RETRYABLE = 3; + int MITIGATION_RESULT_FAILURE_NON_RETRYABLE = 4; + } + /** Possible severity values of the user impact of a * {@link PackageHealthObserver#onExecuteHealthCheckMitigation}. * @hide @@ -809,16 +873,25 @@ public class PackageWatchdog { int mitigationCount); /** - * Executes mitigation for {@link #onHealthCheckFailed}. + * This would be called after {@link #onHealthCheckFailed}. + * This is called only if current observer returned least impact mitigation for failed + * health check. * * @param versionedPackage the package that is failing. This may be null if a native - * service is crashing. - * @param failureReason the type of failure that is occurring. + * service is crashing. + * @param failureReason the type of failure that is occurring. * @param mitigationCount the number of times mitigation has been called for this package - * (including this time). - * @return {@code true} if action was executed successfully, {@code false} otherwise + * (including this time). + * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful, + * {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be + * retried, + * {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and + * cannot be retried, + * {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown, + * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped. */ - boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage versionedPackage, + @ObserverMitigationResult int onExecuteHealthCheckMitigation( + @Nullable VersionedPackage versionedPackage, @FailureReasons int failureReason, int mitigationCount); @@ -834,12 +907,23 @@ public class PackageWatchdog { } /** - * Executes mitigation for {@link #onBootLoop} + * This would be called after {@link #onBootLoop}. + * This is called only if current observer returned least impact mitigation for fixing + * boot loop. + * * @param mitigationCount the number of times mitigation has been attempted for this * boot loop (including this time). + * + * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful, + * {@link #MITIGATION_RESULT_FAILURE_RETRYABLE} if the mitigation failed but can be + * retried, + * {@link #MITIGATION_RESULT_FAILURE_NON_RETRYABLE} if the mitigation failed and + * cannot be retried, + * {@link #MITIGATION_RESULT_UNKNOWN} if the result of the mitigation is unknown, + * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped. */ - default boolean onExecuteBootLoopMitigation(int mitigationCount) { - return false; + default @ObserverMitigationResult int onExecuteBootLoopMitigation(int mitigationCount) { + return ObserverMitigationResult.MITIGATION_RESULT_SKIPPED; } // TODO(b/120598832): Ensure uniqueness? diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java index 2bb72fb43dff..c6452d31f881 100644 --- a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java @@ -18,6 +18,8 @@ package com.android.server; import static android.provider.DeviceConfig.Properties; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent; import android.annotation.IntDef; @@ -859,10 +861,10 @@ public class RescueParty { } @Override - public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, + public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, @FailureReasons int failureReason, int mitigationCount) { if (isDisabled()) { - return false; + return MITIGATION_RESULT_SKIPPED; } Slog.i(TAG, "Executing remediation." + " failedPackage: " @@ -884,9 +886,9 @@ public class RescueParty { } executeRescueLevel(mContext, failedPackage == null ? null : failedPackage.getPackageName(), level); - return true; + return MITIGATION_RESULT_SUCCESS; } else { - return false; + return MITIGATION_RESULT_SKIPPED; } } @@ -927,9 +929,9 @@ public class RescueParty { } @Override - public boolean onExecuteBootLoopMitigation(int mitigationCount) { + public int onExecuteBootLoopMitigation(int mitigationCount) { if (isDisabled()) { - return false; + return MITIGATION_RESULT_SKIPPED; } boolean mayPerformReboot = !shouldThrottleReboot(); final int level; @@ -944,7 +946,7 @@ public class RescueParty { level = getRescueLevel(mitigationCount, mayPerformReboot); } executeRescueLevel(mContext, /*failedPackage=*/ null, level); - return true; + return MITIGATION_RESULT_SUCCESS; } @Override diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 0692cdbc5e40..04115373e926 100644 --- a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -18,6 +18,8 @@ package com.android.server.rollback; import static android.content.pm.Flags.provideInfoOfApkInApex; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent; import android.annotation.AnyThread; @@ -175,7 +177,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } @Override - public boolean onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, + public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage, @FailureReasons int rollbackReason, int mitigationCount) { Slog.i(TAG, "Executing remediation." + " failedPackage: " @@ -186,7 +188,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason)); - return true; + return MITIGATION_RESULT_SUCCESS; } List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel( @@ -201,7 +203,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } else { if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { mHandler.post(() -> rollbackAll(rollbackReason)); - return true; + return MITIGATION_RESULT_SUCCESS; } RollbackInfo rollback = getAvailableRollback(failedPackage); @@ -213,7 +215,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } // Assume rollbacks executed successfully - return true; + return MITIGATION_RESULT_SUCCESS; } @Override @@ -229,15 +231,15 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve } @Override - public boolean onExecuteBootLoopMitigation(int mitigationCount) { + public int onExecuteBootLoopMitigation(int mitigationCount) { if (Flags.recoverabilityDetection()) { List<RollbackInfo> availableRollbacks = getAvailableRollbacks(); triggerLeastImpactLevelRollback(availableRollbacks, PackageWatchdog.FAILURE_REASON_BOOT_LOOP); - return true; + return MITIGATION_RESULT_SUCCESS; } - return false; + return MITIGATION_RESULT_SKIPPED; } @Override diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp index 0382829b2652..08dd27fd25ce 100644 --- a/packages/SettingsLib/ButtonPreference/Android.bp +++ b/packages/SettingsLib/ButtonPreference/Android.bp @@ -24,4 +24,8 @@ android_library { sdk_version: "system_current", min_sdk_version: "21", + apex_available: [ + "//apex_available:platform", + "com.android.healthfitness", + ], } diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java index 993555e78bea..be711accd542 100644 --- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java +++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java @@ -247,4 +247,28 @@ public class ButtonPreference extends Preference implements GroupSectionDividerM mButton.setLayoutParams(lp); } } + + /** + * Sets the style of the button. + * + * @param type Specifies the button's type, which sets the attribute `buttonPreferenceType`. + * Possible values are: + * <ul> + * <li>0: filled</li> + * <li>1: tonal</li> + * <li>2: outline</li> + * </ul> + * @param size Specifies the button's size, which sets the attribute `buttonPreferenceSize`. + * Possible values are: + * <ul> + * <li>0: normal</li> + * <li>1: large</li> + * <li>2: extra large</li> + * </ul> + */ + public void setButtonStyle(int type, int size) { + int layoutId = ButtonStyle.getLayoutId(type, size); + setLayoutResource(layoutId); + notifyChanged(); + } } diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt index 7aece5185800..3c8d6ed0bf55 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt @@ -38,11 +38,11 @@ abstract class GetPreferenceGraphApiHandler( override suspend fun invoke( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, request: GetPreferenceGraphRequest, ): PreferenceGraphProto { - val builder = PreferenceGraphBuilder.of(application, myUid, callingUid, request) + val builder = PreferenceGraphBuilder.of(application, callingPid, callingUid, request) if (request.screenKeys.isEmpty()) { for (key in PreferenceScreenRegistry.preferenceScreens.keys) { builder.addPreferenceScreenFromRegistry(key) diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt index c8453efb9161..de5731eacfd7 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt @@ -83,14 +83,14 @@ class PreferenceGetterApiHandler( override fun hasPermission( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, request: PreferenceGetterRequest, - ) = permissionChecker.hasPermission(application, myUid, callingUid, request) + ) = permissionChecker.hasPermission(application, callingPid, callingUid, request) override suspend fun invoke( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, request: PreferenceGetterRequest, ): PreferenceGetterResponse { @@ -123,7 +123,7 @@ class PreferenceGetterApiHandler( val preferenceProto = metadata.toProto( application, - myUid, + callingPid, callingUid, screenMetadata, metadata.key == screenMetadata.key, diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt index 606710e6f356..eaa79266b194 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt @@ -22,6 +22,7 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.content.res.Configuration import android.os.Build import android.os.Bundle @@ -65,7 +66,7 @@ private const val TAG = "PreferenceGraphBuilder" class PreferenceGraphBuilder private constructor( private val context: Context, - private val myUid: Int, + private val callingPid: Int, private val callingUid: Int, private val request: GetPreferenceGraphRequest, ) { @@ -81,7 +82,7 @@ private constructor( } } - fun build() = builder.build() + fun build(): PreferenceGraphProto = builder.build() /** * Adds an activity to the graph. @@ -268,16 +269,18 @@ private constructor( metadata: PreferenceMetadata, isRoot: Boolean, ) = - metadata.toProto(context, myUid, callingUid, screenMetadata, isRoot, request.flags).also { - if (metadata is PreferenceScreenMetadata) { - @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata) - } - metadata.intent(context)?.resolveActivity(context.packageManager)?.let { - if (it.packageName == context.packageName) { - add(it.className) + metadata + .toProto(context, callingPid, callingUid, screenMetadata, isRoot, request.flags) + .also { + if (metadata is PreferenceScreenMetadata) { + @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata) + } + metadata.intent(context)?.resolveActivity(context.packageManager)?.let { + if (it.packageName == context.packageName) { + add(it.className) + } } } - } private suspend fun String?.toActionTarget(extras: Bundle?): ActionTarget? { if (this.isNullOrEmpty()) return null @@ -343,16 +346,16 @@ private constructor( companion object { suspend fun of( context: Context, - myUid: Int, + callingPid: Int, callingUid: Int, request: GetPreferenceGraphRequest, - ) = PreferenceGraphBuilder(context, myUid, callingUid, request).also { it.init() } + ) = PreferenceGraphBuilder(context, callingPid, callingUid, request).also { it.init() } } } fun PreferenceMetadata.toProto( context: Context, - myUid: Int, + callingPid: Int, callingUid: Int, screenMetadata: PreferenceScreenMetadata, isRoot: Boolean, @@ -394,7 +397,7 @@ fun PreferenceMetadata.toProto( (!hasAvailable() || available) && (!hasRestricted() || !restricted) && metadata is PersistentPreference<*> && - metadata.getReadPermit(context, myUid, callingUid) == ReadWritePermit.ALLOW + metadata.evalReadPermit(context, callingPid, callingUid) == ReadWritePermit.ALLOW ) { value = preferenceValueProto { when (metadata) { @@ -425,6 +428,20 @@ fun PreferenceMetadata.toProto( } } +/** Evaluates the read permit of a persistent preference. */ +fun <T> PersistentPreference<T>.evalReadPermit( + context: Context, + callingPid: Int, + callingUid: Int, +): Int { + for (permission in getReadPermissions(context)) { + if (context.checkPermission(permission, callingPid, callingUid) != PERMISSION_GRANTED) { + return ReadWritePermit.REQUIRE_APP_PERMISSION + } + } + return getReadPermit(context, callingPid, callingUid) +} + private fun PreferenceMetadata.getTitleTextProto(context: Context, isRoot: Boolean): TextProto? { if (isRoot && this is PreferenceScreenMetadata) { val titleRes = screenTitle diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt index 56b169370e47..d72ba0805db3 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt @@ -17,6 +17,8 @@ package com.android.settingslib.graph import android.app.Application +import android.content.Context +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.os.Bundle import androidx.annotation.IntDef import com.android.settingslib.graph.proto.PreferenceValueProto @@ -99,14 +101,14 @@ class PreferenceSetterApiHandler( override fun hasPermission( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, request: PreferenceSetterRequest, - ) = permissionChecker.hasPermission(application, myUid, callingUid, request) + ) = permissionChecker.hasPermission(application, callingPid, callingUid, request) override suspend fun invoke( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, request: PreferenceSetterRequest, ): Int { @@ -127,7 +129,7 @@ class PreferenceSetterApiHandler( fun <T> PreferenceMetadata.checkWritePermit(value: T): Int { @Suppress("UNCHECKED_CAST") val preference = (this as PersistentPreference<T>) - return when (preference.getWritePermit(application, value, myUid, callingUid)) { + return when (preference.evalWritePermit(application, value, callingPid, callingUid)) { ReadWritePermit.ALLOW -> PreferenceSetterResult.OK ReadWritePermit.DISALLOW -> PreferenceSetterResult.DISALLOW ReadWritePermit.REQUIRE_APP_PERMISSION -> @@ -171,6 +173,21 @@ class PreferenceSetterApiHandler( get() = IntMessageCodec } +/** Evaluates the write permit of a persistent preference. */ +fun <T> PersistentPreference<T>.evalWritePermit( + context: Context, + value: T?, + callingPid: Int, + callingUid: Int, +): Int { + for (permission in getWritePermissions(context)) { + if (context.checkPermission(permission, callingPid, callingUid) != PERMISSION_GRANTED) { + return ReadWritePermit.REQUIRE_APP_PERMISSION + } + } + return getWritePermit(context, value, callingPid, callingUid) +} + /** Message codec for [PreferenceSetterRequest]. */ object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> { override fun encode(data: PreferenceSetterRequest) = diff --git a/packages/SettingsLib/Ipc/README.md b/packages/SettingsLib/Ipc/README.md index ea2c3a1b52db..719d01e1c686 100644 --- a/packages/SettingsLib/Ipc/README.md +++ b/packages/SettingsLib/Ipc/README.md @@ -101,14 +101,14 @@ object EchoApiImpl : ApiHandler<String?, String?>, ApiDescriptor<String?, String?> by EchoApi { override suspend fun invoke( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, request: String?, ): String? = request override fun hasPermission( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, request: String?, ): Boolean = (request?.length ?: 0) <= 5 diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt index 4febd89a7da4..6d746e020243 100644 --- a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt +++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/ApiHandler.kt @@ -62,12 +62,17 @@ fun interface ApiPermissionChecker<R> { * Returns if the request is permitted. * * @param application application context - * @param myUid uid of current process + * @param callingPid pid of peer process * @param callingUid uid of peer process * @param request API request * @return `false` if permission is denied, otherwise `true` */ - fun hasPermission(application: Application, myUid: Int, callingUid: Int, request: R): Boolean + fun hasPermission( + application: Application, + callingPid: Int, + callingUid: Int, + request: R, + ): Boolean companion object { private val ALWAYS_ALLOW = ApiPermissionChecker<Any> { _, _, _, _ -> true } @@ -96,7 +101,7 @@ interface ApiHandler<Request, Response> : */ suspend fun invoke( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, request: Request, ): Response diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerService.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerService.kt index 0bdae38a0a24..7c80b5910f4b 100644 --- a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerService.kt +++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerService.kt @@ -25,7 +25,6 @@ import android.os.IBinder import android.os.Looper import android.os.Message import android.os.Messenger -import android.os.Process import android.util.Log import androidx.annotation.VisibleForTesting import kotlinx.coroutines.CoroutineScope @@ -92,7 +91,6 @@ open class MessengerService( private val apiHandlers: Array<ApiHandler<*, *>>, private val permissionChecker: PermissionChecker, ) : Handler(looper) { - @VisibleForTesting internal val myUid = Process.myUid() val coroutineScope = CoroutineScope(asCoroutineDispatcher().immediate + SupervisorJob()) override fun handleMessage(msg: Message) { @@ -109,18 +107,22 @@ open class MessengerService( } val apiId = msg.what val txnId = msg.arg1 + val callingPid = msg.arg2 val callingUid = msg.sendingUid val data = msg.data // WARNING: never access "msg" beyond this point as it may be recycled by Looper val response = Message.obtain(null, apiId, txnId, ApiServiceException.CODE_OK) try { - if (permissionChecker.check(application, myUid, callingUid)) { + if (permissionChecker.check(application, callingPid, callingUid)) { @Suppress("UNCHECKED_CAST") val apiHandler = findApiHandler(apiId) as? ApiHandler<Any, Any> if (apiHandler != null) { val request = apiHandler.requestCodec.decode(data) - if (apiHandler.hasPermission(application, myUid, callingUid, request)) { - val result = apiHandler.invoke(application, myUid, callingUid, request) + if ( + apiHandler.hasPermission(application, callingPid, callingUid, request) + ) { + val result = + apiHandler.invoke(application, callingPid, callingUid, request) response.data = apiHandler.responseCodec.encode(result) } else { response.arg2 = ApiServiceException.CODE_PERMISSION_DENIED diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt index ef907e17d824..3f7eea5de779 100644 --- a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt +++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/MessengerServiceClient.kt @@ -28,6 +28,7 @@ import android.os.IBinder import android.os.Looper import android.os.Message import android.os.Messenger +import android.os.Process import android.util.Log import androidx.annotation.OpenForTesting import androidx.annotation.VisibleForTesting @@ -190,6 +191,7 @@ constructor( private val metricsLogger: MetricsLogger?, ) : Handler(looper), ServiceConnection { private val clientMessenger = Messenger(this) + internal val myPid = Process.myPid() internal val pendingRequests = ArrayDeque<RequestWrapper<*, *>>() internal var serviceMessenger: Messenger? = null internal open var connectionState: Int = STATE_INIT @@ -364,7 +366,7 @@ constructor( drainPendingRequests() } val message = - obtainMessage(request.apiDescriptor.id, request.txnId, 0).apply { + obtainMessage(request.apiDescriptor.id, request.txnId, myPid).apply { replyTo = clientMessenger } try { diff --git a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/PermissionChecker.kt b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/PermissionChecker.kt index da9c955d5069..6653b663606d 100644 --- a/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/PermissionChecker.kt +++ b/packages/SettingsLib/Ipc/src/com/android/settingslib/ipc/PermissionChecker.kt @@ -18,6 +18,7 @@ package com.android.settingslib.ipc import android.app.Application import android.content.pm.PackageManager +import android.os.Process import androidx.collection.mutableIntIntMapOf /** Checker for permission. */ @@ -26,18 +27,18 @@ fun interface PermissionChecker { * Checks permission. * * @param application application context - * @param myUid uid of current process + * @param callingPid uid of peer process * @param callingUid uid of peer process */ - fun check(application: Application, myUid: Int, callingUid: Int): Boolean + fun check(application: Application, callingPid: Int, callingUid: Int): Boolean } /** Verifies apk signatures as permission check. */ class SignatureChecker : PermissionChecker { private val cache = mutableIntIntMapOf() - override fun check(application: Application, myUid: Int, callingUid: Int): Boolean = + override fun check(application: Application, callingPid: Int, callingUid: Int): Boolean = cache.getOrPut(callingUid) { - application.packageManager.checkSignatures(myUid, callingUid) + application.packageManager.checkSignatures(Process.myUid(), callingUid) } == PackageManager.SIGNATURE_MATCH } diff --git a/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java b/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java index 49f045f4423c..5df617c37945 100644 --- a/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java +++ b/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java @@ -41,7 +41,7 @@ import androidx.preference.PreferenceViewHolder; * xxxxxxx:allowDividerBelow="true" * */ -public class LayoutPreference extends Preference { +public class LayoutPreference extends Preference implements GroupSectionDividerMixin { private final View.OnClickListener mClickListener = v -> performClick(v); private boolean mAllowDividerAbove; diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt index 668f981e215b..d3a731688c24 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt @@ -29,6 +29,7 @@ import com.android.settingslib.datastore.KeyValueStore ReadWritePermit.REQUIRE_USER_AGREEMENT, ) @Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.TYPE) annotation class ReadWritePermit { companion object { /** Allow to read/write value. */ @@ -67,35 +68,46 @@ interface PersistentPreference<T> { fun storage(context: Context): KeyValueStore = PreferenceScreenRegistry.getKeyValueStore(context, this as PreferenceMetadata)!! + /** Returns the required permissions to read preference value. */ + fun getReadPermissions(context: Context): Array<String> = arrayOf() + /** - * Returns if the external application (identified by [callingUid]) has permission to read - * preference value. + * Returns if the external application (identified by [callingPid] and [callingUid]) is + * permitted to read preference value. * * The underlying implementation does NOT need to check common states like isEnabled, - * isRestricted or isAvailable. + * isRestricted, isAvailable or permissions in [getReadPermissions]. The framework will do it + * behind the scene. */ - @ReadWritePermit - fun getReadPermit(context: Context, myUid: Int, callingUid: Int): Int = + fun getReadPermit(context: Context, callingPid: Int, callingUid: Int): @ReadWritePermit Int = PreferenceScreenRegistry.getReadPermit( context, - myUid, + callingPid, callingUid, this as PreferenceMetadata, ) + /** Returns the required permissions to write preference value. */ + fun getWritePermissions(context: Context): Array<String> = arrayOf() + /** - * Returns if the external application (identified by [callingUid]) has permission to write - * preference with given [value]. + * Returns if the external application (identified by [callingPid] and [callingUid]) is + * permitted to write preference with given [value]. * * The underlying implementation does NOT need to check common states like isEnabled, - * isRestricted or isAvailable. + * isRestricted, isAvailable or permissions in [getWritePermissions]. The framework will do it + * behind the scene. */ - @ReadWritePermit - fun getWritePermit(context: Context, value: T?, myUid: Int, callingUid: Int): Int = + fun getWritePermit( + context: Context, + value: T?, + callingPid: Int, + callingUid: Int, + ): @ReadWritePermit Int = PreferenceScreenRegistry.getWritePermit( context, value, - myUid, + callingPid, callingUid, this as PreferenceMetadata, ) diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt index 6646d6c32d15..ff0991023393 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt @@ -37,7 +37,8 @@ object PreferenceScreenRegistry : ReadWritePermitProvider { val preferenceScreens: PreferenceScreenMap get() = preferenceScreensSupplier.get() - private var readWritePermitProvider: ReadWritePermitProvider? = null + private var readWritePermitProvider: ReadWritePermitProvider = + object : ReadWritePermitProvider {} /** Sets the [KeyValueStoreProvider]. */ fun setKeyValueStoreProvider(keyValueStoreProvider: KeyValueStoreProvider) { @@ -77,28 +78,24 @@ object PreferenceScreenRegistry : ReadWritePermitProvider { /** * Sets the provider to check read write permit. Read and write requests are denied by default. */ - fun setReadWritePermitProvider(readWritePermitProvider: ReadWritePermitProvider?) { + fun setReadWritePermitProvider(readWritePermitProvider: ReadWritePermitProvider) { this.readWritePermitProvider = readWritePermitProvider } override fun getReadPermit( context: Context, - myUid: Int, + callingPid: Int, callingUid: Int, preference: PreferenceMetadata, - ) = - readWritePermitProvider?.getReadPermit(context, myUid, callingUid, preference) - ?: ReadWritePermit.DISALLOW + ) = readWritePermitProvider.getReadPermit(context, callingPid, callingUid, preference) override fun getWritePermit( context: Context, value: Any?, - myUid: Int, + callingPid: Int, callingUid: Int, preference: PreferenceMetadata, - ) = - readWritePermitProvider?.getWritePermit(context, value, myUid, callingUid, preference) - ?: ReadWritePermit.DISALLOW + ) = readWritePermitProvider.getWritePermit(context, value, callingPid, callingUid, preference) } /** Provider of [KeyValueStore]. */ @@ -117,41 +114,21 @@ fun interface KeyValueStoreProvider { /** Provider of read and write permit. */ interface ReadWritePermitProvider { - @ReadWritePermit + val defaultReadWritePermit: @ReadWritePermit Int + get() = ReadWritePermit.DISALLOW + fun getReadPermit( context: Context, - myUid: Int, + callingPid: Int, callingUid: Int, preference: PreferenceMetadata, - ): Int + ): @ReadWritePermit Int = defaultReadWritePermit - @ReadWritePermit fun getWritePermit( context: Context, value: Any?, - myUid: Int, + callingPid: Int, callingUid: Int, preference: PreferenceMetadata, - ): Int - - companion object { - @JvmField - val ALLOW_ALL_READ_WRITE = - object : ReadWritePermitProvider { - override fun getReadPermit( - context: Context, - myUid: Int, - callingUid: Int, - preference: PreferenceMetadata, - ) = ReadWritePermit.ALLOW - - override fun getWritePermit( - context: Context, - value: Any?, - myUid: Int, - callingUid: Int, - preference: PreferenceMetadata, - ) = ReadWritePermit.ALLOW - } - } + ): @ReadWritePermit Int = defaultReadWritePermit } diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt index 1823ba641775..ae9642a38778 100644 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt +++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt @@ -33,8 +33,8 @@ internal class PreferenceGraphApi( override fun hasPermission( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, request: GetPreferenceGraphRequest, - ) = permissionChecker.hasPermission(application, myUid, callingUid, request) + ) = permissionChecker.hasPermission(application, callingPid, callingUid, request) } diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt index 265c065e924e..bfaeb42d5a31 100644 --- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt +++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt @@ -16,6 +16,9 @@ package com.android.settingslib.widget +import android.os.Bundle +import android.view.View +import androidx.annotation.CallSuper import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceScreen import androidx.recyclerview.widget.RecyclerView @@ -23,9 +26,18 @@ import androidx.recyclerview.widget.RecyclerView /** Base class for Settings to use PreferenceFragmentCompat */ abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() { + @CallSuper + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (SettingsThemeHelper.isExpressiveTheme(requireContext())) { + // Don't allow any divider in between the preferences in expressive design. + setDivider(null) + } + } + override fun onCreateAdapter(preferenceScreen: PreferenceScreen): RecyclerView.Adapter<*> { if (SettingsThemeHelper.isExpressiveTheme(requireContext())) return SettingsPreferenceGroupAdapter(preferenceScreen) return super.onCreateAdapter(preferenceScreen) } -}
\ No newline at end of file +} diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index cf452314163f..c9aac91f1320 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -21,6 +21,7 @@ import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_ import android.annotation.IntDef; import android.annotation.MainThread; +import android.app.ActivityManager; import android.app.AppGlobals; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -1643,7 +1644,7 @@ public class AccessPoint implements Comparable<AccessPoint> { CharSequence appLabel = ""; ApplicationInfo appInfo = null; try { - int userId = UserHandle.getUserId(UserHandle.USER_CURRENT); + int userId = ActivityManager.getCurrentUser(); appInfo = packageManager.getApplicationInfoAsUser(packageName, 0 /* flags */, userId); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Failed to get app info", e); diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index f380e7f7e7a6..81358ca168d0 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -76,7 +76,6 @@ java_genrule { tools: ["soong_zip"], cmd: "mkdir -p $(genDir)/META-INF/services/ && touch $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" + "echo -e 'org.robolectric.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + - "echo -e 'org.robolectric.shadows.multidex.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + "echo -e 'org.robolectric.shadows.httpclient.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + //"echo -e 'com.android.settings.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + "echo -e 'com.android.settingslib.testutils.shadow.Shadows' >> $(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider && " + diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 731cb7269037..18bebd40b03a 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -52,6 +52,7 @@ public class SecureSettings { Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, + Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, Settings.Secure.CONTRAST_LEVEL, Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 039832cee6f2..1d7608d7d4d0 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -88,6 +88,9 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put( + Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, + new DiscreteValueValidator(new String[] {"0", "1", "2"})); VALIDATORS.put(Secure.CONTRAST_LEVEL, new InclusiveFloatRangeValidator(-1f, 1f)); VALIDATORS.put( Secure.ACCESSIBILITY_CAPTIONING_PRESET, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 6f40e2760386..924c151a99a0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -25,6 +25,8 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.hardware.display.ColorDisplayManager; import android.icu.util.ULocale; @@ -32,6 +34,7 @@ import android.media.AudioManager; import android.media.RingtoneManager; import android.media.Utils; import android.net.Uri; +import android.os.Build; import android.os.LocaleList; import android.os.RemoteException; import android.os.ServiceManager; @@ -50,6 +53,7 @@ import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManage import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Set; @@ -69,6 +73,9 @@ public class SettingsHelper { private static final int LONG_PRESS_POWER_FOR_ASSISTANT = 5; /** See frameworks/base/core/res/res/values/config.xml#config_keyChordPowerVolumeUp **/ private static final int KEY_CHORD_POWER_VOLUME_UP_GLOBAL_ACTIONS = 2; + @VisibleForTesting + static final String HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION = + "com.android.settings.accessibility.ACTION_HIGH_CONTRAST_TEXT_RESTORED"; // Error messages for logging metrics. private static final String ERROR_REMOTE_EXCEPTION_SETTING_LOCALE_DATA = @@ -94,21 +101,26 @@ public class SettingsHelper { */ private static final ArraySet<String> sBroadcastOnRestore; private static final ArraySet<String> sBroadcastOnRestoreSystemUI; + private static final ArraySet<String> sBroadcastOnRestoreAccessibility; static { - sBroadcastOnRestore = new ArraySet<String>(12); + sBroadcastOnRestore = new ArraySet<>(7); sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS); - sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); sBroadcastOnRestore.add(Settings.Global.BLUETOOTH_ON); sBroadcastOnRestore.add(Settings.Secure.UI_NIGHT_MODE); sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_START_TIME); sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME); - sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); - sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); - sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS); - sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); sBroadcastOnRestore.add(Settings.Secure.SCREEN_RESOLUTION_MODE); - sBroadcastOnRestoreSystemUI = new ArraySet<String>(2); + + sBroadcastOnRestoreAccessibility = new ArraySet<>(5); + sBroadcastOnRestoreAccessibility.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + sBroadcastOnRestoreAccessibility.add( + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); + sBroadcastOnRestoreAccessibility.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); + sBroadcastOnRestoreAccessibility.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS); + sBroadcastOnRestoreAccessibility.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); + + sBroadcastOnRestoreSystemUI = new ArraySet<>(2); sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES); sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES); } @@ -181,6 +193,7 @@ public class SettingsHelper { String oldValue = null; boolean sendBroadcast = false; boolean sendBroadcastSystemUI = false; + boolean sendBroadcastAccessibility = false; final SettingsLookup table; if (destination.equals(Settings.Secure.CONTENT_URI)) { @@ -193,6 +206,7 @@ public class SettingsHelper { sendBroadcast = sBroadcastOnRestore.contains(name); sendBroadcastSystemUI = sBroadcastOnRestoreSystemUI.contains(name); + sendBroadcastAccessibility = sBroadcastOnRestoreAccessibility.contains(name); if (sendBroadcast) { // TODO: http://b/22388012 @@ -202,6 +216,10 @@ public class SettingsHelper { // It would probably be correct to do it for the ones sent to the system, but consumers // may be depending on the current behavior. oldValue = table.lookup(cr, name, context.getUserId()); + } else if (sendBroadcastAccessibility) { + int userId = android.view.accessibility.Flags.restoreA11ySecureSettingsOnHsumDevice() + ? context.getUserId() : UserHandle.USER_SYSTEM; + oldValue = table.lookup(cr, name, userId); } try { @@ -244,14 +262,27 @@ public class SettingsHelper { } else if (Settings.System.ACCELEROMETER_ROTATION.equals(name) && shouldSkipAutoRotateRestore()) { return; - } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(name)) { - // Don't write it to setting. Let the broadcast receiver in - // AccessibilityManagerService handle restore/merging logic. - return; - } else if (Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE.equals(name)) { + } else if (shouldSkipAndLetBroadcastHandlesRestoreLogic(name)) { // Don't write it to setting. Let the broadcast receiver in // AccessibilityManagerService handle restore/merging logic. return; + } else if (com.android.graphics.hwui.flags.Flags.highContrastTextSmallTextRect() + && Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED.equals(name)) { + final boolean currentlyEnabled = Settings.Secure.getInt( + context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0) == 1; + final boolean enabledInRestore = value != null && Integer.parseInt(value) == 1; + + // If restoring from Android 15 or earlier and the user didn't already enable HCT + // on this new device, then don't restore and trigger custom migration logic. + final boolean needsCustomMigration = !currentlyEnabled + && restoredFromSdkInt < Build.VERSION_CODES.BAKLAVA + && enabledInRestore; + if (needsCustomMigration) { + migrateHighContrastText(context); + return; + } + // fall through to the ordinary write to settings } // Default case: write the restored value to settings @@ -263,12 +294,13 @@ public class SettingsHelper { // If we fail to apply the setting, by definition nothing happened sendBroadcast = false; sendBroadcastSystemUI = false; + sendBroadcastAccessibility = false; Log.e(TAG, "Failed to restore setting name: " + name + " + value: " + value, e); } finally { // If this was an element of interest, send the "we just restored it" // broadcast with the historical value now that the new value has // been committed and observers kicked off. - if (sendBroadcast || sendBroadcastSystemUI) { + if (sendBroadcast || sendBroadcastSystemUI || sendBroadcastAccessibility) { Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) .putExtra(Intent.EXTRA_SETTING_NAME, name) @@ -285,6 +317,13 @@ public class SettingsHelper { context.getString(com.android.internal.R.string.config_systemUi)); context.sendBroadcastAsUser(intent, context.getUser(), null); } + if (sendBroadcastAccessibility) { + UserHandle userHandle = + android.view.accessibility.Flags.restoreA11ySecureSettingsOnHsumDevice() + ? context.getUser() : UserHandle.SYSTEM; + intent.setPackage("android"); + context.sendBroadcastAsUser(intent, userHandle, null); + } } } } @@ -450,6 +489,19 @@ public class SettingsHelper { } } + private boolean shouldSkipAndLetBroadcastHandlesRestoreLogic(String settingName) { + boolean restoreHandledByBroadcast = Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals( + settingName) + || Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE.equals(settingName); + if (android.view.accessibility.Flags.restoreA11ySecureSettingsOnHsumDevice()) { + restoreHandledByBroadcast |= + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS.equals(settingName) + || Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(settingName); + } + + return restoreHandledByBroadcast; + } + private void setAutoRestore(boolean enabled) { try { IBackupManager bm = IBackupManager.Stub.asInterface( @@ -529,6 +581,30 @@ public class SettingsHelper { } } + private static void migrateHighContrastText(Context context) { + final Intent intent = new Intent(HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION) + .setPackage(getSettingsAppPackage(context)); + context.sendBroadcastAsUser(intent, context.getUser(), null); + } + + /** + * Returns the System Settings application's package name + */ + private static String getSettingsAppPackage(Context context) { + String settingsAppPackage = null; + PackageManager packageManager = context.getPackageManager(); + if (packageManager != null) { + List<ResolveInfo> results = packageManager.queryIntentActivities( + new Intent(Settings.ACTION_SETTINGS), + PackageManager.MATCH_SYSTEM_ONLY); + if (!results.isEmpty()) { + settingsAppPackage = results.getFirst().activityInfo.applicationInfo.packageName; + } + } + + return !TextUtils.isEmpty(settingsAppPackage) ? settingsAppPackage : "com.android.settings"; + } + /* package */ byte[] getLocaleData() { Configuration conf = mContext.getResources().getConfiguration(); return conf.getLocales().toLanguageTags().getBytes(); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 37eda3ebf9a8..661a09553914 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1778,6 +1778,9 @@ class SettingsProtoDumpUtil { Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, SecureSettingsProto.Accessibility.HIGH_TEXT_CONTRAST_ENABLED); dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, + SecureSettingsProto.Accessibility.HCT_RECT_PROMPT_STATUS); + dumpSetting(s, p, Settings.Secure.CONTRAST_LEVEL, SecureSettingsProto.Accessibility.CONTRAST_LEVEL); dumpSetting(s, p, @@ -2517,6 +2520,13 @@ class SettingsProtoDumpUtil { Settings.Secure.RTT_CALLING_MODE, SecureSettingsProto.RTT_CALLING_MODE); + final long screenoffudfpsenabledToken = p.start( + SecureSettingsProto.SCREEN_OFF_UDFPS_ENABLED); + dumpSetting(s, p, + Settings.Secure.SCREEN_OFF_UNLOCK_UDFPS_ENABLED, + SecureSettingsProto.SCREEN_OFF_UDFPS_ENABLED); + p.end(screenoffudfpsenabledToken); + final long screensaverToken = p.start(SecureSettingsProto.SCREENSAVER); dumpSetting(s, p, Settings.Secure.SCREENSAVER_ENABLED, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java index 048d93b09967..62c03ddc42b9 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java @@ -26,18 +26,22 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.SettingsStringUtil; +import android.view.accessibility.Flags; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.test.BroadcastInterceptingContext; +import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; import java.util.concurrent.ExecutionException; @@ -48,18 +52,100 @@ import java.util.concurrent.ExecutionException; */ @RunWith(AndroidJUnit4.class) public class SettingsHelperRestoreTest { - + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + public final BroadcastInterceptingContext mInterceptingContext = + new BroadcastInterceptingContext( + InstrumentationRegistry.getInstrumentation().getContext()); private static final float FLOAT_TOLERANCE = 0.01f; - - private Context mContext; private ContentResolver mContentResolver; private SettingsHelper mSettingsHelper; @Before public void setUp() { - mContext = InstrumentationRegistry.getContext(); - mContentResolver = mContext.getContentResolver(); - mSettingsHelper = new SettingsHelper(mContext); + mContentResolver = mInterceptingContext.getContentResolver(); + mSettingsHelper = new SettingsHelper(mInterceptingContext); + } + + @After + public void cleanUp() { + setDefaultAccessibilityDisplayMagnificationScale(); + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0); + Settings.Secure.putString(mContentResolver, Settings.Secure.ACCESSIBILITY_QS_TARGETS, null); + Settings.Secure.putString(mContentResolver, + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, null); + Settings.Secure.putString(mContentResolver, + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null); + } + + @Test + public void restoreHighTextContrastEnabled_currentlyEnabled_enableInRestoredFromVanilla_dontSendNotification_hctKeepsEnabled() + throws ExecutionException, InterruptedException { + BroadcastInterceptingContext.FutureIntent futureIntent = + mInterceptingContext.nextBroadcastIntent( + SettingsHelper.HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION); + String settingName = Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED; + Settings.Secure.putInt(mContentResolver, settingName, 1); + + mSettingsHelper.restoreValue( + mInterceptingContext, + mContentResolver, + new ContentValues(2), + Settings.Secure.getUriFor(settingName), + settingName, + String.valueOf(1), + Build.VERSION_CODES.VANILLA_ICE_CREAM); + + futureIntent.assertNotReceived(); + assertThat(Settings.Secure.getInt(mContentResolver, settingName, 0)).isEqualTo(1); + } + + @EnableFlags(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + @Test + public void restoreHighTextContrastEnabled_currentlyDisabled_enableInRestoredFromVanilla_sendNotification_hctKeepsDisabled() + throws ExecutionException, InterruptedException { + BroadcastInterceptingContext.FutureIntent futureIntent = + mInterceptingContext.nextBroadcastIntent( + SettingsHelper.HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION); + String settingName = Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED; + Settings.Secure.putInt(mContentResolver, settingName, 0); + + mSettingsHelper.restoreValue( + mInterceptingContext, + mContentResolver, + new ContentValues(2), + Settings.Secure.getUriFor(settingName), + settingName, + String.valueOf(1), + Build.VERSION_CODES.VANILLA_ICE_CREAM); + + Intent intentReceived = futureIntent.get(); + assertThat(intentReceived).isNotNull(); + assertThat(intentReceived.getPackage()).isNotNull(); + assertThat(Settings.Secure.getInt(mContentResolver, settingName, 0)).isEqualTo(0); + } + + @Test + public void restoreHighTextContrastEnabled_currentlyDisabled_enableInRestoredFromAfterVanilla_dontSendNotification_hctShouldEnabled() + throws ExecutionException, InterruptedException { + BroadcastInterceptingContext.FutureIntent futureIntent = + mInterceptingContext.nextBroadcastIntent( + SettingsHelper.HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION); + String settingName = Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED; + Settings.Secure.putInt(mContentResolver, settingName, 0); + + mSettingsHelper.restoreValue( + mInterceptingContext, + mContentResolver, + new ContentValues(2), + Settings.Secure.getUriFor(settingName), + settingName, + String.valueOf(1), + Build.VERSION_CODES.BAKLAVA); + + futureIntent.assertNotReceived(); + assertThat(Settings.Secure.getInt(mContentResolver, settingName, 0)).isEqualTo(1); } /** Tests for {@link Settings.Secure#ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}. */ @@ -76,7 +162,7 @@ public class SettingsHelperRestoreTest { Settings.Secure.putFloat(mContentResolver, settingName, configuredSettingValue); mSettingsHelper.restoreValue( - mContext, + mInterceptingContext, mContentResolver, new ContentValues(2), Settings.Secure.getUriFor(settingName), @@ -99,7 +185,7 @@ public class SettingsHelperRestoreTest { float restoreSettingValue = defaultSettingValue + 0.5f; mSettingsHelper.restoreValue( - Mockito.mock(Context.class), + mInterceptingContext, mContentResolver, new ContentValues(2), Settings.Secure.getUriFor(settingName), @@ -121,7 +207,7 @@ public class SettingsHelperRestoreTest { */ private float setDefaultAccessibilityDisplayMagnificationScale() { float defaultSettingValue = - mContext.getResources() + mInterceptingContext.getResources() .getFraction( R.fraction.def_accessibility_display_magnification_scale, 1, 1); Settings.Secure.putFloat( @@ -142,7 +228,7 @@ public class SettingsHelperRestoreTest { Settings.Secure.putInt(mContentResolver, settingName, configuredSettingValue); mSettingsHelper.restoreValue( - Mockito.mock(Context.class), + mInterceptingContext, mContentResolver, new ContentValues(2), Settings.Secure.getUriFor(settingName), @@ -164,7 +250,7 @@ public class SettingsHelperRestoreTest { int restoreSettingValue = 1; mSettingsHelper.restoreValue( - Mockito.mock(Context.class), + mInterceptingContext, mContentResolver, new ContentValues(2), Settings.Secure.getUriFor(settingName), @@ -178,17 +264,15 @@ public class SettingsHelperRestoreTest { @Test public void restoreAccessibilityQsTargets_broadcastSent() throws ExecutionException, InterruptedException { - BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext( - mContext); final String settingName = Settings.Secure.ACCESSIBILITY_QS_TARGETS; final String restoreSettingValue = "com.android.server.accessibility/ColorInversion" + SettingsStringUtil.DELIMITER + "com.android.server.accessibility/ColorCorrectionTile"; BroadcastInterceptingContext.FutureIntent futureIntent = - interceptingContext.nextBroadcastIntent(Intent.ACTION_SETTING_RESTORED); + mInterceptingContext.nextBroadcastIntent(Intent.ACTION_SETTING_RESTORED); mSettingsHelper.restoreValue( - interceptingContext, + mInterceptingContext, mContentResolver, new ContentValues(2), Settings.Secure.getUriFor(settingName), @@ -207,15 +291,13 @@ public class SettingsHelperRestoreTest { @Test public void restoreAccessibilityShortcutTargetService_broadcastSent() throws ExecutionException, InterruptedException { - BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext( - mContext); final String settingName = Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; final String restoredValue = "com.android.a11y/Service"; BroadcastInterceptingContext.FutureIntent futureIntent = - interceptingContext.nextBroadcastIntent(Intent.ACTION_SETTING_RESTORED); + mInterceptingContext.nextBroadcastIntent(Intent.ACTION_SETTING_RESTORED); mSettingsHelper.restoreValue( - interceptingContext, + mInterceptingContext, mContentResolver, new ContentValues(2), Settings.Secure.getUriFor(settingName), @@ -230,4 +312,32 @@ public class SettingsHelperRestoreTest { Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, /* defaultValue= */ 0)) .isEqualTo(Build.VERSION.SDK_INT); } + + @EnableFlags(Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + @Test + public void restoreAccessibilityShortcutTargets_broadcastSent() + throws ExecutionException, InterruptedException { + final String settingName = Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS; + final String restoreSettingValue = "com.android.server.accessibility/ColorInversion" + + SettingsStringUtil.DELIMITER + + "com.android.server.accessibility/ColorCorrectionTile"; + BroadcastInterceptingContext.FutureIntent futureIntent = + mInterceptingContext.nextBroadcastIntent(Intent.ACTION_SETTING_RESTORED); + + mSettingsHelper.restoreValue( + mInterceptingContext, + mContentResolver, + new ContentValues(2), + Settings.Secure.getUriFor(settingName), + settingName, + restoreSettingValue, + Build.VERSION.SDK_INT); + + Intent intentReceived = futureIntent.get(); + assertThat(intentReceived.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)) + .isEqualTo(restoreSettingValue); + assertThat(intentReceived.getIntExtra( + Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, /* defaultValue= */ 0)) + .isEqualTo(Build.VERSION.SDK_INT); + } } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java index 9cce43160b52..119b2870b622 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java @@ -16,7 +16,7 @@ package com.android.providers.settings; -import static android.provider.Settings.Secure.ACCESSIBILITY_ENABLED; +import static android.provider.Settings.Secure.CONTENT_CAPTURE_ENABLED; import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS; import static android.provider.Settings.System.RINGTONE; @@ -67,7 +67,7 @@ public class SettingsProviderMultiUsersTest { private static final String SPACE_SYSTEM = "system"; private static final String SPACE_SECURE = "secure"; - private static final String CLONE_TO_MANAGED_PROFILE_SETTING = ACCESSIBILITY_ENABLED; + private static final String CLONE_TO_MANAGED_PROFILE_SETTING = CONTENT_CAPTURE_ENABLED; private static final String CLONE_FROM_PARENT_SETTINGS = RINGTONE; private static final String SYNC_FROM_PARENT_SETTINGS = SYNC_PARENT_SOUNDS; diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 3d250fd82473..0600fb3abd6f 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -85,6 +85,7 @@ filegroup { filegroup { name: "SystemUI-tests-broken-robofiles-run", srcs: [ + "tests/src/**/systemui/dreams/touch/CommunalTouchHandlerTest.java", "tests/src/**/systemui/shade/NotificationShadeWindowViewControllerTest.kt", "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt", "tests/src/**/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt", @@ -418,6 +419,9 @@ android_library { "androidx.slice_slice-view", ], manifest: "AndroidManifest-res.xml", + flags_packages: [ + "com_android_systemui_flags", + ], } android_library { diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig index e3f5378175d2..9692aa5d1a4c 100644 --- a/packages/SystemUI/aconfig/biometrics_framework.aconfig +++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig @@ -4,16 +4,6 @@ container: "system" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. flag { - name: "bp_icon_a11y" - namespace: "biometrics_framework" - description: "Fixes biometric prompt icon not working as button with a11y" - bug: "359423579" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "cont_auth_plugin" namespace: "biometrics_framework" description: "Plugin and related API hooks for contextual auth plugins" diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 755b4542b759..c4d13ba6effa 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -265,14 +265,6 @@ flag { } flag { - name: "keyguard_bottom_area_refactor" - namespace: "systemui" - description: "Bottom area of keyguard refactor move into KeyguardRootView. Includes " - "lock icon and others." - bug: "290652751" -} - -flag { name: "device_entry_udfps_refactor" namespace: "systemui" description: "Refactoring device entry UDFPS icon to use modern architecture and " @@ -504,14 +496,14 @@ flag { } flag { - name: "status_bar_notification_chips_test" + name: "promote_notifications_automatically" namespace: "systemui" - description: "Flag to enable certain features that let us test the status bar notification " - "chips with teamfooders. This flag should *never* be released to trunkfood or nextfood." - bug: "361346412" + description: "Flag to automatically turn certain notifications into promoted notifications so " + " we can test promoted notifications with teamfooders. This flag should *never* be released " + "to trunkfood or nextfood." + bug: "367705002" } - flag { name: "compose_bouncer" namespace: "systemui" @@ -1237,6 +1229,14 @@ flag { } flag { + name: "glanceable_hub_v2_resources" + namespace: "systemui" + description: "Read only flag for rolling out glanceable hub v2 resource values" + bug: "375689917" + is_fixed_read_only: true +} + +flag { name: "dream_overlay_updated_font" namespace: "systemui" description: "Flag to enable updated font settings for dream overlay" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index 41a00f5237f7..b0c7ac09551a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -17,7 +17,6 @@ package com.android.systemui.animation import android.app.ActivityManager -import android.app.ActivityOptions import android.app.ActivityTaskManager import android.app.PendingIntent import android.app.TaskInfo @@ -292,7 +291,7 @@ constructor( ?: throw IllegalStateException( "ActivityTransitionAnimator.callback must be set before using this animator" ) - val runner = createRunner(controller) + val runner = createEphemeralRunner(controller) val runnerDelegate = runner.delegate val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen @@ -416,7 +415,7 @@ constructor( var cleanUpRunnable: Runnable? = null val returnRunner = - createRunner( + createEphemeralRunner( object : DelegateTransitionAnimatorController(launchController) { override val isLaunching = false @@ -458,11 +457,7 @@ constructor( "${launchController.transitionCookie}_returnTransition", ) - transitionRegister?.register( - filter, - transition, - includeTakeover = longLivedReturnAnimationsEnabled(), - ) + transitionRegister?.register(filter, transition, includeTakeover = false) cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) } } @@ -476,12 +471,14 @@ constructor( listeners.remove(listener) } - /** Create a new animation [Runner] controlled by [controller]. */ + /** + * Create a new animation [Runner] controlled by [controller]. + * + * This method must only be used for ephemeral (launch or return) transitions. Otherwise, use + * [createLongLivedRunner]. + */ @VisibleForTesting - @JvmOverloads - fun createRunner(controller: Controller, longLived: Boolean = false): Runner { - if (longLived) assertLongLivedReturnAnimations() - + fun createEphemeralRunner(controller: Controller): Runner { // Make sure we use the modified timings when animating a dialog into an app. val transitionAnimator = if (controller.isDialogLaunch) { @@ -490,7 +487,22 @@ constructor( transitionAnimator } - return Runner(controller, callback!!, transitionAnimator, lifecycleListener, longLived) + return Runner(controller, callback!!, transitionAnimator, lifecycleListener) + } + + /** + * Create a new animation [Runner] controlled by the [Controller] that [controllerFactory] can + * create based on [forLaunch]. + * + * This method must only be used for long-lived registrations. Otherwise, use + * [createEphemeralRunner]. + */ + @VisibleForTesting + fun createLongLivedRunner(controllerFactory: ControllerFactory, forLaunch: Boolean): Runner { + assertLongLivedReturnAnimations() + return Runner(callback!!, transitionAnimator, lifecycleListener) { + controllerFactory.createController(forLaunch) + } } interface PendingIntentStarter { @@ -537,6 +549,23 @@ constructor( } /** + * A factory used to create instances of [Controller] linked to a specific cookie [cookie] and + * [component]. + */ + abstract class ControllerFactory( + val cookie: TransitionCookie, + val component: ComponentName?, + val launchCujType: Int? = null, + val returnCujType: Int? = null, + ) { + /** + * Creates a [Controller] for launching or returning from the activity linked to [cookie] + * and [component]. + */ + abstract fun createController(forLaunch: Boolean): Controller + } + + /** * A controller that takes care of applying the animation to an expanding view. * * Note that all callbacks (onXXX methods) are all called on the main thread. @@ -656,13 +685,13 @@ constructor( } /** - * Registers [controller] as a long-lived transition handler for launch and return animations. + * Registers [controllerFactory] as a long-lived transition handler for launch and return + * animations. * - * The [controller] will only be used for transitions matching the [TransitionCookie] defined - * within it, or the [ComponentName] if the cookie matching fails. Both fields are mandatory for - * this registration. + * The [Controller]s created by [controllerFactory] will only be used for transitions matching + * the [cookie], or the [ComponentName] defined within it if the cookie matching fails. */ - fun register(controller: Controller) { + fun register(cookie: TransitionCookie, controllerFactory: ControllerFactory) { assertLongLivedReturnAnimations() if (transitionRegister == null) { @@ -672,13 +701,8 @@ constructor( ) } - val cookie = - controller.transitionCookie - ?: throw IllegalStateException( - "A cookie must be defined in order to use long-lived animations" - ) val component = - controller.component + controllerFactory.component ?: throw IllegalStateException( "A component must be defined in order to use long-lived animations" ) @@ -699,15 +723,11 @@ constructor( } val launchRemoteTransition = RemoteTransition( - OriginTransition(createRunner(controller, longLived = true)), + OriginTransition(createLongLivedRunner(controllerFactory, forLaunch = true)), "${cookie}_launchTransition", ) transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true) - val returnController = - object : Controller by controller { - override val isLaunching: Boolean = false - } // Cross-task close transitions should not use this animation, so we only register it for // when the opening window is Launcher. val returnFilter = @@ -727,7 +747,7 @@ constructor( } val returnRemoteTransition = RemoteTransition( - OriginTransition(createRunner(returnController, longLived = true)), + OriginTransition(createLongLivedRunner(controllerFactory, forLaunch = false)), "${cookie}_returnTransition", ) transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true) @@ -918,32 +938,61 @@ constructor( } @VisibleForTesting - inner class Runner( + inner class Runner + private constructor( /** * This can hold a reference to a view, so it needs to be cleaned up and can't be held on to - * forever when ![longLived]. + * forever. In case of a long-lived [Runner], this must be null and [controllerFactory] must + * be defined instead. */ private var controller: Controller?, + /** + * Reusable factory to generate single-use controllers. In case of an ephemeral [Runner], + * this must be null and [controller] must be defined instead. + */ + private val controllerFactory: (() -> Controller)?, private val callback: Callback, /** The animator to use to animate the window transition. */ private val transitionAnimator: TransitionAnimator, /** Listener for animation lifecycle events. */ - private val listener: Listener? = null, - /** - * Whether the internal should be kept around after execution for later usage. IMPORTANT: - * should always be false if this [Runner] is to be used directly with [ActivityOptions] - * (i.e. for ephemeral launches), or the controller will leak its view. - */ - private val longLived: Boolean = false, + private val listener: Listener?, ) : IRemoteAnimationRunner.Stub() { + constructor( + controller: Controller, + callback: Callback, + transitionAnimator: TransitionAnimator, + listener: Listener? = null, + ) : this( + controller = controller, + controllerFactory = null, + callback = callback, + transitionAnimator = transitionAnimator, + listener = listener, + ) + + constructor( + callback: Callback, + transitionAnimator: TransitionAnimator, + listener: Listener? = null, + controllerFactory: () -> Controller, + ) : this( + controller = null, + controllerFactory = controllerFactory, + callback = callback, + transitionAnimator = transitionAnimator, + listener = listener, + ) + // This is being passed across IPC boundaries and cycles (through PendingIntentRecords, // etc.) are possible. So we need to make sure we drop any references that might // transitively cause leaks when we're done with animation. @VisibleForTesting var delegate: AnimationDelegate? init { + assert((controller != null).xor(controllerFactory != null)) + delegate = null - if (!longLived) { + if (controller != null) { // Ephemeral launches bundle the runner with the launch request (instead of being // registered ahead of time for later use). This means that there could be a timeout // between creation and invocation, so the delegate needs to exist from the @@ -1021,17 +1070,21 @@ constructor( @AnyThread private fun maybeSetUp() { - if (!longLived || delegate != null) return + if (controllerFactory == null || delegate != null) return createDelegate() } @AnyThread private fun createDelegate() { - if (controller == null) return + var controller = controller + val factory = controllerFactory + if (controller == null && factory == null) return + + controller = controller ?: factory!!.invoke() delegate = AnimationDelegate( mainExecutor, - controller!!, + controller, callback, DelegatingAnimationCompletionListener(listener, this::dispose), transitionAnimator, @@ -1041,13 +1094,12 @@ constructor( @AnyThread fun dispose() { - // Drop references to animation controller once we're done with the animation - // to avoid leaking. + // Drop references to animation controller once we're done with the animation to avoid + // leaking in case of ephemeral launches. When long-lived, [controllerFactory] will + // still be around to create new controllers. mainExecutor.execute { delegate = null - // When long lived, the same Runner can be used more than once. In this case we need - // to keep the controller around so we can rebuild the delegate on demand. - if (!longLived) controller = null + controller = null } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index a17a1d46554f..0a0003ee9a8a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -19,17 +19,20 @@ package com.android.systemui.communal.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.android.compose.animation.scene.SceneScope +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.compose.section.CommunalPopupSection @@ -39,8 +42,11 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialogFactory import javax.inject.Inject +import kotlin.math.min +import kotlin.math.roundToInt /** Renders the content of the glanceable hub. */ class CommunalContent @@ -48,6 +54,7 @@ class CommunalContent constructor( private val viewModel: CommunalViewModel, private val interactionHandler: SmartspaceInteractionHandler, + private val communalSettingsInteractor: CommunalSettingsInteractor, private val dialogFactory: SystemUIDialogFactory, private val lockSection: LockSection, private val bottomAreaSection: BottomAreaSection, @@ -77,11 +84,20 @@ constructor( sceneScope = this@Content, ) } - with(lockSection) { - LockIcon( - overrideColor = MaterialTheme.colorScheme.onPrimaryContainer, + if (communalSettingsInteractor.isV2FlagEnabled()) { + Icon( + painter = painterResource(id = R.drawable.ic_lock), + contentDescription = null, + tint = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.element(Communal.Elements.LockIcon), ) + } else { + with(lockSection) { + LockIcon( + overrideColor = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.element(Communal.Elements.LockIcon), + ) + } } with(bottomAreaSection) { IndicationArea( @@ -98,14 +114,42 @@ constructor( val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0) - val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) + val lockIconPlaceable = + if (communalSettingsInteractor.isV2FlagEnabled()) { + val lockIconSizeInt = lockIconSize.roundToPx() + lockIconMeasurable.measure( + Constraints.fixed(width = lockIconSizeInt, height = lockIconSizeInt) + ) + } else { + lockIconMeasurable.measure(noMinConstraints) + } val lockIconBounds = - IntRect( - left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], - top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], - right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], - bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], - ) + if (communalSettingsInteractor.isV2FlagEnabled()) { + val lockIconDistanceFromBottom = + min( + (constraints.maxHeight * lockIconPercentDistanceFromBottom) + .roundToInt(), + lockIconMinDistanceFromBottom.roundToPx(), + ) + val x = constraints.maxWidth / 2 - lockIconPlaceable.width / 2 + val y = + constraints.maxHeight - + lockIconDistanceFromBottom - + lockIconPlaceable.height + IntRect( + left = x, + top = y, + right = x + lockIconPlaceable.width, + bottom = y + lockIconPlaceable.height, + ) + } else { + IntRect( + left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], + top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], + right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], + bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], + ) + } val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints) @@ -129,12 +173,17 @@ constructor( val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height bottomAreaPlaceable.place(x = 0, y = bottomAreaTop) + + val screensaverButtonPaddingInt = screensaverButtonPadding.roundToPx() screensaverButtonPlaceable?.place( x = constraints.maxWidth - screensaverButtonSizeInt - - Dimensions.ItemSpacing.roundToPx(), - y = lockIconBounds.top, + screensaverButtonPaddingInt, + y = + constraints.maxHeight - + screensaverButtonSizeInt - + screensaverButtonPaddingInt, ) } } @@ -142,6 +191,12 @@ constructor( } companion object { - val screensaverButtonSize: Dp = 64.dp + private val screensaverButtonSize: Dp = 64.dp + private val screensaverButtonPadding: Dp = 24.dp + // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area + // position are sorted. + private val lockIconSize: Dp = 54.dp + private val lockIconPercentDistanceFromBottom = 0.1f + private val lockIconMinDistanceFromBottom = 70.dp } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index d5eaf829b746..5dbedc7045e4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -1020,7 +1020,7 @@ private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunal text = titleForEmptyStateCTA, style = MaterialTheme.typography.displaySmall, textAlign = TextAlign.Center, - color = colors.primary, + color = colors.onPrimary, modifier = Modifier.focusable().semantics(mergeDescendants = true) { contentDescription = titleForEmptyStateCTA @@ -1398,6 +1398,7 @@ private fun WidgetContent( val shrinkWidgetLabel = stringResource(R.string.accessibility_action_label_shrink_widget) val expandWidgetLabel = stringResource(R.string.accessibility_action_label_expand_widget) + val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false) val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle() val selectedIndex = selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } } @@ -1511,7 +1512,8 @@ private fun WidgetContent( ) { with(widgetSection) { Widget( - viewModel = viewModel, + isFocusable = isFocusable, + openWidgetEditor = { viewModel.onOpenWidgetEditor() }, model = model, size = size, modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index 105e8dadfafb..7956d0293a1d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -159,6 +159,7 @@ constructor( with(lockSection) { LockIcon() } // Aligned to bottom and constrained to below the lock icon. + // TODO("b/383588832") change this away from "keyguard_bottom_area" Column(modifier = Modifier.fillMaxWidth().sysuiResTag("keyguard_bottom_area")) { if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { with(ambientIndicationSectionOptional.get()) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 5c7ca97474b7..0344ab8e0196 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -38,7 +38,6 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn import com.android.systemui.keyguard.ui.composable.modifier.burnInAware @@ -90,14 +89,10 @@ constructor( ) { init { - if (!MigrateClocksToBlueprint.isEnabled) { - throw IllegalStateException("this requires MigrateClocksToBlueprint.isEnabled") - } // This scene container section moves the NSSL to the SharedNotificationContainer. // This also requires that SharedNotificationContainer gets moved to the // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container, - // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this - // container by the NotificationStackScrollLayoutSection. + // NSSL is moved into this container by the NotificationStackScrollLayoutSection. // Ensure stackScrollLayout is a child of sharedNotificationContainer. if (stackScrollLayout.parent != sharedNotificationContainer) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index fa5f72bc0997..1480db9de701 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -37,12 +37,14 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.CustomAccessibilityAction @@ -66,6 +68,10 @@ import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState +import kotlin.math.round +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map @Composable fun VolumeSlider( @@ -196,9 +202,17 @@ private fun LegacyVolumeSlider( ) } } - - // Perform haptics due to UI composition - hapticsViewModel?.onValueChange(value) + var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) } + LaunchedEffect(value) { + snapshotFlow { value } + .map { round(it) } + .filter { it != lastDiscreteStep } + .distinctUntilChanged() + .collect { discreteStep -> + lastDiscreteStep = discreteStep + hapticsViewModel?.onValueChange(discreteStep) + } + } PlatformSlider( modifier = diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 801a2d6170cc..b76656d78cc4 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -71,7 +71,6 @@ constructor( } var hasCustomPositionUpdatedAnimation: Boolean = false - var migratedClocks: Boolean = false private val time = Calendar.getInstance() @@ -228,11 +227,7 @@ constructor( override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { logger.d("onMeasure") - if ( - migratedClocks && - !isSingleLineInternal && - MeasureSpec.getMode(heightMeasureSpec) == EXACTLY - ) { + if (!isSingleLineInternal && MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) { // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize val size = min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) super.setTextSize(COMPLEX_UNIT_PX, size) @@ -248,7 +243,7 @@ constructor( } } - if (migratedClocks && hasCustomPositionUpdatedAnimation) { + if (hasCustomPositionUpdatedAnimation) { // Expand width to avoid clock being clipped during stepping animation val targetWidth = measuredWidth + MeasureSpec.getSize(widthMeasureSpec) / 2 @@ -582,12 +577,10 @@ constructor( } override fun onRtlPropertiesChanged(layoutDirection: Int) { - if (migratedClocks) { - if (layoutDirection == LAYOUT_DIRECTION_RTL) { - textAlignment = TEXT_ALIGNMENT_TEXT_END - } else { - textAlignment = TEXT_ALIGNMENT_TEXT_START - } + if (layoutDirection == LAYOUT_DIRECTION_RTL) { + textAlignment = TEXT_ALIGNMENT_TEXT_END + } else { + textAlignment = TEXT_ALIGNMENT_TEXT_START } super.onRtlPropertiesChanged(layoutDirection) } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index ad9eba841c86..74d595ce65e6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -20,7 +20,6 @@ import android.graphics.Rect import android.icu.text.NumberFormat import android.util.TypedValue import android.view.LayoutInflater -import android.view.View import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import com.android.systemui.customization.R @@ -55,7 +54,6 @@ class DefaultClockController( private val layoutInflater: LayoutInflater, private val resources: Resources, private val settings: ClockSettings?, - private val migratedClocks: Boolean = false, messageBuffers: ClockMessageBuffers? = null, ) : ClockController { override val smallClock: DefaultClockFaceController @@ -67,7 +65,6 @@ class DefaultClockController( private val burmeseLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese) private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale) - protected var onSecondaryDisplay: Boolean = false override val events: DefaultClockEvents override val config: ClockConfig by lazy { @@ -175,10 +172,7 @@ class DefaultClockController( recomputePadding(targetRegion) } - override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) { - this@DefaultClockController.onSecondaryDisplay = onSecondaryDisplay - recomputePadding(null) - } + override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {} } open fun recomputePadding(targetRegion: Rect?) {} @@ -197,32 +191,11 @@ class DefaultClockController( override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true) init { - view.migratedClocks = migratedClocks view.hasCustomPositionUpdatedAnimation = true animations = LargeClockAnimations(view, 0f, 0f) } - override fun recomputePadding(targetRegion: Rect?) { - if (migratedClocks) { - return - } - // We center the view within the targetRegion instead of within the parent - // view by computing the difference and adding that to the padding. - val lp = view.getLayoutParams() as FrameLayout.LayoutParams - lp.topMargin = - if (onSecondaryDisplay) { - // On the secondary display we don't want any additional top/bottom margin. - 0 - } else { - val parent = view.parent - val yDiff = - if (targetRegion != null && parent is View && parent.isLaidOut()) - targetRegion.centerY() - parent.height / 2f - else 0f - (-0.5f * view.bottom + yDiff).toInt() - } - view.setLayoutParams(lp) - } + override fun recomputePadding(targetRegion: Rect?) {} /** See documentation at [AnimatableClockView.offsetGlyphsForStepClockAnimation]. */ fun offsetGlyphsForStepClockAnimation(fromLeft: Int, direction: Int, fraction: Float) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index e8987257bb47..c73e1c33f88a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -47,7 +47,6 @@ class DefaultClockProvider( val ctx: Context, val layoutInflater: LayoutInflater, val resources: Resources, - private val migratedClocks: Boolean = false, private val isClockReactiveVariantsEnabled: Boolean = false, ) : ClockProvider { private var messageBuffers: ClockMessageBuffers? = null @@ -83,14 +82,7 @@ class DefaultClockProvider( FLEX_DESIGN, ) } else { - DefaultClockController( - ctx, - layoutInflater, - resources, - settings, - migratedClocks, - messageBuffers, - ) + DefaultClockController(ctx, layoutInflater, resources, settings, messageBuffers) } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt index 21d41ae744a7..4a47f1bc12bf 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt @@ -140,8 +140,8 @@ class FlexClockFaceController( } /** - * targetRegion passed to all customized clock applies counter translationY of - * KeyguardStatusView and keyguard_large_clock_top_margin from default clock + * targetRegion passed to all customized clock applies counter translationY of Keyguard and + * keyguard_large_clock_top_margin from default clock */ override fun onTargetRegionChanged(targetRegion: Rect?) { // When a clock needs to be aligned with screen, like weather clock diff --git a/packages/SystemUI/docs/clock-plugins.md b/packages/SystemUI/docs/clock-plugins.md index fee82dfcf2e3..813038ee81ec 100644 --- a/packages/SystemUI/docs/clock-plugins.md +++ b/packages/SystemUI/docs/clock-plugins.md @@ -43,12 +43,6 @@ present in the source tree, although it will likely be removed in a later patch. SystemUI event dispatchers to the clock controllers. It maintains a set of event listeners, but otherwise attempts to do as little work as possible. It does maintain some state where necessary. -[KeyguardClockSwitchController](../src/com/android/keyguard/KeyguardClockSwitchController.java) is -the primary controller for the [KeyguardClockSwitch](../src/com/android/keyguard/KeyguardClockSwitch.java), -which serves as the view parent within SystemUI. Together they ensure the correct clock (either -large or small) is shown, handle animation between clock sizes, and control some sizing/layout -parameters for the clocks. - ### Creating a custom clock In order to create a custom clock, a partner must: - Write an implementation of ClockProviderPlugin and the subinterfaces relevant to your use-case. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt index fa5af510fec1..77e386963129 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt @@ -127,6 +127,7 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @DisableFlags( Flags.FLAG_COMMUNAL_HUB, + Flags.FLAG_GLANCEABLE_HUB_V2, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX, Flags.FLAG_SCENE_CONTAINER, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt index 08f139c6a3af..9c9d5adcfcc9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt @@ -51,7 +51,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { title = title, subtitle = subtitle, description = description, - contentView = contentView + contentView = contentView, ), BiometricUserInfo(USER_ID), BiometricOperationInfo(OPERATION_ID), @@ -101,9 +101,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { val fpPros = fingerprintSensorPropertiesInternal().first() val request = BiometricPromptRequest.Biometric( - promptInfo( - logoBitmap = logoBitmap, - ), + promptInfo(logoBitmap = logoBitmap), BiometricUserInfo(USER_ID), BiometricOperationInfo(OPERATION_ID), BiometricModalities(fingerprintProperties = fpPros), @@ -162,7 +160,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { BiometricUserInfo(USER_ID), BiometricOperationInfo(OPERATION_ID), stealth, - ) + ), ) for (request in toCheck) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index ab936590de93..66f44babdf5f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -158,6 +158,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private val mockFingerprintIconWidth = 300 private val mockFingerprintIconHeight = 300 + private val faceIconAuthingDescription = + R.string.biometric_dialog_face_icon_description_authenticating + private val faceIconAuthedDescription = + R.string.biometric_dialog_face_icon_description_authenticated + private val faceIconConfirmedDescription = + R.string.biometric_dialog_face_icon_description_confirmed + private val faceIconIdleDescription = R.string.biometric_dialog_face_icon_description_idle + private val sfpsFindSensorDescription = + R.string.security_settings_sfps_enroll_find_sensor_message + private val udfpsIconDescription = R.string.accessibility_fingerprint_label + private val faceFailedDescription = R.string.keyguard_face_failed + private val bpTryAgainDescription = R.string.biometric_dialog_try_again + private val bpConfirmDescription = R.string.biometric_dialog_confirm + private val fingerprintIconAuthenticatedDescription = + R.string.fingerprint_dialog_authenticated_confirmation + /** Mock [UdfpsOverlayParams] for a test. */ private fun mockUdfpsOverlayParams(isLandscape: Boolean = false): UdfpsOverlayParams = UdfpsOverlayParams( @@ -337,21 +353,18 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa if ((testCase.isCoex && !forceExplicitFlow) || testCase.isFaceOnly) { // Face-only or implicit co-ex auth assertThat(iconAsset).isEqualTo(R.raw.face_dialog_authenticating) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating) + assertThat(iconContentDescriptionId).isEqualTo(faceIconAuthingDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else if ((testCase.isCoex && forceExplicitFlow) || testCase.isFingerprintOnly) { // Fingerprint-only or explicit co-ex auth if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintAuthenticating()) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message) + assertThat(iconContentDescriptionId).isEqualTo(sfpsFindSensorDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else { assertThat(iconAsset) .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription) assertThat(shouldAnimateIconView).isEqualTo(false) } } @@ -397,26 +410,25 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa if (testCase.isFaceOnly) { // Face-only auth assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_error) - assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed) + assertThat(iconContentDescriptionId).isEqualTo(faceFailedDescription) assertThat(shouldAnimateIconView).isEqualTo(true) // Clear error, go to idle errorJob.join() assertThat(iconAsset).isEqualTo(R.raw.face_dialog_error_to_idle) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_face_icon_description_idle) + assertThat(iconContentDescriptionId).isEqualTo(faceIconIdleDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else if ((testCase.isCoex && forceExplicitFlow) || testCase.isFingerprintOnly) { // Fingerprint-only or explicit co-ex auth if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintToError()) - assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again) + assertThat(iconContentDescriptionId).isEqualTo(bpTryAgainDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else { assertThat(iconAsset) .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie) - assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again) + assertThat(iconContentDescriptionId).isEqualTo(bpTryAgainDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } @@ -425,14 +437,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { assertThat(iconAsset).isEqualTo(getSfpsAsset_errorToFingerprint()) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message) + assertThat(iconContentDescriptionId).isEqualTo(sfpsFindSensorDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else { assertThat(iconAsset) .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } } @@ -472,13 +482,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa // Covers (1) fingerprint-only (2) co-ex, authenticated by fingerprint if (testCase.authenticatedByFingerprint) { assertThat(iconAsset).isEqualTo(R.raw.biometricprompt_sfps_error_to_success) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message) + assertThat(iconContentDescriptionId).isEqualTo(sfpsFindSensorDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else { // Covers co-ex, authenticated by face assertThat(iconAsset).isEqualTo(R.raw.biometricprompt_sfps_error_to_unlock) assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation) + .isEqualTo(fingerprintIconAuthenticatedDescription) assertThat(shouldAnimateIconView).isEqualTo(true) // Confirm authentication @@ -486,8 +495,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(iconAsset) .isEqualTo(R.raw.biometricprompt_sfps_unlock_to_success) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } } else { // Non-SFPS (UDFPS / rear-FPS) test cases @@ -495,14 +503,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa if (testCase.authenticatedByFingerprint) { assertThat(iconAsset) .isEqualTo(R.raw.fingerprint_dialogue_error_to_success_lottie) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else { // co-ex, authenticated by face assertThat(iconAsset) .isEqualTo(R.raw.fingerprint_dialogue_error_to_unlock_lottie) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_confirm) + assertThat(iconContentDescriptionId).isEqualTo(bpConfirmDescription) assertThat(shouldAnimateIconView).isEqualTo(true) // Confirm authentication @@ -512,8 +518,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa .isEqualTo( R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie ) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } } @@ -543,22 +548,19 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa // Fingerprint icon asset assertions if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintToSuccess()) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message) + assertThat(iconContentDescriptionId).isEqualTo(sfpsFindSensorDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else { assertThat(iconAsset) .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } } else if (testCase.isFaceOnly || testCase.isCoex) { // Face icon asset assertions // If co-ex, use implicit flow (explicit flow always requires confirmation) assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated) + assertThat(iconContentDescriptionId).isEqualTo(faceIconAuthedDescription) assertThat(shouldAnimateIconView).isEqualTo(true) assertThat(message).isEqualTo(PromptMessage.Empty) } @@ -586,20 +588,18 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa if (testCase.isFaceOnly) { assertThat(iconAsset).isEqualTo(R.raw.face_dialog_wink_from_dark) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated) + assertThat(iconContentDescriptionId).isEqualTo(faceIconAuthedDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else if (testCase.isCoex) { // explicit flow, confirmation requested if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintToUnlock()) assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation) + .isEqualTo(fingerprintIconAuthenticatedDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } else { assertThat(iconAsset) .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_confirm) + assertThat(iconContentDescriptionId).isEqualTo(bpConfirmDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } } @@ -628,8 +628,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa if (testCase.isFaceOnly) { assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed) + assertThat(iconContentDescriptionId).isEqualTo(faceIconConfirmedDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } @@ -644,8 +643,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa .isEqualTo( R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie ) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription) assertThat(shouldAnimateIconView).isEqualTo(true) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt index a308c8ee38ca..3f4d3f8ba12a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt @@ -98,6 +98,21 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() { } @Test + fun onMovedToDisplays_updatesOnMovedToDisplay() = + testScope.runTest { + val lastOnMovedToDisplay by collectLastValue(underTest.onMovedToDisplay) + assertThat(lastOnMovedToDisplay).isNull() + + val configurationCallback = withArgCaptor { + verify(configurationController).addCallback(capture()) + } + + configurationCallback.onMovedToDisplay(1, Configuration()) + runCurrent() + assertThat(lastOnMovedToDisplay).isEqualTo(1) + } + + @Test fun onAnyConfigurationChange_updatesOnConfigChanged() = testScope.runTest { val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt index 370adee44a42..03bf79bc7a38 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal import android.app.StatsManager import android.app.StatsManager.StatsPullAtomCallback import android.content.pm.UserInfo +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.util.StatsEvent import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -32,6 +33,7 @@ import com.android.systemui.communal.shared.log.CommunalMetricsLogger import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.settings.fakeUserTracker import com.android.systemui.shared.system.SysUiStatsLog @@ -75,10 +77,7 @@ class CommunalMetricsStartableTest : SysuiTestCase() { // Set up an existing user, which is required for widgets to show val userInfos = listOf(UserInfo(0, "main", UserInfo.FLAG_MAIN)) userRepository.setUserInfos(userInfos) - userTracker.set( - userInfos = userInfos, - selectedUserIndex = 0, - ) + userTracker.set(userInfos = userInfos, selectedUserIndex = 0) underTest = CommunalMetricsStartable( @@ -90,14 +89,16 @@ class CommunalMetricsStartableTest : SysuiTestCase() { ) } + @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) @Test - fun start_communalFlagDisabled_doNotSetPullAtomCallback() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + fun start_communalFlagDisabled_doNotSetPullAtomCallback() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) - underTest.start() + underTest.start() - verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any()) - } + verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any()) + } @Test fun onPullAtom_atomTagDoesNotMatch_pullSkip() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt index b66727e492cf..038ea9ccaaa9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -26,8 +26,8 @@ import android.content.res.mainResources import android.os.UserManager.USER_TYPE_PROFILE_MANAGED import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 @@ -35,10 +35,13 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.model.DisabledReason import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING +import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.util.mockito.nullable @@ -51,15 +54,21 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class CommunalSettingsRepositoryImplTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiTestCase() { private val kosmos = testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources } private val testScope = kosmos.testScope private lateinit var underTest: CommunalSettingsRepository + init { + mSetFlagsRule.setFlagsParameterization(flags!!) + } + @Before fun setUp() { kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) @@ -164,17 +173,17 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER) } - @EnableFlags(FLAG_COMMUNAL_HUB) + @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test fun classicFlagIsDisabled() = - testScope.runTest { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + kosmos.runTest { + setCommunalV2Enabled(false) val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isFalse() assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG) } - @DisableFlags(FLAG_COMMUNAL_HUB) + @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test fun communalHubFlagIsDisabled() = testScope.runTest { @@ -295,6 +304,34 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { } } + @Test + fun screensaverDisabledByUser() = + testScope.runTest { + val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) + + kosmos.fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + 0, + PRIMARY_USER.id, + ) + + assertThat(enabledState).isFalse() + } + + @Test + fun screensaverEnabledByUser() = + testScope.runTest { + val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) + + kosmos.fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + 1, + PRIMARY_USER.id, + ) + + assertThat(enabledState).isTrue() + } + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) @@ -310,5 +347,11 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0) val WORK_PROFILE = UserInfo(10, "work", /* iconPath= */ "", /* flags= */ 0, USER_TYPE_PROFILE_MANAGED) + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index b9e646fee98f..7ae0577bd289 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -35,6 +35,7 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.model.CommunalSmartspaceTimer @@ -232,7 +233,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun isCommunalAvailable_communalDisabled_false() = testScope.runTest { - mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB) + mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt new file mode 100644 index 000000000000..a8a3873d6de2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import android.appwidget.AppWidgetHost.AppWidgetHostListener +import android.appwidget.AppWidgetHostView +import android.platform.test.flag.junit.FlagsParameterization +import android.util.SizeF +import android.widget.RemoteViews +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_SECONDARY_USER_WIDGET_HOST +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper +import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate +import com.android.systemui.communal.widgets.CommunalAppWidgetHost +import com.android.systemui.communal.widgets.GlanceableHubWidgetManager +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.testKosmos +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@SmallTest +@RunWith(ParameterizedAndroidJunit4::class) +class CommunalAppWidgetViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { + val kosmos = testKosmos() + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + private val Kosmos.listenerDelegateFactory by + Kosmos.Fixture { + AppWidgetHostListenerDelegate.Factory { listener -> + AppWidgetHostListenerDelegate(fakeExecutor, listener) + } + } + + private val Kosmos.appWidgetHost by + Kosmos.Fixture { + mock<CommunalAppWidgetHost> { + on { setListener(any(), any()) } doAnswer + { invocation -> + val callback = invocation.arguments[1] as AppWidgetHostListener + callback.updateAppWidget(mock<RemoteViews>()) + } + } + } + + private val Kosmos.glanceableHubWidgetManager by + Kosmos.Fixture { + mock<GlanceableHubWidgetManager> { + on { setAppWidgetHostListener(any(), any()) } doAnswer + { invocation -> + val callback = invocation.arguments[1] as AppWidgetHostListener + callback.updateAppWidget(mock<RemoteViews>()) + } + } + } + + private val Kosmos.underTest by + Kosmos.Fixture { + CommunalAppWidgetViewModel( + backgroundCoroutineContext, + { appWidgetHost }, + listenerDelegateFactory, + { glanceableHubWidgetManager }, + fakeGlanceableHubMultiUserHelper, + ) + .apply { activateIn(testScope) } + } + + @Test + fun setListener() = + kosmos.runTest { + val listener = mock<AppWidgetHostListener>() + + underTest.setListener(123, listener) + runAll() + + verify(listener).updateAppWidget(any()) + } + + @Test + fun setListener_HSUM() = + kosmos.runTest { + fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true) + val listener = mock<AppWidgetHostListener>() + + underTest.setListener(123, listener) + runAll() + + verify(listener).updateAppWidget(any()) + } + + @Test + fun updateSize() = + kosmos.runTest { + val view = mock<AppWidgetHostView>() + val size = SizeF(/* width= */ 100f, /* height= */ 200f) + + underTest.updateSize(size, view) + runAll() + + verify(view) + .updateAppWidgetSize( + /* newOptions = */ any(), + /* minWidth = */ eq(100), + /* minHeight = */ eq(200), + /* maxWidth = */ eq(100), + /* maxHeight = */ eq(200), + /* ignorePadding = */ eq(true), + ) + } + + private fun Kosmos.runAll() { + runCurrent() + fakeExecutor.runAllReady() + } + + private companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf(FLAG_SECONDARY_USER_WIDGET_HOST) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt index 88206850eb60..b78080885b0a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.viewmodel import android.platform.test.annotations.EnableFlags +import android.provider.Settings import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -29,12 +30,16 @@ import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn +import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.policy.batteryController import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.whenever @@ -56,10 +61,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test - fun shouldShowDreamButtonOnHub_trueWhenCanDream() = + fun shouldShowDreamButtonOnHub_trueWhenPluggedIn() = with(kosmos) { runTest { - whenever(dreamManager.canStartDreaming(any())).thenReturn(true) whenever(batteryController.isPluggedIn()).thenReturn(true) val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) @@ -68,11 +72,10 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test - fun shouldShowDreamButtonOnHub_falseWhenCannotDream() = + fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() = with(kosmos) { runTest { - whenever(dreamManager.canStartDreaming(any())).thenReturn(false) - whenever(batteryController.isPluggedIn()).thenReturn(true) + whenever(batteryController.isPluggedIn()).thenReturn(false) val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) assertThat(shouldShowButton).isFalse() @@ -80,25 +83,40 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test - fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() = + fun onShowDreamButtonTap_dreamsEnabled_startsDream() = with(kosmos) { runTest { - whenever(dreamManager.canStartDreaming(any())).thenReturn(true) - whenever(batteryController.isPluggedIn()).thenReturn(false) + val currentUser = fakeUserRepository.asMainUser() + kosmos.fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + 1, + currentUser.id, + ) + runCurrent() - val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) - assertThat(shouldShowButton).isFalse() + underTest.onShowDreamButtonTap() + runCurrent() + + verify(dreamManager).startDream() } } @Test - fun onShowDreamButtonTap_startsDream() = + fun onShowDreamButtonTap_dreamsDisabled_startsActivity() = with(kosmos) { runTest { + val currentUser = fakeUserRepository.asMainUser() + kosmos.fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + 0, + currentUser.id, + ) + runCurrent() + underTest.onShowDreamButtonTap() runCurrent() - verify(dreamManager).startDream() + verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt()) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 9d711ab0cd29..d70af2806430 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -172,7 +172,6 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.testDispatcher, testScope, kosmos.testScope.backgroundScope, - context.resources, kosmos.keyguardTransitionInteractor, kosmos.keyguardInteractor, mock<KeyguardIndicationController>(), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt index 928514657257..c8661cf59051 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.controls.ui +import android.view.HapticFeedbackConstants import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.view.HapticFeedbackConstants import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.controls.ControlsMetricsLogger @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.wm.shell.taskview.TaskViewFactory +import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -45,31 +46,20 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.util.Optional @SmallTest @RunWith(AndroidJUnit4::class) class ControlActionCoordinatorImplTest : SysuiTestCase() { - @Mock - private lateinit var vibratorHelper: VibratorHelper - @Mock - private lateinit var keyguardStateController: KeyguardStateController - @Mock - private lateinit var bgExecutor: DelayableExecutor - @Mock - private lateinit var uiExecutor: DelayableExecutor - @Mock - private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var broadcastSender: BroadcastSender - @Mock - private lateinit var taskViewFactory: Optional<TaskViewFactory> - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private lateinit var cvh: ControlViewHolder - @Mock - private lateinit var metricsLogger: ControlsMetricsLogger - @Mock - private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager + @Mock private lateinit var vibratorHelper: VibratorHelper + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var bgExecutor: DelayableExecutor + @Mock private lateinit var uiExecutor: DelayableExecutor + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var broadcastSender: BroadcastSender + @Mock private lateinit var taskViewFactory: Optional<TaskViewFactory> + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var cvh: ControlViewHolder + @Mock private lateinit var metricsLogger: ControlsMetricsLogger + @Mock private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager companion object { fun <T> any(): T = Mockito.any<T>() @@ -89,18 +79,21 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true) controlsSettingsRepository.setCanShowControlsInLockscreen(true) - coordinator = spy(ControlActionCoordinatorImpl( - mContext, - bgExecutor, - uiExecutor, - activityStarter, - broadcastSender, - keyguardStateController, - taskViewFactory, - metricsLogger, - vibratorHelper, - controlsSettingsRepository, - )) + coordinator = + spy( + ControlActionCoordinatorImpl( + mContext, + bgExecutor, + uiExecutor, + activityStarter, + broadcastSender, + keyguardStateController, + taskViewFactory, + metricsLogger, + vibratorHelper, + controlsSettingsRepository, + ) + ) coordinator.activityContext = mContext `when`(cvh.cws.ci.controlId).thenReturn(ID) @@ -198,19 +191,15 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { fun drag_isEdge_performsSegmentTickHaptics() { coordinator.drag(cvh, true) - verify(vibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.SEGMENT_TICK) - ) + verify(vibratorHelper) + .performHapticFeedback(any(), eq(HapticFeedbackConstants.SEGMENT_TICK)) } @Test fun drag_isNotEdge_performsFrequentTickHaptics() { coordinator.drag(cvh, false) - verify(vibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK) - ) + verify(vibratorHelper) + .performHapticFeedback(any(), eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index b07097d61b96..5921e9479bd9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -92,10 +92,10 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.isNull import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.firstValue @@ -768,7 +768,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() { runCurrent() verify(mDreamOverlayCallback).onRedirectWake(true) client.onWakeRequested() - verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), isNull()) + verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), anyOrNull()) verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt index c950523f7854..c950523f7854 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt index 9cfd328a9484..f2a6c11b872e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt @@ -36,6 +36,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -43,6 +44,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@Ignore("b/384284415") class ContextualEducationRepositoryTest : SysuiTestCase() { private lateinit var underTest: UserContextualEducationRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepositoryTest.kt new file mode 100644 index 000000000000..477e31e8a66c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepositoryTest.kt @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.data.repository + +import android.hardware.input.AppLaunchData +import android.hardware.input.AppLaunchData.RoleData +import android.hardware.input.InputGestureData +import android.hardware.input.InputGestureData.createKeyTrigger +import android.hardware.input.fakeInputManager +import android.view.KeyEvent.KEYCODE_A +import android.view.KeyEvent.META_ALT_ON +import android.view.KeyEvent.META_CTRL_ON +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyboard.shortcut.appLaunchDataRepository +import com.android.systemui.keyboard.shortcut.shared.model.shortcutCommand +import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AppLaunchDataRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val inputManager = kosmos.fakeInputManager.inputManager + private val testHelper = kosmos.shortcutHelperTestHelper + private val repo = kosmos.appLaunchDataRepository + + @Test + fun appLaunchData_returnsDataRetrievedFromApiBasedOnShortcutCommand() = + kosmos.runTest { + val inputGesture = simpleInputGestureDataForAppLaunchShortcut() + setApiAppLaunchBookmarks(listOf(inputGesture)) + + testHelper.toggle(TEST_DEVICE_ID) + + val appLaunchData = + repo.getAppLaunchDataForShortcutWithCommand( + shortcutCommand = + shortcutCommand { + key("Ctrl") + key("Alt") + key("A") + } + ) + + assertThat(appLaunchData).isEqualTo(inputGesture.action.appLaunchData()) + } + + @Test + fun appLaunchData_returnsSameDataForAnyOrderOfShortcutCommandKeys() = + kosmos.runTest { + val inputGesture = simpleInputGestureDataForAppLaunchShortcut() + setApiAppLaunchBookmarks(listOf(inputGesture)) + + testHelper.toggle(TEST_DEVICE_ID) + + val shortcutCommandCtrlAltA = shortcutCommand { + key("Ctrl") + key("Alt") + key("A") + } + + val shortcutCommandCtrlAAlt = shortcutCommand { + key("Ctrl") + key("A") + key("Alt") + } + + val shortcutCommandAltCtrlA = shortcutCommand { + key("Alt") + key("Ctrl") + key("A") + } + + assertThat(repo.getAppLaunchDataForShortcutWithCommand(shortcutCommandCtrlAltA)) + .isEqualTo(inputGesture.action.appLaunchData()) + + assertThat(repo.getAppLaunchDataForShortcutWithCommand(shortcutCommandCtrlAAlt)) + .isEqualTo(inputGesture.action.appLaunchData()) + + assertThat(repo.getAppLaunchDataForShortcutWithCommand(shortcutCommandAltCtrlA)) + .isEqualTo(inputGesture.action.appLaunchData()) + } + + private fun setApiAppLaunchBookmarks(appLaunchBookmarks: List<InputGestureData>) { + whenever(inputManager.appLaunchBookmarks).thenReturn(appLaunchBookmarks) + } + + private fun simpleInputGestureDataForAppLaunchShortcut( + keyCode: Int = KEYCODE_A, + modifiers: Int = META_CTRL_ON or META_ALT_ON, + appLaunchData: AppLaunchData = RoleData(TEST_ROLE), + ): InputGestureData { + return InputGestureData.Builder() + .setTrigger(createKeyTrigger(keyCode, modifiers)) + .setAppLaunchData(appLaunchData) + .build() + } + + private companion object { + private const val TEST_ROLE = "Test role" + private const val TEST_DEVICE_ID = 123 + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt index d12c04586ac2..4cfb26e6555b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt @@ -18,14 +18,21 @@ package com.android.systemui.keyboard.shortcut.data.repository import android.content.Context import android.content.Context.INPUT_SERVICE +import android.hardware.input.AppLaunchData +import android.hardware.input.AppLaunchData.RoleData import android.hardware.input.InputGestureData +import android.hardware.input.InputGestureData.createKeyTrigger import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION import android.hardware.input.fakeInputManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.view.KeyEvent.KEYCODE_A import android.view.KeyEvent.KEYCODE_SLASH +import android.view.KeyEvent.META_ALT_ON import android.view.KeyEvent.META_CAPS_LOCK_ON +import android.view.KeyEvent.META_CTRL_ON import android.view.KeyEvent.META_META_ON import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -44,14 +51,15 @@ import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCusto import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.launchCalendarShortcutAddRequest import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardKeyCombination import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Add -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Delete +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.SingleShortcutCustomization import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.userTracker @@ -72,7 +80,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { private val mockUserContext: Context = mock() private val kosmos = - testKosmos().also { + testKosmos().useUnconfinedTestDispatcher().also { it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } @@ -242,6 +250,32 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { } @Test + fun buildInputGestureDataForAppLaunchShortcut_keyGestureTypeIsTypeLaunchApp() = + testScope.runTest { + setApiAppLaunchBookmarks(listOf(simpleInputGestureDataForAppLaunchShortcut())) + helper.toggle(deviceId = 123) + repo.onCustomizationRequested(launchCalendarShortcutAddRequest) + repo.updateUserKeyCombination(standardKeyCombination) + + val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized() + + assertThat(inputGestureData?.action?.keyGestureType()) + .isEqualTo(KEY_GESTURE_TYPE_LAUNCH_APPLICATION) + } + + @Test + fun buildInputGestureDataForAppLaunchShortcut_appLaunchDataIsAdded() = + testScope.runTest { + setApiAppLaunchBookmarks(listOf(simpleInputGestureDataForAppLaunchShortcut())) + helper.toggle(deviceId = 123) + repo.onCustomizationRequested(launchCalendarShortcutAddRequest) + repo.updateUserKeyCombination(standardKeyCombination) + + val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized() + assertThat(inputGestureData?.action?.appLaunchData()).isNotNull() + } + + @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun deleteShortcut_successfullyRetrievesGestureDataAndDeletesShortcut() { testScope.runTest { @@ -304,17 +338,17 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { private suspend fun customizeShortcut( customizationRequest: ShortcutCustomizationRequestInfo, - keyCombination: KeyCombination? = null - ): ShortcutCustomizationRequestResult{ + keyCombination: KeyCombination? = null, + ): ShortcutCustomizationRequestResult { repo.onCustomizationRequested(customizationRequest) repo.updateUserKeyCombination(keyCombination) return when (customizationRequest) { - is Add -> { + is SingleShortcutCustomization.Add -> { repo.confirmAndSetShortcutCurrentlyBeingCustomized() } - is Delete -> { + is SingleShortcutCustomization.Delete -> { repo.deleteShortcutCurrentlyBeingCustomized() } @@ -352,4 +386,19 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { assertThat(categories).isEmpty() } } + + private fun setApiAppLaunchBookmarks(appLaunchBookmarks: List<InputGestureData>) { + whenever(inputManager.appLaunchBookmarks).thenReturn(appLaunchBookmarks) + } + + private fun simpleInputGestureDataForAppLaunchShortcut( + keyCode: Int = KEYCODE_A, + modifiers: Int = META_CTRL_ON or META_ALT_ON, + appLaunchData: AppLaunchData = RoleData("Test role"), + ): InputGestureData { + return InputGestureData.Builder() + .setTrigger(createKeyTrigger(keyCode, modifiers)) + .setAppLaunchData(appLaunchData) + .build() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt index f78c692ee4c2..96410597e20c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt @@ -28,6 +28,7 @@ import android.hardware.input.AppLaunchData import android.hardware.input.AppLaunchData.RoleData import android.hardware.input.InputGestureData import android.hardware.input.InputGestureData.createKeyTrigger +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION import android.view.KeyEvent.KEYCODE_A import android.view.KeyEvent.META_ALT_ON import android.view.KeyEvent.META_CTRL_ON @@ -55,14 +56,15 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever - @SmallTest @RunWith(AndroidJUnit4::class) class InputGestureDataAdapterTest : SysuiTestCase() { - private val kosmos = testKosmos().also { kosmos -> - kosmos.userTracker = FakeUserTracker(onCreateCurrentUserContext = { kosmos.mockedContext }) - } + private val kosmos = + testKosmos().also { kosmos -> + kosmos.userTracker = + FakeUserTracker(onCreateCurrentUserContext = { kosmos.mockedContext }) + } private val adapter = kosmos.inputGestureDataAdapter private val roleManager = kosmos.roleManager private val packageManager: PackageManager = kosmos.packageManager @@ -139,24 +141,40 @@ class InputGestureDataAdapterTest : SysuiTestCase() { val inputGestureData = buildInputGestureDataForAppLaunchShortcut() val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData)) - assertThat(internalGroups).containsExactly( - InternalGroupsSource( - type = ShortcutCategoryType.AppCategories, - groups = listOf( - InternalKeyboardShortcutGroup( - label = APPLICATION_SHORTCUT_GROUP_LABEL, - items = listOf( - InternalKeyboardShortcutInfo( - label = expectedShortcutLabelForFirstAppMatchingIntent, - keycode = KEYCODE_A, - modifiers = META_CTRL_ON or META_ALT_ON, - isCustomShortcut = true + assertThat(internalGroups) + .containsExactly( + InternalGroupsSource( + type = ShortcutCategoryType.AppCategories, + groups = + listOf( + InternalKeyboardShortcutGroup( + label = APPLICATION_SHORTCUT_GROUP_LABEL, + items = + listOf( + InternalKeyboardShortcutInfo( + label = + expectedShortcutLabelForFirstAppMatchingIntent, + keycode = KEYCODE_A, + modifiers = META_CTRL_ON or META_ALT_ON, + isCustomShortcut = true, + ) + ), ) - ) - ) + ), + ) + ) + } + + @Test + fun keyGestureType_returnsTypeLaunchApplicationForAppLaunchShortcutCategory() = + kosmos.runTest { + assertThat( + adapter.getKeyGestureTypeForShortcut( + shortcutLabel = "Test Shortcut label", + shortcutCategoryType = ShortcutCategoryType.AppCategories, ) ) - ) + .isEqualTo(KEY_GESTURE_TYPE_LAUNCH_APPLICATION) } private fun setApiToRetrieveResolverActivity() { @@ -169,11 +187,10 @@ class InputGestureDataAdapterTest : SysuiTestCase() { .thenReturn(fakeActivityInfo) } - private fun buildInputGestureDataForAppLaunchShortcut( keyCode: Int = KEYCODE_A, modifiers: Int = META_CTRL_ON or META_ALT_ON, - appLaunchData: AppLaunchData = RoleData(TEST_ROLE) + appLaunchData: AppLaunchData = RoleData(TEST_ROLE), ): InputGestureData { return InputGestureData.Builder() .setTrigger(createKeyTrigger(keyCode, modifiers)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepositoryTest.kt new file mode 100644 index 000000000000..ded2d223aab4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepositoryTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyboard.shortcut.shortcutHelperInputDeviceRepository +import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ShortcutHelperInputDeviceRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val testHelper = kosmos.shortcutHelperTestHelper + private val repo = kosmos.shortcutHelperInputDeviceRepository + + @Test + fun activeInputDevice_nullByDefault() = + kosmos.runTest { + val activeInputDevice by collectLastValue(repo.activeInputDevice) + + assertThat(activeInputDevice).isNull() + } + + @Test + fun activeInputDevice_nonNullWhenHelperIsShown() = + kosmos.runTest { + val activeInputDevice by collectLastValue(repo.activeInputDevice) + + testHelper.showFromActivity() + + assertThat(activeInputDevice).isNotNull() + } + + @Test + fun activeInputDevice_nullWhenHelperIsClosed() = + kosmos.runTest { + val activeInputDevice by collectLastValue(repo.activeInputDevice) + + testHelper.showFromActivity() + testHelper.hideFromActivity() + + assertThat(activeInputDevice).isNull() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt index 7dc7016e5e74..7c88d76f28bd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt @@ -43,11 +43,12 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.SingleShortcutCustomization import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.shared.model.shortcut import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory +import com.android.systemui.keyboard.shortcut.shared.model.shortcutCommand import com.android.systemui.res.R object TestShortcuts { @@ -596,14 +597,27 @@ object TestShortcuts { ) val allAppsShortcutAddRequest = - ShortcutCustomizationRequestInfo.Add( + SingleShortcutCustomization.Add( label = "Open apps list", categoryType = System, subCategoryLabel = "System controls", ) + val launchCalendarShortcutAddRequest = + SingleShortcutCustomization.Add( + label = "Calendar", + categoryType = ShortcutCategoryType.AppCategories, + subCategoryLabel = "Applications", + shortcutCommand = + shortcutCommand { + key("Ctrl") + key("Alt") + key("A") + }, + ) + val allAppsShortcutDeleteRequest = - ShortcutCustomizationRequestInfo.Delete( + SingleShortcutCustomization.Delete( label = "Open apps list", categoryType = System, subCategoryLabel = "System controls", @@ -698,7 +712,7 @@ object TestShortcuts { ) val standardAddShortcutRequest = - ShortcutCustomizationRequestInfo.Add( + SingleShortcutCustomization.Add( label = "Standard shortcut", categoryType = System, subCategoryLabel = "Standard subcategory", diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 77f19795eaf7..6805a133459f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -123,27 +123,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test - fun bottomAreaAlpha() = - testScope.runTest { - assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f) - - underTest.setBottomAreaAlpha(0.1f) - assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f) - - underTest.setBottomAreaAlpha(0.2f) - assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f) - - underTest.setBottomAreaAlpha(0.3f) - assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f) - - underTest.setBottomAreaAlpha(0.5f) - assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f) - - underTest.setBottomAreaAlpha(1.0f) - assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f) - } - - @Test fun panelAlpha() = testScope.runTest { assertThat(underTest.panelAlpha.value).isEqualTo(1f) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt index ead151ee6df2..b0698555941c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt @@ -106,7 +106,7 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { repository.setKeyguardEnabled(true) runCurrent() - assertEquals(listOf(false, true, false), canWake) + assertEquals(listOf(false, true), canWake) } @Test @@ -374,6 +374,8 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() { // Should be canceled by the wakeup, but there would still be an // alarm in flight that should be canceled. false, + // True once we're actually GONE. + true, ), canWake, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt index df4d5ab90f5e..ea2b3cdcf98c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -206,6 +206,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { @Test @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility_without_keyguard_shell_transitions() { + // Show the surface behind, then hide it. + underTest.setLockscreenShown(true) + underTest.setSurfaceBehindVisibility(true) underTest.setSurfaceBehindVisibility(false) verify(activityTaskManagerService).setLockScreenShown(eq(true), any()) } @@ -213,6 +216,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { @Test @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING) fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility_with_keyguard_shell_transitions() { + // Show the surface behind, then hide it. + underTest.setLockscreenShown(true) + underTest.setSurfaceBehindVisibility(true) underTest.setSurfaceBehindVisibility(false) verify(keyguardTransitions).startKeyguardTransition(eq(true), any()) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt index 10f7128af43c..9ceabd743618 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt @@ -56,21 +56,12 @@ class DefaultIndicationAreaSectionTest : SysuiTestCase() { @Test fun addViewsConditionally() { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) val constraintLayout = ConstraintLayout(context, null) underTest.addViews(constraintLayout) assertThat(constraintLayout.childCount).isGreaterThan(0) } @Test - fun addViewsConditionally_migrateFlagOff() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - val constraintLayout = ConstraintLayout(context, null) - underTest.addViews(constraintLayout) - assertThat(constraintLayout.childCount).isEqualTo(0) - } - - @Test fun applyConstraints() { val cs = ConstraintSet() underTest.applyConstraints(cs) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt index 9fab60374f8b..70d7a5f2bdc1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt @@ -27,7 +27,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -47,6 +49,8 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } private val underTest by lazy { kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel } + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + @Test fun deviceEntryParentViewDisappear() = testScope.runTest { @@ -67,13 +71,44 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() values.forEach { assertThat(it).isEqualTo(0f) } } + @Test + fun blurRadiusGoesToMaximumWhenShadeIsExpanded() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f), + startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + checkInterpolatedValues = false, + transitionFactory = ::step, + actualValuesProvider = { values }, + ) + } + + @Test + fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + kosmos.bouncerWindowBlurTestUtil.shadeExpanded(false) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f), + startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + transitionFactory = ::step, + actualValuesProvider = { values }, + ) + } + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.PRIMARY_BOUNCER, value = value, transitionState = state, - ownerName = "AlternateBouncerToPrimaryBouncerTransitionViewModelTest" + ownerName = "AlternateBouncerToPrimaryBouncerTransitionViewModelTest", ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt new file mode 100644 index 000000000000..0f239e8472a8 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest by lazy { kosmos.aodToPrimaryBouncerTransitionViewModel } + + @Test + fun aodToPrimaryBouncerChangesBlurToMax() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 1.0f), + startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + transitionFactory = { value, state -> + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.PRIMARY_BOUNCER, + value = value, + transitionState = state, + ownerName = "AodToPrimaryBouncerTransitionViewModelTest", + ) + }, + actualValuesProvider = { values }, + checkInterpolatedValues = false, + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerWindowBlurTestUtilKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerWindowBlurTestUtilKosmos.kt new file mode 100644 index 000000000000..c3f0deb925e4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerWindowBlurTestUtilKosmos.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.util.MathUtils +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.ShadeTestUtil +import com.android.systemui.shade.shadeTestUtil +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope + +val Kosmos.bouncerWindowBlurTestUtil by + Kosmos.Fixture { + BouncerWindowBlurTestUtil( + shadeTestUtil = shadeTestUtil, + fakeKeyguardTransitionRepository = fakeKeyguardTransitionRepository, + fakeKeyguardRepository = fakeKeyguardRepository, + testScope = testScope, + ) + } + +class BouncerWindowBlurTestUtil( + private val shadeTestUtil: ShadeTestUtil, + private val fakeKeyguardTransitionRepository: FakeKeyguardTransitionRepository, + private val fakeKeyguardRepository: FakeKeyguardRepository, + private val testScope: TestScope, +) { + + suspend fun assertTransitionToBlurRadius( + transitionProgress: List<Float>, + startValue: Float, + endValue: Float, + actualValuesProvider: () -> List<Float>, + transitionFactory: (value: Float, state: TransitionState) -> TransitionStep, + checkInterpolatedValues: Boolean = true, + ) { + val transitionSteps = + listOf( + transitionFactory(transitionProgress.first(), STARTED), + *transitionProgress.drop(1).map { transitionFactory(it, RUNNING) }.toTypedArray(), + ) + fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope) + + val interpolationFunction = { step: Float -> MathUtils.lerp(startValue, endValue, step) } + + if (checkInterpolatedValues) { + assertThat(actualValuesProvider.invoke()) + .containsExactly(*transitionProgress.map(interpolationFunction).toTypedArray()) + } else { + assertThat(actualValuesProvider.invoke()).contains(endValue) + } + } + + fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeTestUtil.setQsExpansion(1f) + } else { + fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeTestUtil.setQsExpansion(0f) + shadeTestUtil.setLockscreenShadeExpansion(0f) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt index bf71bec9e1f6..7a68d4eda654 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -70,13 +71,27 @@ class DozingToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { values.forEach { assertThat(it).isEqualTo(0f) } } + @Test + fun windowBlurRadiusGoesFromMinToMax() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + actualValuesProvider = { values }, + transitionFactory = ::step, + ) + } + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( from = KeyguardState.DOZING, to = KeyguardState.PRIMARY_BOUNCER, value = value, transitionState = state, - ownerName = "DozingToPrimaryBouncerTransitionViewModelTest" + ownerName = "DozingToPrimaryBouncerTransitionViewModelTest", ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt index 6f74ed34c4e9..242ee3a783f2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt @@ -20,7 +20,6 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState -import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.domain.interactor.configurationInteractor @@ -31,7 +30,6 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor -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.shared.model.BurnInModel @@ -61,8 +59,6 @@ import platform.test.runner.parameterized.Parameters class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - - private val bottomAreaInteractor = kosmos.keyguardBottomAreaInteractor private lateinit var underTest: KeyguardIndicationAreaViewModel private val keyguardRepository = kosmos.fakeKeyguardRepository private val communalSceneRepository = kosmos.fakeCommunalSceneRepository @@ -87,12 +83,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT @Before fun setUp() { - val bottomAreaViewModel = - mock<KeyguardBottomAreaViewModel> { - on { startButton } doReturn startButtonFlow - on { endButton } doReturn endButtonFlow - on { alpha } doReturn alphaFlow - } val burnInInteractor = mock<BurnInInteractor> { on { burnIn(anyInt(), anyInt()) } doReturn flowOf(BurnInModel()) @@ -109,8 +99,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT underTest = KeyguardIndicationAreaViewModel( keyguardInteractor = kosmos.keyguardInteractor, - bottomAreaInteractor = bottomAreaInteractor, - keyguardBottomAreaViewModel = bottomAreaViewModel, burnInHelperWrapper = burnInHelperWrapper, burnInInteractor = burnInInteractor, shortcutsCombinedViewModel = shortcutsCombinedViewModel, @@ -123,23 +111,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT } @Test - fun alpha() = - testScope.runTest { - val alpha by collectLastValue(underTest.alpha) - - assertThat(alpha).isEqualTo(1f) - alphaFlow.value = 0.1f - assertThat(alpha).isEqualTo(0.1f) - alphaFlow.value = 0.5f - assertThat(alpha).isEqualTo(0.5f) - alphaFlow.value = 0.2f - assertThat(alpha).isEqualTo(0.2f) - alphaFlow.value = 0f - assertThat(alpha).isEqualTo(0f) - } - - @Test - @DisableFlags(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) fun isIndicationAreaPadded() = testScope.runTest { keyguardRepository.setKeyguardShowing(true) @@ -157,23 +128,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT } @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - fun indicationAreaTranslationX() = - testScope.runTest { - val translationX by collectLastValue(underTest.indicationAreaTranslationX) - - assertThat(translationX).isEqualTo(0f) - bottomAreaInteractor.setClockPosition(100, 100) - assertThat(translationX).isEqualTo(100f) - bottomAreaInteractor.setClockPosition(200, 100) - assertThat(translationX).isEqualTo(200f) - bottomAreaInteractor.setClockPosition(200, 200) - assertThat(translationX).isEqualTo(200f) - bottomAreaInteractor.setClockPosition(300, 100) - assertThat(translationX).isEqualTo(300f) - } - - @Test @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun indicationAreaTranslationY() = testScope.runTest { @@ -236,7 +190,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT @Parameters(name = "{0}") fun getParams(): List<FlagsParameterization> { return FlagsParameterization.allCombinationsOf( - FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index b5e670c4bbcc..95ffc962797d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -24,7 +24,6 @@ import android.platform.test.flag.junit.FlagsParameterization import android.view.View import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState -import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.communalSceneRepository @@ -74,7 +73,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) +@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index 1c1fcc450d73..fba39970ddb2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.andSceneContainer @@ -31,6 +32,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -128,7 +130,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza emptyFlow(), emptyFlow(), false, - emptyFlow() + emptyFlow(), ) runCurrent() // fade out @@ -150,6 +152,39 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza Truth.assertThat(actual).isEqualTo(0f) } + @Test + @BrokenWithSceneContainer(330311871) + fun blurRadiusIsMaxWhenShadeIsExpanded() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + actualValuesProvider = { values }, + transitionFactory = ::step, + checkInterpolatedValues = false, + ) + } + + @Test + @BrokenWithSceneContainer(330311871) + fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + kosmos.bouncerWindowBlurTestUtil.shadeExpanded(false) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + actualValuesProvider = { values }, + transitionFactory = ::step, + ) + } + private fun step( value: Float, state: TransitionState = TransitionState.RUNNING, @@ -161,7 +196,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza else KeyguardState.PRIMARY_BOUNCER, value = value, transitionState = state, - ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest" + ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest", ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt index c55c27c3b516..b406e6cdef37 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt @@ -21,11 +21,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -138,16 +140,31 @@ class PrimaryBouncerToAodTransitionViewModelTest : SysuiTestCase() { assertThat(deviceEntryParentViewAlpha).isNull() } + @Test + fun blurRadiusGoesToMinImmediately() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + actualValuesProvider = { values }, + transitionFactory = ::step, + checkInterpolatedValues = false, + ) + } + private fun step( value: Float, - state: TransitionState = TransitionState.RUNNING + state: TransitionState = TransitionState.RUNNING, ): TransitionStep { return TransitionStep( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.AOD, value = value, transitionState = state, - ownerName = "PrimaryBouncerToAodTransitionViewModelTest" + ownerName = "PrimaryBouncerToAodTransitionViewModelTest", ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt index 28473b204ce3..a8f0f2f453df 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -122,13 +123,28 @@ class PrimaryBouncerToDozingTransitionViewModelTest : SysuiTestCase() { values.forEach { assertThat(it).isEqualTo(0f) } } + @Test + fun blurRadiusGoesToMinImmediately() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + actualValuesProvider = { values }, + transitionFactory = ::step, + checkInterpolatedValues = false, + ) + } + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.DOZING, value = value, transitionState = state, - ownerName = "PrimaryBouncerToDozingTransitionViewModelTest" + ownerName = "PrimaryBouncerToDozingTransitionViewModelTest", ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt new file mode 100644 index 000000000000..2c6e553dae90 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class PrimaryBouncerToGlanceableHubTransitionViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest by lazy { kosmos.primaryBouncerToGlanceableHubTransitionViewModel } + + @Test + @DisableSceneContainer + fun blurBecomesMinValueImmediately() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + actualValuesProvider = { values }, + transitionFactory = { step, transitionState -> + TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.GLANCEABLE_HUB, + value = step, + transitionState = transitionState, + ownerName = "PrimaryBouncerToGlanceableHubTransitionViewModelTest", + ) + }, + checkInterpolatedValues = false, + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt index 5ec566bab6d5..9e5976f914ed 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt @@ -21,11 +21,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.collect.Range @@ -110,16 +112,47 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { assertThat(bgViewAlpha).isEqualTo(1f) } + @Test + fun blurRadiusGoesFromMaxToMinWhenShadeIsNotExpanded() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + kosmos.bouncerWindowBlurTestUtil.shadeExpanded(false) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS, + actualValuesProvider = { values }, + transitionFactory = ::step, + ) + } + + @Test + fun blurRadiusRemainsAtMaxWhenShadeIsExpanded() = + testScope.runTest { + val values by collectValues(underTest.windowBlurRadius) + kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + + kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), + startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS, + actualValuesProvider = { values }, + transitionFactory = ::step, + checkInterpolatedValues = false, + ) + } + private fun step( value: Float, - state: TransitionState = TransitionState.RUNNING + state: TransitionState = TransitionState.RUNNING, ): TransitionStep { return TransitionStep( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.LOCKSCREEN, value = value, transitionState = state, - ownerName = "PrimaryBouncerToLockscreenTransitionViewModelTest" + ownerName = "PrimaryBouncerToLockscreenTransitionViewModelTest", ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt index 6ec38ba171c3..77be8c718b14 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt @@ -16,9 +16,8 @@ package com.android.systemui.mediarouter.data.repository -import android.media.projection.StopReason -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos @@ -102,7 +101,7 @@ class MediaRouterRepositoryTest : SysuiTestCase() { origin = CastDevice.CastOrigin.MediaRouter, ) - underTest.stopCasting(device, StopReason.STOP_UNKNOWN) + underTest.stopCasting(device) assertThat(castController.lastStoppedDevice).isEqualTo(device) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java index 004aeb069a14..3d9fb0a9be16 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java @@ -56,6 +56,7 @@ import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; +import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; @@ -84,6 +85,7 @@ public class FgsManagerControllerTest extends SysuiTestCase { FakeExecutor mMainExecutor; FakeExecutor mBackgroundExecutor; DeviceConfigProxyFake mDeviceConfigProxyFake; + FakeShadeDialogContextInteractor mFakeShadeDialogContextInteractor; @Mock IActivityManager mIActivityManager; @@ -118,6 +120,7 @@ public class FgsManagerControllerTest extends SysuiTestCase { public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); + mFakeShadeDialogContextInteractor = new FakeShadeDialogContextInteractor(mContext); mDeviceConfigProxyFake = new DeviceConfigProxyFake(); mSystemClock = new FakeSystemClock(); mMainExecutor = new FakeExecutor(mSystemClock); @@ -346,7 +349,6 @@ public class FgsManagerControllerTest extends SysuiTestCase { SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, "true", false); FgsManagerController fmc = new FgsManagerControllerImpl( - mContext, mContext.getResources(), mMainExecutor, mBackgroundExecutor, @@ -359,7 +361,8 @@ public class FgsManagerControllerTest extends SysuiTestCase { mDialogTransitionAnimator, mBroadcastDispatcher, mDumpManager, - mSystemUIDialogFactory + mSystemUIDialogFactory, + mFakeShadeDialogContextInteractor ); fmc.init(); Assert.assertTrue(fmc.getIncludesUserVisibleJobs()); @@ -374,7 +377,6 @@ public class FgsManagerControllerTest extends SysuiTestCase { SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, "false", false); fmc = new FgsManagerControllerImpl( - mContext, mContext.getResources(), mMainExecutor, mBackgroundExecutor, @@ -387,7 +389,8 @@ public class FgsManagerControllerTest extends SysuiTestCase { mDialogTransitionAnimator, mBroadcastDispatcher, mDumpManager, - mSystemUIDialogFactory + mSystemUIDialogFactory, + mFakeShadeDialogContextInteractor ); fmc.init(); Assert.assertFalse(fmc.getIncludesUserVisibleJobs()); @@ -487,7 +490,6 @@ public class FgsManagerControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(BroadcastReceiver.class); FgsManagerController result = new FgsManagerControllerImpl( - mContext, mContext.getResources(), mMainExecutor, mBackgroundExecutor, @@ -500,7 +502,8 @@ public class FgsManagerControllerTest extends SysuiTestCase { mDialogTransitionAnimator, mBroadcastDispatcher, mDumpManager, - mSystemUIDialogFactory + mSystemUIDialogFactory, + mFakeShadeDialogContextInteractor ); result.init(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt index c5a2370adcda..6546a5084ac3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt @@ -35,9 +35,7 @@ import com.android.systemui.qs.footer.FooterActionsTestUtils import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.truth.correspondence.FakeUiEvent import com.android.systemui.truth.correspondence.LogMaker -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.google.common.truth.Truth.assertThat @@ -45,8 +43,11 @@ import kotlinx.coroutines.test.TestCoroutineScheduler import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.any +import org.mockito.kotlin.eq @SmallTest @RunWith(AndroidJUnit4::class) @@ -92,9 +93,10 @@ class FooterActionsInteractorTest : SysuiTestCase() { // Dialog is shown. verify(globalActionsDialogLite) .showOrHideDialog( - /* keyguardShowing= */ false, - /* isDeviceProvisioned= */ true, - expandable, + /* keyguardShowing= */ eq(false), + /* isDeviceProvisioned= */ eq(true), + eq(expandable), + anyInt(), ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt index a8e390c25a4d..a8e390c25a4d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index 165ff7bf08b1..090e2e9e19a9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dump.nano.SystemUIProtoDump import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTile.BooleanState +import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.FakeQSFactory import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.external.CustomTile @@ -154,7 +155,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { TileSpec.create("e"), CUSTOM_TILE_SPEC, TileSpec.create("d"), - TileSpec.create("non_existent") + TileSpec.create("non_existent"), ) tileSpecRepository.setTiles(USER_INFO_0.id, specs) @@ -190,11 +191,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { @Test fun logTileCreated() = testScope.runTest(USER_INFO_0) { - val specs = - listOf( - TileSpec.create("a"), - CUSTOM_TILE_SPEC, - ) + val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() @@ -204,10 +201,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { @Test fun logTileNotFoundInFactory() = testScope.runTest(USER_INFO_0) { - val specs = - listOf( - TileSpec.create("non_existing"), - ) + val specs = listOf(TileSpec.create("non_existing")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() @@ -218,10 +212,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { @Test fun tileNotAvailableDestroyed_logged() = testScope.runTest(USER_INFO_0) { - val specs = - listOf( - TileSpec.create("e"), - ) + val specs = listOf(TileSpec.create("e")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() @@ -229,7 +220,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { verify(logger) .logTileDestroyed( specs[0], - QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE + QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE, ) } @@ -238,11 +229,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { testScope.runTest(USER_INFO_0) { val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) - val specs = - listOf( - TileSpec.create("a"), - TileSpec.create("e"), - ) + val specs = listOf(TileSpec.create("a"), TileSpec.create("e")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) assertThat(tiles).isEqualTo(listOf(TileSpec.create("a"))) @@ -266,10 +253,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { testScope.runTest(USER_INFO_0) { val tiles by collectLastValue(underTest.currentTiles) - val specs = - listOf( - TileSpec.create("a"), - ) + val specs = listOf(TileSpec.create("a")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) val originalTileA = tiles!![0].tile @@ -299,7 +283,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { verify(logger) .logTileDestroyed( TileSpec.create("c"), - QSPipelineLogger.TileDestroyedReason.TILE_REMOVED + QSPipelineLogger.TileDestroyedReason.TILE_REMOVED, ) } @@ -325,7 +309,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { verify(logger) .logTileDestroyed( TileSpec.create("a"), - QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE + QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE, ) assertThat(tiles?.size).isEqualTo(1) @@ -370,7 +354,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { verify(logger) .logTileDestroyed( specs0[0], - QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER + QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER, ) } @@ -418,21 +402,12 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { testScope.runTest(USER_INFO_0) { val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val spec = TileSpec.create("a") - val currentSpecs = - listOf( - TileSpec.create("b"), - TileSpec.create("c"), - ) + val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c")) tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) underTest.addTile(spec, position = 1) - val expectedSpecs = - listOf( - TileSpec.create("b"), - spec, - TileSpec.create("c"), - ) + val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c")) assertThat(tiles).isEqualTo(expectedSpecs) } @@ -442,11 +417,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id)) val spec = TileSpec.create("a") - val currentSpecs = - listOf( - TileSpec.create("b"), - TileSpec.create("c"), - ) + val currentSpecs = listOf(TileSpec.create("b"), TileSpec.create("c")) tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs) @@ -455,12 +426,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { assertThat(tiles0).isEqualTo(currentSpecs) - val expectedSpecs = - listOf( - TileSpec.create("b"), - spec, - TileSpec.create("c"), - ) + val expectedSpecs = listOf(TileSpec.create("b"), spec, TileSpec.create("c")) assertThat(tiles1).isEqualTo(expectedSpecs) } @@ -515,11 +481,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id)) val currentSpecs = - listOf( - TileSpec.create("a"), - TileSpec.create("b"), - TileSpec.create("c"), - ) + listOf(TileSpec.create("a"), TileSpec.create("b"), TileSpec.create("c")) tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs) @@ -557,11 +519,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) runCurrent() - val newSpecs = - listOf( - otherCustomTileSpec, - TileSpec.create("a"), - ) + val newSpecs = listOf(otherCustomTileSpec, TileSpec.create("a")) underTest.setTiles(newSpecs) runCurrent() @@ -615,7 +573,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { tileSpecRepository.setTiles( USER_INFO_0.id, - listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) + listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC), ) val newTileA = tiles!![0].tile assertThat(tileA).isSameInstanceAs(newTileA) @@ -650,7 +608,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { installedTilesPackageRepository.setInstalledPackagesForUser( USER_INFO_0.id, - setOf(TEST_COMPONENT) + setOf(TEST_COMPONENT), ) assertThat(tiles!!.size).isEqualTo(3) @@ -676,10 +634,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { @Test fun changeInPackagesTiles_doesntTriggerUserChange_logged() = testScope.runTest(USER_INFO_0) { - val specs = - listOf( - TileSpec.create("a"), - ) + val specs = listOf(TileSpec.create("a")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() // Settled on the same list of tiles. @@ -691,7 +646,6 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0) } - @Test fun getTileDetails() = testScope.runTest(USER_INFO_0) { @@ -711,11 +665,19 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { assertThat(tiles!![2].spec).isEqualTo(tileNoDetails) (tiles!![2].tile as FakeQSTile).hasDetailsViewModel = false - assertThat(tiles!![0].tile.detailsViewModel.getTitle()).isEqualTo("a") - assertThat(tiles!![1].tile.detailsViewModel.getTitle()).isEqualTo("b") - assertThat(tiles!![2].tile.detailsViewModel).isNull() - } + var currentModel: TileDetailsViewModel? = null + val setCurrentModel = { model: TileDetailsViewModel? -> currentModel = model } + tiles!![0].tile.getDetailsViewModel(setCurrentModel) + assertThat(currentModel?.getTitle()).isEqualTo("a") + + currentModel = null + tiles!![1].tile.getDetailsViewModel(setCurrentModel) + assertThat(currentModel?.getTitle()).isEqualTo("b") + currentModel = null + tiles!![2].tile.getDetailsViewModel(setCurrentModel) + assertThat(currentModel).isNull() + } private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) { this.state = state @@ -745,7 +707,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { customTileAddedRepository.setTileAdded( CUSTOM_TILE_SPEC.componentName, currentUser, - true + true, ) } in VALID_TILES -> FakeQSTile(currentUser, available = spec !in unavailableTiles) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java index 31a627fe0667..9f12b189d76a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -20,7 +20,6 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; @@ -31,7 +30,6 @@ import static org.mockito.Mockito.when; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.media.projection.MediaProjectionInfo; -import android.media.projection.StopReason; import android.os.Handler; import android.service.quicksettings.Tile; import android.testing.TestableLooper; @@ -338,8 +336,7 @@ public class CastTileTest extends SysuiTestCase { mCastTile.handleClick(null /* view */); mTestableLooper.processAllMessages(); - verify(mController, times(1)) - .stopCasting(same(device), eq(StopReason.STOP_QS_TILE)); + verify(mController, times(1)).stopCasting(same(device)); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt index fbbdc46a7873..0e823ccf1df5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.DataSaverController import com.android.systemui.util.mockito.whenever @@ -96,6 +97,7 @@ class DataSaverTileTest(flags: FlagsParameterization) : SysuiTestCase() { dataSaverController, mDialogTransitionAnimator, systemUIDialogFactory, + FakeShadeDialogContextInteractor(mContext), ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index fc1d73b62abd..0fd7c9876f25 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -27,13 +27,11 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Dialog; -import android.media.projection.StopReason; import android.os.Handler; import android.platform.test.flag.junit.FlagsParameterization; import android.service.quicksettings.Tile; @@ -236,7 +234,7 @@ public class ScreenRecordTileTest extends SysuiTestCase { mTile.handleClick(null /* view */); - verify(mController, times(1)).stopRecording(eq(StopReason.STOP_QS_TILE)); + verify(mController, times(1)).stopRecording(); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt index 778c73fd8638..0b56d7b64aab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor import android.app.Dialog -import android.media.projection.StopReason import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -93,7 +92,7 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { underTest.handleInput(QSTileInputTestKtx.click(recordingModel)) - verify(recordingController).stopRecording(eq(StopReason.STOP_QS_TILE)) + verify(recordingController).stopRecording() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt index 039a1dc05c79..518a0a5a1446 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PseudoGridView import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.qs.tiles.UserDetailView +import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq @@ -84,6 +85,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { mDialogTransitionAnimator, uiEventLogger, dialogFactory, + FakeShadeDialogContextInteractor(mContext), ) } @@ -91,32 +93,32 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { fun showDialog_callsDialogShow() { val launchController = mock<DialogTransitionAnimator.Controller>() `when`(launchExpandable.dialogTransitionController(any())).thenReturn(launchController) - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(mDialogTransitionAnimator).show(eq(dialog), eq(launchController), anyBoolean()) verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN) } @Test fun dialog_showForAllUsers() { - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog).setShowForAllUsers(true) } @Test fun dialog_cancelOnTouchOutside() { - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog).setCanceledOnTouchOutside(true) } @Test fun adapterAndGridLinked() { - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>()) } @Test fun doneButtonLogsCorrectly() { - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor)) @@ -129,7 +131,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { fun clickSettingsButton_noFalsing_opensSettings() { `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false) - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog) .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */) @@ -150,7 +152,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { fun clickSettingsButton_Falsing_notOpensSettings() { `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true) - controller.showDialog(context, launchExpandable) + controller.showDialog(launchExpandable) verify(dialog) .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index e56b965d9402..3187cca6ca45 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -37,7 +37,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.EnableSceneContainer @@ -48,9 +47,9 @@ import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.currentValue -import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.verifyCurrent import com.android.systemui.lifecycle.activateIn import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest @@ -77,12 +76,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.verify /** * Integration test cases for the Scene Framework. @@ -137,10 +133,10 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneContainerViewModel.activateIn(testScope) assertWithMessage("Initial scene key mismatch!") - .that(sceneContainerViewModel.currentScene.value) + .that(currentValue(sceneContainerViewModel.currentScene)) .isEqualTo(sceneContainerConfig.initialSceneKey) assertWithMessage("Initial scene container visibility mismatch!") - .that(sceneContainerViewModel.isVisible) + .that(currentValue { sceneContainerViewModel.isVisible }) .isTrue() } @@ -337,7 +333,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(bouncerActionButton) .isNotNull() kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!) - runCurrent() // TODO(b/369765704): Assert that an activity was started once we use ActivityStarter. } @@ -358,9 +353,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(bouncerActionButton) .isNotNull() kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!) - runCurrent() - verify(mockTelecomManager).showInCallScreen(any()) + verifyCurrent(mockTelecomManager).showInCallScreen(any()) } @Test @@ -413,7 +407,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { * the UI must gradually transition between scenes. */ private fun Kosmos.getCurrentSceneInUi(): SceneKey { - return when (val state = transitionState.value) { + return when (val state = currentValue(transitionState)) { is ObservableTransitionState.Idle -> state.currentScene is ObservableTransitionState.Transition.ChangeScene -> state.fromScene is ObservableTransitionState.Transition.ShowOrHideOverlay -> state.currentScene @@ -436,7 +430,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { // is not an observable that can trigger a new evaluation. fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen) fakeAuthenticationRepository.setAuthenticationMethod(authMethod) - testScope.runCurrent() } /** Emulates a phone call in progress. */ @@ -447,7 +440,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { setIsInCall(true) setCallState(TelephonyManager.CALL_STATE_OFFHOOK) } - testScope.runCurrent() } /** @@ -480,24 +472,21 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) - testScope.runCurrent() // Report progress of transition. - while (progressFlow.value < 1f) { + while (currentValue(progressFlow) < 1f) { progressFlow.value += 0.2f - testScope.runCurrent() } // End the transition and report the change. transitionState.value = ObservableTransitionState.Idle(to) fakeSceneDataSource.unpause(force = true) - testScope.runCurrent() assertWithMessage("Visibility mismatch after scene transition from $from to $to!") - .that(sceneContainerViewModel.isVisible) + .that(currentValue { sceneContainerViewModel.isVisible }) .isEqualTo(expectedVisible) - assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to) + assertThat(currentValue(sceneContainerViewModel.currentScene)).isEqualTo(to) bouncerSceneJob = if (to == Scenes.Bouncer) { @@ -510,7 +499,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { bouncerSceneJob?.cancel() null } - testScope.runCurrent() } /** @@ -556,13 +544,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ) powerInteractor.setAwakeForTest() - testScope.runCurrent() } /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */ private fun Kosmos.unlockDevice() { assertWithMessage("Cannot unlock a device that's already unlocked!") - .that(deviceEntryInteractor.isUnlocked.value) + .that(currentValue(deviceEntryInteractor.isUnlocked)) .isFalse() emulateUserDrivenTransition(Scenes.Bouncer) @@ -595,7 +582,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { pinBouncerViewModel.onPinButtonClicked(digit) } pinBouncerViewModel.onAuthenticateButtonClicked() - testScope.runCurrent() } /** @@ -625,26 +611,23 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } pinBouncerViewModel.onAuthenticateButtonClicked() fakeMobileConnectionsRepository.isAnySimSecure.value = false - testScope.runCurrent() setAuthMethod(authMethodAfterSimUnlock, enableLockscreen) - testScope.runCurrent() } /** Changes device wakefulness state from asleep to awake, going through intermediary states. */ private fun Kosmos.wakeUpDevice() { - val wakefulnessModel = powerInteractor.detailedWakefulness.value + val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness) assertWithMessage("Cannot wake up device as it's already awake!") .that(wakefulnessModel.isAwake()) .isFalse() powerInteractor.setAwakeForTest() - testScope.runCurrent() } /** Changes device wakefulness state from awake to asleep, going through intermediary states. */ private suspend fun Kosmos.putDeviceToSleep(waitForLock: Boolean = true) { - val wakefulnessModel = powerInteractor.detailedWakefulness.value + val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness) assertWithMessage("Cannot put device to sleep as it's already asleep!") .that(wakefulnessModel.isAwake()) .isTrue() @@ -659,22 +642,18 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ) .toLong() ) - } else { - testScope.runCurrent() } } /** Emulates the dismissal of the IME (soft keyboard). */ private fun Kosmos.dismissIme() { - (bouncerSceneContentViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let { - it.onImeDismissed() - testScope.runCurrent() - } + (currentValue(bouncerSceneContentViewModel.authMethodViewModel) + as? PasswordBouncerViewModel) + ?.let { it.onImeDismissed() } } private fun Kosmos.introduceLockedSim() { setAuthMethod(AuthenticationMethodModel.Sim) fakeMobileConnectionsRepository.isAnySimSecure.value = true - testScope.runCurrent() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt index f86337ec63dc..396f531b7e1b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt @@ -20,7 +20,7 @@ import android.platform.test.flag.junit.FlagsParameterization import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_EXAMPLE_FLAG -import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR +import com.android.systemui.Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.flags.andSceneContainer @@ -66,7 +66,7 @@ internal class SceneContainerFlagParameterizationTest : SysuiTestCase() { @Test fun oneDependencyAndSceneContainer() { - val dependentFlag = FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR + val dependentFlag = FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN val result = FlagsParameterization.allCombinationsOf(dependentFlag).andSceneContainer() Truth.assertThat(result).hasSize(3) Truth.assertThat(result[0].mOverrides[dependentFlag]).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java index 50fa9d29659d..a6a1d4a05dc7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java @@ -41,7 +41,6 @@ import android.app.ActivityOptions.LaunchCookie; import android.app.Notification; import android.app.NotificationManager; import android.content.Intent; -import android.media.projection.StopReason; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -200,16 +199,16 @@ public class RecordingServiceTest extends SysuiTestCase { public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException { doReturn(true).when(mController).isRecording(); - mRecordingService.onStopped(StopReason.STOP_UNKNOWN); + mRecordingService.onStopped(); - verify(mScreenMediaRecorder).end(eq(StopReason.STOP_UNKNOWN)); + verify(mScreenMediaRecorder).end(); } @Test public void testOnSystemRequestedStop_recordingInProgress_updatesState() { doReturn(true).when(mController).isRecording(); - mRecordingService.onStopped(StopReason.STOP_UNKNOWN); + mRecordingService.onStopped(); assertUpdateState(false); } @@ -219,18 +218,18 @@ public class RecordingServiceTest extends SysuiTestCase { throws IOException { doReturn(false).when(mController).isRecording(); - mRecordingService.onStopped(StopReason.STOP_UNKNOWN); + mRecordingService.onStopped(); - verify(mScreenMediaRecorder, never()).end(StopReason.STOP_UNKNOWN); + verify(mScreenMediaRecorder, never()).end(); } @Test public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording() throws IOException { doReturn(true).when(mController).isRecording(); - doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(StopReason.STOP_UNKNOWN); + doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(); - mRecordingService.onStopped(StopReason.STOP_UNKNOWN); + mRecordingService.onStopped(); verify(mScreenMediaRecorder).release(); } @@ -239,7 +238,7 @@ public class RecordingServiceTest extends SysuiTestCase { public void testOnSystemRequestedStop_whenRecordingInProgress_showsNotifications() { doReturn(true).when(mController).isRecording(); - mRecordingService.onStopped(StopReason.STOP_UNKNOWN); + mRecordingService.onStopped(); // Processing notification ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class); @@ -272,9 +271,9 @@ public class RecordingServiceTest extends SysuiTestCase { public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_showsErrorNotification() throws IOException { doReturn(true).when(mController).isRecording(); - doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(anyInt()); + doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(); - mRecordingService.onStopped(StopReason.STOP_UNKNOWN); + mRecordingService.onStopped(); verify(mRecordingService).createErrorSavingNotification(any()); ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class); @@ -290,9 +289,9 @@ public class RecordingServiceTest extends SysuiTestCase { public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording() throws IOException { doReturn(true).when(mController).isRecording(); - doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end(anyInt()); + doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end(); - assertThrows(Throwable.class, () -> mRecordingService.onStopped(StopReason.STOP_UNKNOWN)); + assertThrows(Throwable.class, () -> mRecordingService.onStopped()); verify(mScreenMediaRecorder).release(); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt index ade5941d010d..aceea909e595 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.screenrecord.data.repository -import android.media.projection.StopReason import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -32,7 +31,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -128,8 +126,8 @@ class ScreenRecordRepositoryTest : SysuiTestCase() { @Test fun stopRecording_invokesController() = testScope.runTest { - underTest.stopRecording(StopReason.STOP_PRIVACY_CHIP) + underTest.stopRecording() - verify(recordingController).stopRecording(eq(StopReason.STOP_PRIVACY_CHIP)) + verify(recordingController).stopRecording() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 0d8d57e52dbf..d3b58287e961 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -68,7 +68,6 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.LatencyTracker; -import com.android.keyguard.EmptyLockIconViewController; import com.android.keyguard.KeyguardClockSwitch; import com.android.keyguard.KeyguardClockSwitchController; import com.android.keyguard.KeyguardSliceViewController; @@ -99,7 +98,6 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewConfigurator; import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; @@ -108,7 +106,6 @@ import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingOb import com.android.systemui.keyguard.ui.view.KeyguardRootView; 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.KeyguardTouchHandlingViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; @@ -167,8 +164,6 @@ 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.KeyguardBottomAreaView; -import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; @@ -228,10 +223,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected CentralSurfaces mCentralSurfaces; @Mock protected NotificationStackScrollLayout mNotificationStackScrollLayout; - @Mock protected KeyguardBottomAreaView mKeyguardBottomArea; - @Mock protected KeyguardBottomAreaViewController mKeyguardBottomAreaViewController; @Mock protected ViewPropertyAnimator mViewPropertyAnimator; - @Mock protected KeyguardBottomAreaView mQsFrame; @Mock protected HeadsUpManager mHeadsUpManager; @Mock protected NotificationGutsManager mGutsManager; @Mock protected KeyguardStatusBarView mKeyguardStatusBar; @@ -270,7 +262,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardUserSwitcherController mKeyguardUserSwitcherController; @Mock protected KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; - @Mock protected EmptyLockIconViewController mLockIconViewController; @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController; @@ -317,7 +308,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected ViewGroup mQsHeader; @Mock protected ViewParent mViewParent; @Mock protected ViewTreeObserver mViewTreeObserver; - @Mock protected KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; @Mock protected DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; @Mock protected OccludedToLockscreenTransitionViewModel @@ -352,7 +342,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector; protected final int mMaxUdfpsBurnInOffsetY = 5; protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); - protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; protected KeyguardClockInteractor mKeyguardClockInteractor; protected FakeKeyguardRepository mFakeKeyguardRepository; protected FakeKeyguardClockRepository mFakeKeyguardClockRepository; @@ -397,13 +386,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false); mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); - mMainDispatcher = getMainDispatcher(); KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps = KeyguardInteractorFactory.create(); mFakeKeyguardRepository = keyguardInteractorDeps.getRepository(); - mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository); mFakeKeyguardClockRepository = new FakeKeyguardClockRepository(); mKeyguardClockInteractor = mKosmos.getKeyguardClockInteractor(); mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor(); @@ -500,9 +486,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { 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(mViewPropertyAnimator); when(mView.animate()).thenReturn(mViewPropertyAnimator); when(mKeyguardStatusView.animate()).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.translationX(anyFloat())).thenReturn(mViewPropertyAnimator); @@ -513,7 +496,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mViewPropertyAnimator.setListener(any())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.setUpdateListener(any())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.withEndAction(any())).thenReturn(mViewPropertyAnimator); - when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame); when(mView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); ViewGroup rootView = mock(ViewGroup.class); @@ -647,8 +629,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { .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); doAnswer(invocation -> { @@ -720,7 +700,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mMediaDataManager, mNotificationShadeDepthController, mAmbientState, - mLockIconViewController, mKeyguardMediaController, mTapAgainViewController, mNavigationModeController, @@ -736,15 +715,12 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mShadeRepository, mSysUIUnfoldComponent, mSysUiState, - () -> mKeyguardBottomAreaViewController, mKeyguardUnlockAnimationController, mKeyguardIndicationController, mNotificationListContainer, mNotificationStackSizeCalculator, mUnlockedScreenOffAnimationController, systemClock, - mKeyguardBottomAreaViewModel, - mKeyguardBottomAreaInteractor, mKeyguardClockInteractor, mAlternateBouncerInteractor, mDreamingToLockscreenTransitionViewModel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt index 97441f01bcf5..5289554e9e18 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt @@ -28,7 +28,6 @@ import androidx.test.filters.SmallTest import com.android.internal.util.CollectionUtils import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.systemui.Flags -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.StatusBarState.SHADE @@ -215,31 +214,4 @@ class NotificationPanelViewControllerWithCoroutinesTest : } advanceUntilIdle() } - - @Test - fun onLayoutChange_shadeCollapsed_bottomAreaAlphaIsZero() = runTest { - // GIVEN bottomAreaShadeAlpha was updated before - mNotificationPanelViewController.maybeAnimateBottomAreaAlpha() - - // WHEN a layout change is triggered with the shade being closed - triggerLayoutChange() - - // THEN the bottomAreaAlpha is zero - val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha) - assertThat(bottomAreaAlpha).isEqualTo(0f) - } - - @Test - fun onShadeExpanded_bottomAreaAlphaIsFullyOpaque() = runTest { - // GIVEN bottomAreaShadeAlpha was updated before - mNotificationPanelViewController.maybeAnimateBottomAreaAlpha() - - // WHEN the shade expanded - val transitionDistance = mNotificationPanelViewController.maxPanelTransitionDistance - mNotificationPanelViewController.expandedHeight = transitionDistance.toFloat() - - // THEN the bottomAreaAlpha is fully opaque - val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha) - assertThat(bottomAreaAlpha).isEqualTo(1f) - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 5d1ce7c5ca05..0361ffe475a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -212,18 +212,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { } @Test - @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testDragDownHelperCalledWhenDraggingDown() = - testScope.runTest { - whenever(dragDownHelper.isDraggingDown).thenReturn(true) - val now = SystemClock.elapsedRealtime() - val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */) - underTest.onTouchEvent(ev) - verify(dragDownHelper).onTouchEvent(ev) - ev.recycle() - } - - @Test fun testNoInterceptTouch() = testScope.runTest { captureInteractionEventHandler() @@ -253,6 +241,16 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { verify(configurationForwarder).onConfigurationChanged(eq(config)) } + @Test + @EnableFlags(AConfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND) + fun onMovedToDisplay_configForwarderSet_propagatesConfig() { + val config = Configuration() + + underTest.onMovedToDisplay(1, config) + + verify(configurationForwarder).dispatchOnMovedToDisplay(eq(1), eq(config)) + } + private fun captureInteractionEventHandler() { verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) interactionEventHandler = interactionEventHandlerCaptor.value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt index aa8b4f136683..4423426945eb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -117,7 +117,6 @@ class DefaultClockProviderTest : SysuiTestCase() { verify(mockLargeClockView).onTimeZoneChanged(notNull()) verify(mockSmallClockView).refreshTime() verify(mockLargeClockView).refreshTime() - verify(mockLargeClockView).setLayoutParams(any()) } @Test @@ -163,7 +162,6 @@ class DefaultClockProviderTest : SysuiTestCase() { clock.largeClock.events.onFontSettingChanged(200f) verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f)) - verify(mockLargeClockView).setLayoutParams(any()) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt index 63efc55e1a09..9ad1f409a8ea 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt @@ -26,7 +26,6 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel @@ -50,7 +49,6 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { activeNotificationModel( key = "notif1", statusBarChipIcon = icon, - whenTime = 5432, promotedContent = PROMOTED_CONTENT, ) @@ -60,7 +58,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { assertThat(latest!!.key).isEqualTo("notif1") assertThat(latest!!.statusBarChipIconView).isEqualTo(icon) - assertThat(latest!!.whenTime).isEqualTo(5432) + assertThat(latest!!.promotedContent).isEqualTo(PROMOTED_CONTENT) } @Test @@ -83,14 +81,12 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { activeNotificationModel( key = "notif1", statusBarChipIcon = newIconView, - whenTime = 6543, promotedContent = PROMOTED_CONTENT, ) ) assertThat(latest!!.key).isEqualTo("notif1") assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView) - assertThat(latest!!.whenTime).isEqualTo(6543) } @Test @@ -174,22 +170,14 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { activeNotificationModel( key = "notif1", statusBarChipIcon = null, - whenTime = 123L, promotedContent = PROMOTED_CONTENT, ) ) val latest by collectLastValue(underTest.notificationChip) - assertThat(latest) - .isEqualTo( - NotificationChipModel( - "notif1", - statusBarChipIconView = null, - whenTime = 123L, - promotedContent = PROMOTED_CONTENT, - ) - ) + assertThat(latest).isNotNull() + assertThat(latest!!.key).isEqualTo("notif1") } @Test @@ -234,20 +222,12 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { activeNotificationModel( key = "notif1", statusBarChipIcon = null, - whenTime = 123L, promotedContent = PROMOTED_CONTENT, ) ) - assertThat(latest) - .isEqualTo( - NotificationChipModel( - key = "notif1", - statusBarChipIconView = null, - whenTime = 123L, - promotedContent = PROMOTED_CONTENT, - ) - ) + assertThat(latest).isNotNull() + assertThat(latest!!.key).isEqualTo("notif1") } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index d4910cec3df4..165e943a0cc0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -116,7 +116,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { assertThat(latest).hasSize(1) val chip = latest!![0] - assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown::class.java) assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon)) } @@ -139,7 +139,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { assertThat(latest).hasSize(1) val chip = latest!![0] - assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown::class.java) assertThat(chip.icon) .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notifKey)) } @@ -242,15 +242,158 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + fun chips_hasShortCriticalText_usesTextInsteadOfTime() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.shortCriticalText = "Arrived" + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.BasicTime, + ) + } + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = promotedContentBuilder.build(), + ) + ) + ) + + assertThat(latest).hasSize(1) + assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Shown.Text::class.java) + assertThat((latest!![0] as OngoingActivityChipModel.Shown.Text).text) + .isEqualTo("Arrived") + } + + @Test + fun chips_noTime_isIconOnly() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { this.time = null } + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = promotedContentBuilder.build(), + ) + ) + ) + + assertThat(latest).hasSize(1) + assertThat(latest!![0]) + .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + } + + @Test + fun chips_basicTime_isShortTimeDelta() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.BasicTime, + ) + } + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = promotedContentBuilder.build(), + ) + ) + ) + + assertThat(latest).hasSize(1) + assertThat(latest!![0]) + .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + } + + @Test + fun chips_countUpTime_isTimer() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.CountUp, + ) + } + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = promotedContentBuilder.build(), + ) + ) + ) + + assertThat(latest).hasSize(1) + assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + } + + @Test + fun chips_countDownTime_isTimer() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.CountDown, + ) + } + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = promotedContentBuilder.build(), + ) + ) + ) + + assertThat(latest).hasSize(1) + assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + } + + @Test fun chips_noHeadsUp_showsTime() = kosmos.runTest { val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.BasicTime, + ) + } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = mock<StatusBarIconView>(), - promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + promotedContent = promotedContentBuilder.build(), ) ) ) @@ -267,12 +410,21 @@ class NotifChipsViewModelTest : SysuiTestCase() { fun chips_hasHeadsUpByUser_onlyShowsIcon() = kosmos.runTest { val latest by collectLastValue(underTest.chips) + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.time = + PromotedNotificationContentModel.When( + time = 6543L, + mode = PromotedNotificationContentModel.When.Mode.BasicTime, + ) + } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = mock<StatusBarIconView>(), - promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + promotedContent = promotedContentBuilder.build(), ) ) ) @@ -324,15 +476,11 @@ class NotifChipsViewModelTest : SysuiTestCase() { companion object { fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) { - assertThat(latest) - .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) assertThat((latest as OngoingActivityChipModel.Shown).icon) .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon)) } fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) { - assertThat(latest) - .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) assertThat((latest as OngoingActivityChipModel.Shown).icon) .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(expectedKey)) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt index 6736ccf739ce..3359db0a22e6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt @@ -43,7 +43,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) -class PromotedNotificationContentExtractorTest : SysuiTestCase() { +class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { private val kosmos = testKosmos() private val provider = @@ -54,7 +54,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun shouldNotExtract_bothFlagsDisabled() { - val notif = createEntry().also { provider.promotedEntries.add(it) } + val notif = createEntry() val content = extractContent(notif) assertThat(content).isNull() } @@ -63,7 +63,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @EnableFlags(PromotedNotificationUi.FLAG_NAME) @DisableFlags(StatusBarNotifChips.FLAG_NAME) fun shouldExtract_promotedNotificationUiFlagEnabled() { - val entry = createEntry().also { provider.promotedEntries.add(it) } + val entry = createEntry() val content = extractContent(entry) assertThat(content).isNotNull() } @@ -72,7 +72,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @EnableFlags(StatusBarNotifChips.FLAG_NAME) @DisableFlags(PromotedNotificationUi.FLAG_NAME) fun shouldExtract_statusBarNotifChipsFlagEnabled() { - val entry = createEntry().also { provider.promotedEntries.add(it) } + val entry = createEntry() val content = extractContent(entry) assertThat(content).isNotNull() } @@ -80,7 +80,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun shouldExtract_bothFlagsEnabled() { - val entry = createEntry().also { provider.promotedEntries.add(it) } + val entry = createEntry() val content = extractContent(entry) assertThat(content).isNotNull() } @@ -88,21 +88,19 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun shouldNotExtract_providerDidNotPromote() { - val entry = createEntry().also { provider.promotedEntries.remove(it) } + val entry = createEntry(promoted = false) val content = extractContent(entry) assertThat(content).isNull() } @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) - fun extractContent_commonFields() { - val entry = - createEntry { - setSubText(TEST_SUB_TEXT) - setContentTitle(TEST_CONTENT_TITLE) - setContentText(TEST_CONTENT_TEXT) - } - .also { provider.promotedEntries.add(it) } + fun extractsContent_commonFields() { + val entry = createEntry { + setSubText(TEST_SUB_TEXT) + setContentTitle(TEST_CONTENT_TITLE) + setContentText(TEST_CONTENT_TEXT) + } val content = extractContent(entry) @@ -114,9 +112,50 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) - fun extractContent_fromBigPictureStyle() { - val entry = - createEntry { setStyle(BigPictureStyle()) }.also { provider.promotedEntries.add(it) } + @DisableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + fun extractContent_apiFlagOff_shortCriticalTextNotExtracted() { + val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) } + + val content = extractContent(entry) + + assertThat(content).isNotNull() + assertThat(content?.text).isNull() + } + + @Test + @EnableFlags( + PromotedNotificationUi.FLAG_NAME, + StatusBarNotifChips.FLAG_NAME, + android.app.Flags.FLAG_API_RICH_ONGOING, + ) + fun extractContent_apiFlagOn_shortCriticalTextExtracted() { + val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) } + + val content = extractContent(entry) + + assertThat(content).isNotNull() + assertThat(content?.shortCriticalText).isEqualTo(TEST_SHORT_CRITICAL_TEXT) + } + + @Test + @EnableFlags( + PromotedNotificationUi.FLAG_NAME, + StatusBarNotifChips.FLAG_NAME, + android.app.Flags.FLAG_API_RICH_ONGOING, + ) + fun extractContent_noShortCriticalTextSet_textIsNull() { + val entry = createEntry { setShortCriticalText(null) } + + val content = extractContent(entry) + + assertThat(content).isNotNull() + assertThat(content?.shortCriticalText).isNull() + } + + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) + fun extractsContent_fromBigPictureStyle() { + val entry = createEntry { setStyle(BigPictureStyle()) } val content = extractContent(entry) @@ -127,8 +166,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun extractContent_fromBigTextStyle() { - val entry = - createEntry { setStyle(BigTextStyle()) }.also { provider.promotedEntries.add(it) } + val entry = createEntry { setStyle(BigTextStyle()) } val content = extractContent(entry) @@ -140,11 +178,13 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun extractContent_fromCallStyle() { val hangUpIntent = - PendingIntent.getBroadcast(context, 0, Intent("hangup"), PendingIntent.FLAG_IMMUTABLE) - - val entry = - createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) } - .also { provider.promotedEntries.add(it) } + PendingIntent.getBroadcast( + context, + 0, + Intent("hangup_action"), + PendingIntent.FLAG_IMMUTABLE, + ) + val entry = createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) } val content = extractContent(entry) @@ -155,11 +195,9 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun extractContent_fromProgressStyle() { - val entry = - createEntry { - setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75)) - } - .also { provider.promotedEntries.add(it) } + val entry = createEntry { + setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75)) + } val content = extractContent(entry) @@ -173,13 +211,9 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { @Test @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun extractContent_fromIneligibleStyle() { - val entry = - createEntry { - setStyle( - MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON) - ) - } - .also { provider.promotedEntries.add(it) } + val entry = createEntry { + setStyle(MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON)) + } val content = extractContent(entry) @@ -192,15 +226,21 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() { return underTest.extractContent(entry, recoveredBuilder) } - private fun createEntry(builderBlock: Notification.Builder.() -> Unit = {}): NotificationEntry { - val notif = Notification.Builder(context, "a").also(builderBlock).build() - return NotificationEntryBuilder().setNotification(notif).build() + private fun createEntry( + promoted: Boolean = true, + builderBlock: Notification.Builder.() -> Unit = {}, + ): NotificationEntry { + val notif = Notification.Builder(context, "channel").also(builderBlock).build() + return NotificationEntryBuilder().setNotification(notif).build().also { + provider.shouldPromoteForEntry[it] = promoted + } } companion object { private const val TEST_SUB_TEXT = "sub text" private const val TEST_CONTENT_TITLE = "content title" private const val TEST_CONTENT_TEXT = "content text" + private const val TEST_SHORT_CRITICAL_TEXT = "short" private const val TEST_PERSON_NAME = "person name" private const val TEST_PERSON_KEY = "person key" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index 6eb2764165b5..cdc8bc1e6cbb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -30,7 +30,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -61,7 +60,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor; +import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi; import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; @@ -105,7 +104,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { @Mock private NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; @Mock private HeadsUpStyleProvider mHeadsUpStyleProvider; @Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory; - @Mock private PromotedNotificationContentExtractor mPromotedNotificationContentExtractor; + private final FakePromotedNotificationContentExtractor mPromotedNotificationContentExtractor = + new FakePromotedNotificationContentExtractor(); private final SmartReplyStateInflater mSmartReplyStateInflater = new SmartReplyStateInflater() { @@ -395,12 +395,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { public void testExtractsPromotedContent_notWhenBothFlagsDisabled() throws Exception { final PromotedNotificationContentModel content = new PromotedNotificationContentModel.Builder("key").build(); - when(mPromotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, never()).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyZeroExtractCalls(); } @Test @@ -410,12 +409,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { throws Exception { final PromotedNotificationContentModel content = new PromotedNotificationContentModel.Builder("key").build(); - when(mPromotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyOneExtractCall(); assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel()); } @@ -425,12 +423,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase { public void testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() throws Exception { final PromotedNotificationContentModel content = new PromotedNotificationContentModel.Builder("key").build(); - when(mPromotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyOneExtractCall(); assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel()); } @@ -439,23 +436,22 @@ public class NotificationContentInflaterTest extends SysuiTestCase { public void testExtractsPromotedContent_whenBothFlagsEnabled() throws Exception { final PromotedNotificationContentModel content = new PromotedNotificationContentModel.Builder("key").build(); - when(mPromotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyOneExtractCall(); assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel()); } @Test @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME}) public void testExtractsPromotedContent_null() throws Exception { - when(mPromotedNotificationContentExtractor.extractContent(any(), any())).thenReturn(null); + mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), null); inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); - verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any()); + mPromotedNotificationContentExtractor.verifyOneExtractCall(); assertNull(mRow.getEntry().getPromotedNotificationContentModel()); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt index 18517998096a..9fb72fba4d71 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt @@ -37,7 +37,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.ConversationNotificationProcessor import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor +import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams @@ -67,7 +67,6 @@ import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.never import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -110,7 +109,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { return inflatedSmartReplyState } } - private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor = mock() + private val promotedNotificationContentExtractor = FakePromotedNotificationContentExtractor() @Before fun setUp() { @@ -234,7 +233,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { packageContext = mContext, remoteViews = NewRemoteViews(), contentModel = NotificationContentModel(headsUpStatusBarModel), - extractedPromotedNotificationContentModel = null, + promotedContent = null, ) val countDownLatch = CountDownLatch(1) NotificationRowContentBinderImpl.applyRemoteView( @@ -475,12 +474,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun testExtractsPromotedContent_notWhenBothFlagsDisabled() { val content = PromotedNotificationContentModel.Builder("key").build() - whenever(promotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content) + promotedNotificationContentExtractor.resetForEntry(row.entry, content) inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) - verify(promotedNotificationContentExtractor, never()).extractContent(any(), any()) + promotedNotificationContentExtractor.verifyZeroExtractCalls() } @Test @@ -488,12 +486,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @DisableFlags(StatusBarNotifChips.FLAG_NAME) fun testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled() { val content = PromotedNotificationContentModel.Builder("key").build() - whenever(promotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content) + promotedNotificationContentExtractor.resetForEntry(row.entry, content) inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) - verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any()) + promotedNotificationContentExtractor.verifyOneExtractCall() Assert.assertEquals(content, row.entry.promotedNotificationContentModel) } @@ -502,12 +499,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @DisableFlags(PromotedNotificationUi.FLAG_NAME) fun testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() { val content = PromotedNotificationContentModel.Builder("key").build() - whenever(promotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content) + promotedNotificationContentExtractor.resetForEntry(row.entry, content) inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) - verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any()) + promotedNotificationContentExtractor.verifyOneExtractCall() Assert.assertEquals(content, row.entry.promotedNotificationContentModel) } @@ -515,15 +511,25 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() { @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) fun testExtractsPromotedContent_whenBothFlagsEnabled() { val content = PromotedNotificationContentModel.Builder("key").build() - whenever(promotedNotificationContentExtractor.extractContent(any(), any())) - .thenReturn(content) + promotedNotificationContentExtractor.resetForEntry(row.entry, content) inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) - verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any()) + promotedNotificationContentExtractor.verifyOneExtractCall() Assert.assertEquals(content, row.entry.promotedNotificationContentModel) } + @Test + @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) + fun testExtractsPromotedContent_null() { + promotedNotificationContentExtractor.resetForEntry(row.entry, null) + + inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row) + + promotedNotificationContentExtractor.verifyOneExtractCall() + Assert.assertNull(row.entry.promotedNotificationContentModel) + } + private class ExceptionHolder { var exception: Exception? = null } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index b323ef85b370..b8d18757afbb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -81,7 +81,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.icon.IconBuilder; import com.android.systemui.statusbar.notification.icon.IconManager; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor; +import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; @@ -201,7 +201,7 @@ public class NotificationTestHelper { new MockSmartReplyInflater(), mock(NotifLayoutInflaterFactory.Provider.class), mock(HeadsUpStyleProvider.class), - mock(PromotedNotificationContentExtractor.class), + new FakePromotedNotificationContentExtractor(), mock(NotificationRowContentBinderLogger.class)) : new NotificationContentInflater( mock(NotifRemoteViewCache.class), @@ -212,7 +212,7 @@ public class NotificationTestHelper { new MockSmartReplyInflater(), mock(NotifLayoutInflaterFactory.Provider.class), mock(HeadsUpStyleProvider.class), - mock(PromotedNotificationContentExtractor.class), + new FakePromotedNotificationContentExtractor(), mock(NotificationRowContentBinderLogger.class)); contentBinder.setInflateSynchronously(true); mBindStage = new RowContentBindStage(contentBinder, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index de40abb4d9d8..c6cffa9da13b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -952,11 +952,10 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Test @EnableSceneContainer public void onTouchEvent_stopExpandingNotification_sceneContainerEnabled() { - boolean touchHandled = stopExpandingNotification(); + stopExpandingNotification(); - verify(mNotificationStackScrollLayout).startOverscrollAfterExpanding(); + verify(mExpandHelper).finishExpanding(); verify(mNotificationStackScrollLayout, never()).dispatchDownEventToScroller(any()); - assertTrue(touchHandled); } @Test @@ -964,11 +963,11 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { public void onTouchEvent_stopExpandingNotification_sceneContainerDisabled() { stopExpandingNotification(); - verify(mNotificationStackScrollLayout, never()).startOverscrollAfterExpanding(); + verify(mExpandHelper, never()).finishExpanding(); verify(mNotificationStackScrollLayout).dispatchDownEventToScroller(any()); } - private boolean stopExpandingNotification() { + private void stopExpandingNotification() { when(mNotificationStackScrollLayout.getExpandHelper()).thenReturn(mExpandHelper); when(mNotificationStackScrollLayout.getIsExpanded()).thenReturn(true); when(mNotificationStackScrollLayout.getExpandedInThisMotion()).thenReturn(true); @@ -983,13 +982,13 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { NotificationStackScrollLayoutController.TouchHandler touchHandler = mController.getTouchHandler(); - return touchHandler.onTouchEvent(MotionEvent.obtain( - /* downTime= */ 0, - /* eventTime= */ 0, - MotionEvent.ACTION_DOWN, - 0, - 0, - /* metaState= */ 0 + touchHandler.onTouchEvent(MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + MotionEvent.ACTION_DOWN, + 0, + 0, + /* metaState= */ 0 )); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt index 942ea65ec49e..e87077db8e75 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt @@ -21,11 +21,13 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_TYPE_CAR import android.os.LocaleList +import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.google.common.truth.Truth.assertThat +import java.util.Locale import org.junit.Before import org.junit.Ignore import org.junit.Test @@ -34,7 +36,6 @@ import org.mockito.Mockito.doAnswer import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import java.util.Locale @RunWith(AndroidJUnit4::class) @SmallTest @@ -64,9 +65,11 @@ class ConfigurationControllerImplTest : SysuiTestCase() { mConfigurationController.addCallback(listener2) doAnswer { - mConfigurationController.removeCallback(listener2) - null - }.`when`(listener).onThemeChanged() + mConfigurationController.removeCallback(listener2) + null + } + .`when`(listener) + .onThemeChanged() mConfigurationController.notifyThemeChanged() verify(listener).onThemeChanged() @@ -208,7 +211,6 @@ class ConfigurationControllerImplTest : SysuiTestCase() { assertThat(listener.maxBoundsChanged).isTrue() } - @Test fun localeListChanged_listenerNotified() { val config = mContext.resources.configuration @@ -289,7 +291,6 @@ class ConfigurationControllerImplTest : SysuiTestCase() { assertThat(listener.orientationChanged).isTrue() } - @Test fun multipleUpdates_listenerNotifiedOfAll() { val config = mContext.resources.configuration @@ -313,6 +314,17 @@ class ConfigurationControllerImplTest : SysuiTestCase() { } @Test + fun onMovedToDisplay_dispatchedToChildren() { + val config = mContext.resources.configuration + val listener = createAndAddListener() + + mConfigurationController.dispatchOnMovedToDisplay(newDisplayId = 1, config) + + assertThat(listener.display).isEqualTo(1) + assertThat(listener.changedConfig).isEqualTo(config) + } + + @Test @Ignore("b/261408895") fun equivalentConfigObject_listenerNotNotified() { val config = mContext.resources.configuration @@ -343,35 +355,49 @@ class ConfigurationControllerImplTest : SysuiTestCase() { var localeListChanged = false var layoutDirectionChanged = false var orientationChanged = false + var display = Display.DEFAULT_DISPLAY override fun onConfigChanged(newConfig: Configuration?) { changedConfig = newConfig } + override fun onDensityOrFontScaleChanged() { densityOrFontScaleChanged = true } + override fun onSmallestScreenWidthChanged() { smallestScreenWidthChanged = true } + override fun onMaxBoundsChanged() { maxBoundsChanged = true } + override fun onUiModeChanged() { uiModeChanged = true } + override fun onThemeChanged() { themeChanged = true } + override fun onLocaleListChanged() { localeListChanged = true } + override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) { layoutDirectionChanged = true } + override fun onOrientationChanged(orientation: Int) { orientationChanged = true } + override fun onMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration?) { + display = newDisplayId + changedConfig = newConfiguration + } + fun assertNoMethodsCalled() { assertThat(densityOrFontScaleChanged).isFalse() assertThat(smallestScreenWidthChanged).isFalse() @@ -391,6 +417,7 @@ class ConfigurationControllerImplTest : SysuiTestCase() { themeChanged = false localeListChanged = false layoutDirectionChanged = false + display = Display.DEFAULT_DISPLAY } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java new file mode 100644 index 000000000000..41782a123f14 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE; +import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK; +import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE; + +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 static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.systemui.InitController; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.settings.FakeDisplayTracker; +import com.android.systemui.shade.NotificationShadeWindowView; +import com.android.systemui.shade.QuickSettingsController; +import com.android.systemui.shade.ShadeController; +import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.DynamicPrivacyController; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; +import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.Set; + +@SmallTest +@RunWith(AndroidJUnit4.class) +@RunWithLooper() +public class StatusBarNotificationPresenterTest extends SysuiTestCase { + private StatusBarNotificationPresenter mStatusBarNotificationPresenter; + private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider = + mock(VisualInterruptionDecisionProvider.class); + private NotificationInterruptSuppressor mInterruptSuppressor; + private VisualInterruptionCondition mAlertsDisabledCondition; + private VisualInterruptionCondition mVrModeCondition; + private VisualInterruptionFilter mNeedsRedactionFilter; + private VisualInterruptionCondition mPanelsDisabledCondition; + private CommandQueue mCommandQueue; + private final ShadeController mShadeController = mock(ShadeController.class); + private final NotificationAlertsInteractor mNotificationAlertsInteractor = + mock(NotificationAlertsInteractor.class); + private final KeyguardStateController mKeyguardStateController = + mock(KeyguardStateController.class); + + @Before + public void setup() { + mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext)); + mDependency.injectTestDependency(StatusBarStateController.class, + mock(SysuiStatusBarStateController.class)); + mDependency.injectTestDependency(ShadeController.class, mShadeController); + mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class); + mDependency.injectMockDependency(NotificationShadeWindowController.class); + + when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(true); + + createPresenter(); + if (VisualInterruptionRefactor.isEnabled()) { + verifyAndCaptureSuppressors(); + } else { + verifyAndCaptureLegacySuppressor(); + } + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testInit_refactorDisabled() { + assertFalse(VisualInterruptionRefactor.isEnabled()); + assertNull(mAlertsDisabledCondition); + assertNull(mVrModeCondition); + assertNull(mNeedsRedactionFilter); + assertNull(mPanelsDisabledCondition); + assertNotNull(mInterruptSuppressor); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testInit_refactorEnabled() { + assertTrue(VisualInterruptionRefactor.isEnabled()); + assertNotNull(mAlertsDisabledCondition); + assertNotNull(mVrModeCondition); + assertNotNull(mNeedsRedactionFilter); + assertNotNull(mPanelsDisabledCondition); + assertNull(mInterruptSuppressor); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testNoSuppressHeadsUp_default_refactorDisabled() { + assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testNoSuppressHeadsUp_default_refactorEnabled() { + assertFalse(mAlertsDisabledCondition.shouldSuppress()); + assertFalse(mVrModeCondition.shouldSuppress()); + assertFalse(mNeedsRedactionFilter.shouldSuppress(createNotificationEntry())); + assertFalse(mAlertsDisabledCondition.shouldSuppress()); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressHeadsUp_disabledStatusBar_refactorDisabled() { + mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0, + false /* animate */); + TestableLooper.get(this).processAllMessages(); + + assertTrue("The panel should suppress heads up while disabled", + mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressHeadsUp_disabledStatusBar_refactorEnabled() { + mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0, + false /* animate */); + TestableLooper.get(this).processAllMessages(); + + assertTrue("The panel should suppress heads up while disabled", + mPanelsDisabledCondition.shouldSuppress()); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() { + mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE, + false /* animate */); + TestableLooper.get(this).processAllMessages(); + + assertTrue("The panel should suppress interruptions while notification shade disabled", + mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() { + mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE, + false /* animate */); + TestableLooper.get(this).processAllMessages(); + + assertTrue("The panel should suppress interruptions while notification shade disabled", + mPanelsDisabledCondition.shouldSuppress()); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testPanelsDisabledConditionSuppressesPeek() { + final Set<VisualInterruptionType> types = mPanelsDisabledCondition.getTypes(); + assertTrue(types.contains(PEEK)); + assertFalse(types.contains(PULSE)); + assertFalse(types.contains(BUBBLE)); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + + assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createFsiNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + + assertFalse(mNeedsRedactionFilter.shouldSuppress(createFsiNotificationEntry())); + + final Set<VisualInterruptionType> types = mNeedsRedactionFilter.getTypes(); + assertTrue(types.contains(PEEK)); + assertFalse(types.contains(PULSE)); + assertFalse(types.contains(BUBBLE)); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressInterruptions_vrMode_refactorDisabled() { + mStatusBarNotificationPresenter.mVrMode = true; + + assertTrue("Vr mode should suppress interruptions", + mInterruptSuppressor.suppressAwakeInterruptions(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressInterruptions_vrMode_refactorEnabled() { + mStatusBarNotificationPresenter.mVrMode = true; + + assertTrue("Vr mode should suppress interruptions", mVrModeCondition.shouldSuppress()); + + final Set<VisualInterruptionType> types = mVrModeCondition.getTypes(); + assertTrue(types.contains(PEEK)); + assertFalse(types.contains(PULSE)); + assertTrue(types.contains(BUBBLE)); + } + + @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() { + when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false); + + assertTrue("When alerts aren't enabled, interruptions are suppressed", + mInterruptSuppressor.suppressInterruptions(createNotificationEntry())); + } + + @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) + public void testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() { + when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false); + + assertTrue("When alerts aren't enabled, interruptions are suppressed", + mAlertsDisabledCondition.shouldSuppress()); + + final Set<VisualInterruptionType> types = mAlertsDisabledCondition.getTypes(); + assertTrue(types.contains(PEEK)); + assertTrue(types.contains(PULSE)); + assertTrue(types.contains(BUBBLE)); + } + + private void createPresenter() { + final ShadeViewController shadeViewController = mock(ShadeViewController.class); + + final NotificationShadeWindowView notificationShadeWindowView = + mock(NotificationShadeWindowView.class); + when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources()); + + NotificationStackScrollLayoutController stackScrollLayoutController = + mock(NotificationStackScrollLayoutController.class); + when(stackScrollLayoutController.getView()).thenReturn( + mock(NotificationStackScrollLayout.class)); + + final InitController initController = new InitController(); + + mStatusBarNotificationPresenter = new StatusBarNotificationPresenter( + mContext, + shadeViewController, + mock(PanelExpansionInteractor.class), + mock(QuickSettingsController.class), + mock(HeadsUpManager.class), + notificationShadeWindowView, + mock(ActivityStarter.class), + stackScrollLayoutController, + mock(DozeScrimController.class), + mock(NotificationShadeWindowController.class), + mock(DynamicPrivacyController.class), + mKeyguardStateController, + mNotificationAlertsInteractor, + mock(LockscreenShadeTransitionController.class), + mock(PowerInteractor.class), + mCommandQueue, + mock(NotificationLockscreenUserManager.class), + mock(SysuiStatusBarStateController.class), + mock(NotifShadeEventSource.class), + mock(NotificationMediaManager.class), + mock(NotificationGutsManager.class), + initController, + mVisualInterruptionDecisionProvider, + mock(NotificationRemoteInputManager.class), + mock(NotificationRemoteInputManager.Callback.class), + mock(NotificationListContainer.class)); + + initController.executePostInitTasks(); + } + + private void verifyAndCaptureSuppressors() { + mInterruptSuppressor = null; + + final ArgumentCaptor<VisualInterruptionCondition> conditionCaptor = + ArgumentCaptor.forClass(VisualInterruptionCondition.class); + verify(mVisualInterruptionDecisionProvider, times(3)).addCondition( + conditionCaptor.capture()); + final List<VisualInterruptionCondition> conditions = conditionCaptor.getAllValues(); + mAlertsDisabledCondition = conditions.get(0); + mVrModeCondition = conditions.get(1); + mPanelsDisabledCondition = conditions.get(2); + + final ArgumentCaptor<VisualInterruptionFilter> needsRedactionFilterCaptor = + ArgumentCaptor.forClass(VisualInterruptionFilter.class); + verify(mVisualInterruptionDecisionProvider).addFilter(needsRedactionFilterCaptor.capture()); + mNeedsRedactionFilter = needsRedactionFilterCaptor.getValue(); + } + + private void verifyAndCaptureLegacySuppressor() { + mAlertsDisabledCondition = null; + mVrModeCondition = null; + mNeedsRedactionFilter = null; + mPanelsDisabledCondition = null; + + final ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor = + ArgumentCaptor.forClass(NotificationInterruptSuppressor.class); + verify(mVisualInterruptionDecisionProvider).addLegacySuppressor(suppressorCaptor.capture()); + mInterruptSuppressor = suppressorCaptor.getValue(); + } + + private NotificationEntry createNotificationEntry() { + return new NotificationEntryBuilder() + .setPkg("a") + .setOpPkg("a") + .setTag("a") + .setNotification(new Notification.Builder(getContext(), "a").build()) + .build(); + } + + private NotificationEntry createFsiNotificationEntry() { + final Notification notification = new Notification.Builder(getContext(), "a") + .setFullScreenIntent(mock(PendingIntent.class), true) + .build(); + + return new NotificationEntryBuilder() + .setPkg("a") + .setOpPkg("a") + .setTag("a") + .setNotification(notification) + .build(); + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt deleted file mode 100644 index c347347eff83..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.statusbar.phone - -import android.app.Notification -import android.app.Notification.Builder -import android.app.StatusBarManager -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags -import android.testing.TestableLooper -import android.testing.TestableLooper.RunWithLooper -import android.view.Display.DEFAULT_DISPLAY -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.InitController -import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor -import com.android.systemui.flags.EnableSceneContainer -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.runTest -import com.android.systemui.plugins.activityStarter -import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.settings.FakeDisplayTracker -import com.android.systemui.shade.NotificationShadeWindowView -import com.android.systemui.shade.ShadeViewController -import com.android.systemui.shade.domain.interactor.panelExpansionInteractor -import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.lockscreenShadeTransitionController -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.domain.interactor.notificationAlertsInteractor -import com.android.systemui.statusbar.notification.dynamicPrivacyController -import com.android.systemui.statusbar.notification.headsup.headsUpManager -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor -import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController -import com.android.systemui.statusbar.notification.visualInterruptionDecisionProvider -import com.android.systemui.statusbar.notificationLockscreenUserManager -import com.android.systemui.statusbar.notificationRemoteInputManager -import com.android.systemui.statusbar.notificationShadeWindowController -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.statusbar.policy.keyguardStateController -import com.android.systemui.statusbar.sysuiStatusBarStateController -import com.android.systemui.testKosmos -import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.kotlin.any -import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import org.mockito.kotlin.times -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever - -@SmallTest -@RunWith(AndroidJUnit4::class) -@RunWithLooper -class StatusBarNotificationPresenterTest : SysuiTestCase() { - private lateinit var kosmos: Kosmos - - private var interruptSuppressor: NotificationInterruptSuppressor? = null - private var alertsDisabledCondition: VisualInterruptionCondition? = null - private var vrModeCondition: VisualInterruptionCondition? = null - private var needsRedactionFilter: VisualInterruptionFilter? = null - private var panelsDisabledCondition: VisualInterruptionCondition? = null - - private val commandQueue: CommandQueue = CommandQueue(mContext, FakeDisplayTracker(mContext)) - private val keyguardStateController: KeyguardStateController - get() = kosmos.keyguardStateController - - private val notificationAlertsInteractor - get() = kosmos.notificationAlertsInteractor - - private val visualInterruptionDecisionProvider - get() = kosmos.visualInterruptionDecisionProvider - - private lateinit var underTest: StatusBarNotificationPresenter - - @Before - fun setup() { - kosmos = - testKosmos().apply { - whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()) - .thenReturn(true) - whenever(notificationStackScrollLayoutController.expandHelperCallback) - .thenReturn(mock()) - lockscreenShadeTransitionController.setStackScroller( - notificationStackScrollLayoutController - ) - lockscreenShadeTransitionController.centralSurfaces = mock() - } - - underTest = createPresenter() - if (VisualInterruptionRefactor.isEnabled) { - verifyAndCaptureSuppressors() - } else { - verifyAndCaptureLegacySuppressor() - } - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testInit_refactorDisabled() { - assertThat(VisualInterruptionRefactor.isEnabled).isFalse() - assertThat(alertsDisabledCondition).isNull() - assertThat(vrModeCondition).isNull() - assertThat(needsRedactionFilter).isNull() - assertThat(panelsDisabledCondition).isNull() - assertThat(interruptSuppressor).isNotNull() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testInit_refactorEnabled() { - assertThat(VisualInterruptionRefactor.isEnabled).isTrue() - assertThat(alertsDisabledCondition).isNotNull() - assertThat(vrModeCondition).isNotNull() - assertThat(needsRedactionFilter).isNotNull() - assertThat(panelsDisabledCondition).isNotNull() - assertThat(interruptSuppressor).isNull() - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testNoSuppressHeadsUp_default_refactorDisabled() { - assertThat(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry())).isFalse() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testNoSuppressHeadsUp_default_refactorEnabled() { - assertThat(alertsDisabledCondition!!.shouldSuppress()).isFalse() - assertThat(vrModeCondition!!.shouldSuppress()).isFalse() - assertThat(needsRedactionFilter!!.shouldSuppress(createNotificationEntry())).isFalse() - assertThat(alertsDisabledCondition!!.shouldSuppress()).isFalse() - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressHeadsUp_disabledStatusBar_refactorDisabled() { - commandQueue.disable( - DEFAULT_DISPLAY, - StatusBarManager.DISABLE_EXPAND, - 0, - false, /* animate */ - ) - TestableLooper.get(this).processAllMessages() - - assertWithMessage("The panel should suppress heads up while disabled") - .that(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry())) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressHeadsUp_disabledStatusBar_refactorEnabled() { - commandQueue.disable( - DEFAULT_DISPLAY, - StatusBarManager.DISABLE_EXPAND, - 0, - false, /* animate */ - ) - TestableLooper.get(this).processAllMessages() - - assertWithMessage("The panel should suppress heads up while disabled") - .that(panelsDisabledCondition!!.shouldSuppress()) - .isTrue() - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() { - commandQueue.disable( - DEFAULT_DISPLAY, - 0, - StatusBarManager.DISABLE2_NOTIFICATION_SHADE, - false, /* animate */ - ) - TestableLooper.get(this).processAllMessages() - - assertWithMessage( - "The panel should suppress interruptions while notification shade disabled" - ) - .that(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry())) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() { - commandQueue.disable( - DEFAULT_DISPLAY, - 0, - StatusBarManager.DISABLE2_NOTIFICATION_SHADE, - false, /* animate */ - ) - TestableLooper.get(this).processAllMessages() - - assertWithMessage( - "The panel should suppress interruptions while notification shade disabled" - ) - .that(panelsDisabledCondition!!.shouldSuppress()) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testPanelsDisabledConditionSuppressesPeek() { - val types: Set<VisualInterruptionType> = panelsDisabledCondition!!.types - assertThat(types).contains(VisualInterruptionType.PEEK) - assertThat(types) - .containsNoneOf(VisualInterruptionType.BUBBLE, VisualInterruptionType.PULSE) - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() { - whenever(keyguardStateController.isShowing()).thenReturn(true) - whenever(keyguardStateController.isOccluded()).thenReturn(false) - - assertThat(interruptSuppressor!!.suppressAwakeHeadsUp(createFsiNotificationEntry())) - .isFalse() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() { - whenever(keyguardStateController.isShowing()).thenReturn(true) - whenever(keyguardStateController.isOccluded()).thenReturn(false) - - assertThat(needsRedactionFilter!!.shouldSuppress(createFsiNotificationEntry())).isFalse() - - val types: Set<VisualInterruptionType> = needsRedactionFilter!!.types - assertThat(types).contains(VisualInterruptionType.PEEK) - assertThat(types) - .containsNoneOf(VisualInterruptionType.BUBBLE, VisualInterruptionType.PULSE) - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressInterruptions_vrMode_refactorDisabled() { - underTest.mVrMode = true - - assertWithMessage("Vr mode should suppress interruptions") - .that(interruptSuppressor!!.suppressAwakeInterruptions(createNotificationEntry())) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressInterruptions_vrMode_refactorEnabled() { - underTest.mVrMode = true - - assertWithMessage("Vr mode should suppress interruptions") - .that(vrModeCondition!!.shouldSuppress()) - .isTrue() - - val types: Set<VisualInterruptionType> = vrModeCondition!!.types - assertThat(types).contains(VisualInterruptionType.PEEK) - assertThat(types).doesNotContain(VisualInterruptionType.PULSE) - assertThat(types).contains(VisualInterruptionType.BUBBLE) - } - - @Test - @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() { - whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false) - - assertWithMessage("When alerts aren't enabled, interruptions are suppressed") - .that(interruptSuppressor!!.suppressInterruptions(createNotificationEntry())) - .isTrue() - } - - @Test - @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) - fun testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() { - whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false) - - assertWithMessage("When alerts aren't enabled, interruptions are suppressed") - .that(alertsDisabledCondition!!.shouldSuppress()) - .isTrue() - - val types: Set<VisualInterruptionType> = alertsDisabledCondition!!.types - assertThat(types).contains(VisualInterruptionType.PEEK) - assertThat(types).contains(VisualInterruptionType.PULSE) - assertThat(types).contains(VisualInterruptionType.BUBBLE) - } - - @Test - @EnableSceneContainer - fun testExpandSensitiveNotification_onLockScreen_opensShade() = - kosmos.runTest { - // Given we are on the keyguard - kosmos.sysuiStatusBarStateController.state = StatusBarState.KEYGUARD - // And the device is locked - kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin - ) - - // When the user expands a sensitive Notification - val entry = - createRow().entry.apply { - setSensitive(/* sensitive= */ true, /* deviceSensitive= */ true) - } - underTest.onExpandClicked(entry, mock(), /* nowExpanded= */ true) - - // Then we open the locked shade - assertThat(kosmos.sysuiStatusBarStateController.state) - .isEqualTo(StatusBarState.SHADE_LOCKED) - } - - @Test - @EnableSceneContainer - fun testExpandSensitiveNotification_onLockedShade_showsBouncer() = - kosmos.runTest { - // Given we are on the locked shade - kosmos.sysuiStatusBarStateController.state = StatusBarState.SHADE_LOCKED - // And the device is locked - kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin - ) - - // When the user expands a sensitive Notification - val entry = - createRow().entry.apply { - setSensitive(/* sensitive= */ true, /* deviceSensitive= */ true) - } - underTest.onExpandClicked(entry, mock(), /* nowExpanded= */ true) - - // Then we show the bouncer - verify(kosmos.activityStarter).dismissKeyguardThenExecute(any(), eq(null), eq(false)) - // AND we are still on the locked shade - assertThat(kosmos.sysuiStatusBarStateController.state) - .isEqualTo(StatusBarState.SHADE_LOCKED) - } - - private fun createPresenter(): StatusBarNotificationPresenter { - val shadeViewController: ShadeViewController = mock() - - val notificationShadeWindowView: NotificationShadeWindowView = mock() - whenever(notificationShadeWindowView.resources).thenReturn(mContext.resources) - whenever(kosmos.notificationStackScrollLayoutController.view).thenReturn(mock()) - - val initController: InitController = InitController() - - return StatusBarNotificationPresenter( - mContext, - shadeViewController, - kosmos.panelExpansionInteractor, - /* quickSettingsController = */ mock(), - kosmos.headsUpManager, - notificationShadeWindowView, - kosmos.activityStarter, - kosmos.notificationStackScrollLayoutController, - kosmos.dozeScrimController, - kosmos.notificationShadeWindowController, - kosmos.dynamicPrivacyController, - kosmos.keyguardStateController, - kosmos.notificationAlertsInteractor, - kosmos.lockscreenShadeTransitionController, - kosmos.powerInteractor, - commandQueue, - kosmos.notificationLockscreenUserManager, - kosmos.sysuiStatusBarStateController, - /* notifShadeEventSource = */ mock(), - /* notificationMediaManager = */ mock(), - /* notificationGutsManager = */ mock(), - initController, - kosmos.visualInterruptionDecisionProvider, - kosmos.notificationRemoteInputManager, - /* remoteInputManagerCallback = */ mock(), - /* notificationListContainer = */ mock(), - kosmos.deviceUnlockedInteractor, - ) - .also { initController.executePostInitTasks() } - } - - private fun verifyAndCaptureSuppressors() { - interruptSuppressor = null - - val conditionCaptor = argumentCaptor<VisualInterruptionCondition>() - verify(visualInterruptionDecisionProvider, times(3)).addCondition(conditionCaptor.capture()) - val conditions: List<VisualInterruptionCondition> = conditionCaptor.allValues - alertsDisabledCondition = conditions[0] - vrModeCondition = conditions[1] - panelsDisabledCondition = conditions[2] - - val needsRedactionFilterCaptor = argumentCaptor<VisualInterruptionFilter>() - verify(visualInterruptionDecisionProvider).addFilter(needsRedactionFilterCaptor.capture()) - needsRedactionFilter = needsRedactionFilterCaptor.lastValue - } - - private fun verifyAndCaptureLegacySuppressor() { - alertsDisabledCondition = null - vrModeCondition = null - needsRedactionFilter = null - panelsDisabledCondition = null - - val suppressorCaptor = argumentCaptor<NotificationInterruptSuppressor>() - verify(visualInterruptionDecisionProvider).addLegacySuppressor(suppressorCaptor.capture()) - interruptSuppressor = suppressorCaptor.lastValue - } - - private fun createRow(): ExpandableNotificationRow { - val row: ExpandableNotificationRow = mock() - val entry: NotificationEntry = createNotificationEntry() - whenever(row.entry).thenReturn(entry) - entry.row = row - return row - } - - private fun createNotificationEntry(): NotificationEntry = - NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setNotification(Builder(mContext, "a").build()) - .build() - - private fun createFsiNotificationEntry(): NotificationEntry { - val notification: Notification = - Builder(mContext, "a").setFullScreenIntent(mock(), true).build() - - return NotificationEntryBuilder() - .setPkg("a") - .setOpPkg("a") - .setTag("a") - .setNotification(notification) - .build() - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt index 06b3b57bd133..b2378d2c3aae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.kosmos.mainCoroutineContext import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.runOnMainThreadAndWaitForIdleSync +import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.systemUIDialogFactory import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel @@ -78,6 +79,7 @@ class ModesDialogDelegateTest : SysuiTestCase() { { kosmos.modesDialogViewModel }, mockDialogEventLogger, kosmos.mainCoroutineContext, + kosmos.shadeDialogContextInteractor, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt index 533665eaa626..8972f3ec71af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureRecognizerTest.kt @@ -83,13 +83,40 @@ class ThreeFingerGestureRecognizerTest( } @Test - fun doesntTriggerGestureFinished_onTwoFingersSwipe() { - assertStateAfterEvents(events = TwoFingerGesture.swipeRight(), expectedState = NotStarted) + fun triggersGestureError_onTwoFingersSwipe() { + assertStateAfterEvents(events = TwoFingerGesture.swipeRight(), expectedState = Error) } @Test - fun doesntTriggerGestureFinished_onFourFingersSwipe() { - assertStateAfterEvents(events = FourFingerGesture.swipeRight(), expectedState = NotStarted) + fun doesntTriggerGestureError_TwoFingerSwipeInProgress() { + assertStateAfterEvents( + events = TwoFingerGesture.eventsForGestureInProgress { move(deltaX = SWIPE_DISTANCE) }, + expectedState = NotStarted, + ) + } + + @Test + fun triggersGestureError_onFourFingersSwipe() { + assertStateAfterEvents(events = FourFingerGesture.swipeRight(), expectedState = Error) + } + + @Test + fun doesntTriggerGestureError_FourFingerSwipeInProgress() { + assertStateAfterEvents( + events = FourFingerGesture.eventsForGestureInProgress { move(deltaX = SWIPE_DISTANCE) }, + expectedState = NotStarted, + ) + } + + @Test + fun ignoresOneFingerSwipes() { + val oneFingerSwipe = + listOf( + touchpadEvent(MotionEvent.ACTION_DOWN, 50f, 50f), + touchpadEvent(MotionEvent.ACTION_MOVE, 55f, 55f), + touchpadEvent(MotionEvent.ACTION_UP, 60f, 60f), + ) + assertStateAfterEvents(events = oneFingerSwipe, expectedState = NotStarted) } private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt index fb5ef02aa06a..843afb8d74a8 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt @@ -115,14 +115,25 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout { // and we're not planning to add this vide in clockHostView // so we only need position of device entry icon to constrain clock // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection - val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom") - val defaultDensity = - DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / - DisplayMetrics.DENSITY_DEFAULT.toFloat() - val lockIconRadiusPx = (defaultDensity * 36).toInt() - val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx - - connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin) + clockPreviewConfig.lockId?.let { lockId -> + connect(lockscreenClockViewLargeId, BOTTOM, lockId, TOP) + } + ?: run { + val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom") + val defaultDensity = + DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / + DisplayMetrics.DENSITY_DEFAULT.toFloat() + val lockIconRadiusPx = (defaultDensity * 36).toInt() + val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx + connect( + lockscreenClockViewLargeId, + BOTTOM, + PARENT_ID, + BOTTOM, + clockBottomMargin, + ) + } + val smallClockViewId = getId(context, "lockscreen_clock_view") constrainWidth(smallClockViewId, WRAP_CONTENT) constrainHeight(smallClockViewId, getDimen(context, "small_clock_height")) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt index 544b705c55c5..94e669fc0ea2 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt @@ -22,4 +22,5 @@ data class ClockPreviewConfig( val previewContext: Context, val isShadeLayoutWide: Boolean, val isSceneContainerFlagEnabled: Boolean = false, + val lockId: Int? = null, ) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index 322da322ff0c..56176cfc0f91 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -33,6 +33,7 @@ import com.android.systemui.plugins.qs.QSTile.Icon; import com.android.systemui.plugins.qs.QSTile.State; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Supplier; @ProvidesInterface(version = QSTile.VERSION) @@ -122,9 +123,32 @@ public interface QSTile { boolean isListening(); /** + * Get this tile's {@link TileDetailsViewModel} through a callback. + * + * Please only override this method if the tile can't get its {@link TileDetailsViewModel} + * synchronously and thus need a callback to defer it. + * + * @return a boolean indicating whether this tile has a {@link TileDetailsViewModel}. The tile's + * {@link TileDetailsViewModel} will be passed to the callback. Please always return true when + * overriding this method. Return false will make the tile display its dialog instead of details + * view, and it will not wait for the callback to be returned before proceeding to show the + * dialog. + */ + default boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) { + TileDetailsViewModel tileDetailsViewModel = getDetailsViewModel(); + callback.accept(tileDetailsViewModel); + return tileDetailsViewModel != null; + } + + /** * Return this tile's {@link TileDetailsViewModel} to be used to render the TileDetailsView. + * + * Please only override this method if the tile doesn't need a callback to set its + * {@link TileDetailsViewModel}. */ - default TileDetailsViewModel getDetailsViewModel() { return null; } + default TileDetailsViewModel getDetailsViewModel() { + return null; + } @ProvidesInterface(version = Callback.VERSION) interface Callback { diff --git a/packages/SystemUI/res/color/slider_active_track_color.xml b/packages/SystemUI/res/color/slider_active_track_color.xml deleted file mode 100644 index 8ba5e4901a7a..000000000000 --- a/packages/SystemUI/res/color/slider_active_track_color.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" /> - <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" /> -</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/slider_inactive_track_color.xml b/packages/SystemUI/res/color/slider_inactive_track_color.xml deleted file mode 100644 index 7980f804a516..000000000000 --- a/packages/SystemUI/res/color/slider_inactive_track_color.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="true" /> - <item android:color="@androidprv:color/materialColorPrimary" /> -</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/slider_thumb_color.xml b/packages/SystemUI/res/color/slider_thumb_color.xml deleted file mode 100644 index 8a98902426f8..000000000000 --- a/packages/SystemUI/res/color/slider_thumb_color.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="false" /> - <item android:color="@androidprv:color/materialColorPrimary" /> -</selector> diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml index 215e4e457060..7745af9dd408 100644 --- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml @@ -49,27 +49,25 @@ ongoing_activity_chip_short_time_delta] will ever be shown at one time. --> <!-- Shows a timer, like 00:01. --> + <!-- Don't use the LimitedWidth style for the timer because the end of the timer is often + the most important value. ChipChronometer has the correct logic for when the timer is + too large for the space allowed. --> <com.android.systemui.statusbar.chips.ui.view.ChipChronometer android:id="@+id/ongoing_activity_chip_time" style="@style/StatusBar.Chip.Text" /> <!-- Shows generic text. --> - <!-- Since there's so little room in the status bar chip area, don't ellipsize the text and - instead just fade it out a bit at the end. --> <TextView android:id="@+id/ongoing_activity_chip_text" - style="@style/StatusBar.Chip.Text" - android:ellipsize="none" - android:requiresFadingEdge="horizontal" - android:fadingEdgeLength="@dimen/ongoing_activity_chip_text_fading_edge_length" + style="@style/StatusBar.Chip.Text.LimitedWidth" android:visibility="gone" /> <!-- Shows a time delta in short form, like "15min" or "1hr". --> <android.widget.DateTimeView android:id="@+id/ongoing_activity_chip_short_time_delta" - style="@style/StatusBar.Chip.Text" + style="@style/StatusBar.Chip.Text.LimitedWidth" android:visibility="gone" /> diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml index 9ac456c17084..0acf4109bbb5 100644 --- a/packages/SystemUI/res/layout/volume_dialog_slider.xml +++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml @@ -14,7 +14,6 @@ limitations under the License. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content"> @@ -24,11 +23,6 @@ android:layout_width="@dimen/volume_dialog_slider_width" android:layout_height="@dimen/volume_dialog_slider_height" android:layout_gravity="center" - android:theme="@style/Theme.Material3.Light" android:orientation="vertical" - app:thumbHeight="52dp" - app:trackCornerSize="12dp" - app:trackHeight="40dp" - app:trackStopIndicatorSize="6dp" - app:trackInsideCornerSize="2dp" /> + android:theme="@style/Theme.Material3.DayNight" /> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index c1eff5f629b3..a77f5e4629c1 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -108,6 +108,9 @@ <color name="people_tile_background">@color/material_dynamic_secondary20</color> + <!-- Dark Theme colors for notification shade/scrim --> + <color name="shade_panel">@android:color/system_accent1_900</color> + <!-- Keyboard shortcut helper dialog --> <color name="ksh_key_item_color">@*android:color/system_on_surface_variant_dark</color> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index b4383156dc71..ab0f788dbb13 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -19,7 +19,7 @@ <!-- These resources are around just to allow their values to be customized for different hardware and product builds. --> -<resources> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> <!-- The maximum number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">4</integer> @@ -51,7 +51,9 @@ ignored. --> <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false"> <item>bottom_start:home</item> - <item>bottom_end:create_note</item> + <!-- TODO(b/384119565): revisit decision on defaults --> + <item android:featureFlag="!com.android.systemui.glanceable_hub_v2_resources">bottom_end:create_note</item> + <item android:featureFlag="com.android.systemui.glanceable_hub_v2_resources">bottom_end:glanceable_hub</item> </string-array> <!-- Whether volume panel should use the large screen layout or not --> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 28df2e2a1b8c..d2b7d0b90c43 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -31,6 +31,9 @@ <!-- The dark background color behind the shade --> <color name="shade_scrim_background_dark">@androidprv:color/system_under_surface_light</color> + <!-- Colors for notification shade/scrim --> + <color name="shade_panel">@android:color/system_accent1_800</color> + <!-- The color of the background in the separated list of the Global Actions menu --> <color name="global_actions_separated_background">#F5F5F5</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5894f297d2a7..35cfd082c537 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1760,6 +1760,7 @@ <dimen name="wallet_button_vertical_padding">8dp</dimen> <!-- Ongoing activity chip --> + <dimen name="ongoing_activity_chip_max_text_width">74dp</dimen> <!-- The activity chip side padding, used with the default phone icon. --> <dimen name="ongoing_activity_chip_side_padding">12dp</dimen> <!-- The activity chip side padding, used with an icon that has embedded padding (e.g. if the icon comes from the notification's smallIcon field). If the icon has padding, the chip itself can have less padding. --> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 88ed4e353719..c06b0784092c 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -215,27 +215,30 @@ <item type="id" name="backlight_icon" /> <!-- IDs for use in the keyguard/lockscreen scene --> - <item type="id" name="keyguard_root_view" /> + <item type="id" name="accessibility_actions_view" /> + <item type="id" name="ambient_indication_container" /> + <item type="id" name="aod_notification_icon_container" /> + <item type="id" name="burn_in_layer" /> + <item type="id" name="burn_in_layer_empty_view" /> + <item type="id" name="communal_tutorial_indicator" /> + <item type="id" name="end_button" /> + <item type="id" name="lock_icon" /> + <item type="id" name="lock_icon_bg" /> <item type="id" name="keyguard_indication_area" /> <item type="id" name="keyguard_indication_text" /> <item type="id" name="keyguard_indication_text_bottom" /> + <item type="id" name="keyguard_root_view" /> + <item type="id" name="keyguard_settings_button" /> <item type="id" name="nssl_guideline" /> <item type="id" name="nssl_placeholder" /> - <item type="id" name="aod_notification_icon_container" /> - <item type="id" name="split_shade_guideline" /> - <item type="id" name="lock_icon" /> - <item type="id" name="lock_icon_bg" /> - <item type="id" name="burn_in_layer" /> - <item type="id" name="burn_in_layer_empty_view" /> - <item type="id" name="communal_tutorial_indicator" /> <item type="id" name="nssl_placeholder_barrier_bottom" /> - <item type="id" name="ambient_indication_container" /> - <item type="id" name="status_view_media_container" /> - <item type="id" name="smart_space_barrier_bottom" /> <item type="id" name="small_clock_guideline_top" /> + <item type="id" name="smart_space_barrier_bottom" /> + <item type="id" name="split_shade_guideline" /> + <item type="id" name="start_button" /> + <item type="id" name="status_view_media_container" /> <item type="id" name="weather_clock_date_and_icons_barrier_bottom" /> <item type="id" name="weather_clock_bc_smartspace_bottom" /> - <item type="id" name="accessibility_actions_view" /> <!-- Privacy dialog --> <item type="id" name="privacy_dialog_close_app_button" /> @@ -280,7 +283,7 @@ <item type="id" name="udfps_accessibility_overlay_top_guideline" /> <!-- Ids for communal hub widgets --> - <item type="id" name="communal_widget_disposable_tag"/> + <item type="id" name="communal_widget_listener_tag"/> <!-- snapshot view-binding IDs --> <item type="id" name="snapshot_view_binding" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 56aaf4c0c564..d3ee63ba0dd1 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2504,14 +2504,14 @@ <!-- Accessibility description of action to remove QS tile on click. It will read as "Double-tap to remove tile" in screen readers [CHAR LIMIT=NONE] --> <string name="accessibility_qs_edit_remove_tile_action">remove tile</string> - <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to end" in screen readers [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_add_action">add tile to end</string> + <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to the last position" in screen readers [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_add_action">add tile to the last position</string> <!-- Accessibility action for context menu to move QS tile [CHAR LIMIT=NONE] --> <string name="accessibility_qs_edit_tile_start_move">Move tile</string> - <!-- Accessibility action for context menu to add QS tile [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_start_add">Add tile</string> + <!-- Accessibility action for context menu to add QS tile to a position [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_start_add">Add tile to desired position</string> <!-- Accessibility description when QS tile is to be moved, indicating the destination position [CHAR LIMIT=NONE] --> <string name="accessibility_qs_edit_tile_move_to_position">Move to <xliff:g id="position" example="5">%1$d</xliff:g></string> @@ -2564,7 +2564,7 @@ <string name="accessibility_quick_settings_open_settings">Open <xliff:g name="page" example="Bluetooth">%s</xliff:g> settings.</string> <!-- accessibility label for button to edit quick settings [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_edit">Edit order of settings.</string> + <string name="accessibility_quick_settings_edit">Edit order of Quick Settings.</string> <!-- accessibility label for button to open power menu [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_power_menu">Power menu</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 12f6e6958b42..f6c1ecea2886 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -84,6 +84,16 @@ <item name="android:textColor">?android:attr/colorPrimary</item> </style> + <!-- Style for a status bar chip text that has a maximum width. Since there's so little room in + the status bar chip area, don't ellipsize the text and instead just fade it out a bit at + the end. --> + <style name="StatusBar.Chip.Text.LimitedWidth"> + <item name="android:ellipsize">none</item> + <item name="android:requiresFadingEdge">horizontal</item> + <item name="android:fadingEdgeLength">@dimen/ongoing_activity_chip_text_fading_edge_length</item> + <item name="android:maxWidth">@dimen/ongoing_activity_chip_max_text_width</item> + </style> + <style name="Chipbar" /> <style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title"> @@ -549,15 +559,18 @@ <style name="SystemUI.Material3.Slider.Volume"> <item name="trackHeight">40dp</item> <item name="thumbHeight">52dp</item> + <item name="trackCornerSize">12dp</item> + <item name="trackInsideCornerSize">2dp</item> + <item name="trackStopIndicatorSize">6dp</item> </style> <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider"> <item name="labelStyle">@style/Widget.Material3.Slider.Label</item> - <item name="thumbColor">@color/slider_thumb_color</item> - <item name="tickColorActive">@color/slider_inactive_track_color</item> - <item name="tickColorInactive">@color/slider_active_track_color</item> - <item name="trackColorActive">@color/slider_active_track_color</item> - <item name="trackColorInactive">@color/slider_inactive_track_color</item> + <item name="thumbColor">@androidprv:color/materialColorPrimary</item> + <item name="tickColorActive">@androidprv:color/materialColorSurfaceContainerHighest</item> + <item name="tickColorInactive">@androidprv:color/materialColorPrimary</item> + <item name="trackColorActive">@androidprv:color/materialColorPrimary</item> + <item name="trackColorInactive">@androidprv:color/materialColorSurfaceContainerHighest</item> </style> <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/> diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 5af80cbd4b29..71b622aa0608 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -43,7 +43,6 @@ import com.android.systemui.dagger.qualifiers.DisplaySpecific import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags.REGION_SAMPLING -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -85,8 +84,8 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge /** - * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by - * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController]. + * Controller for a Clock provided by the registry and used on the keyguard. Functionality is forked + * from [AnimatableClockController]. */ open class ClockEventController @Inject @@ -348,14 +347,6 @@ constructor( object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible - if (!MigrateClocksToBlueprint.isEnabled) { - if (!isKeyguardVisible) { - clock?.run { - smallClock.animations.doze(if (isDozing) 1f else 0f) - largeClock.animations.doze(if (isDozing) 1f else 0f) - } - } - } if (visible) { refreshTime() @@ -388,10 +379,6 @@ constructor( } private fun refreshTime() { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - clock?.smallClock?.events?.onTimeTick() clock?.largeClock?.events?.onTimeTick() } @@ -483,14 +470,10 @@ constructor( if (ModesUi.isEnabled) { listenForDnd(this) } - if (MigrateClocksToBlueprint.isEnabled) { - listenForDozeAmountTransition(this) - listenForAnyStateToAodTransition(this) - listenForAnyStateToLockscreenTransition(this) - listenForAnyStateToDozingTransition(this) - } else { - listenForDozeAmount(this) - } + listenForDozeAmountTransition(this) + listenForAnyStateToAodTransition(this) + listenForAnyStateToLockscreenTransition(this) + listenForAnyStateToDozingTransition(this) } } smallTimeListener?.update(shouldTimeListenerRun) @@ -596,11 +579,6 @@ constructor( } @VisibleForTesting - internal fun listenForDozeAmount(scope: CoroutineScope): Job { - return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } } - } - - @VisibleForTesting internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { return scope.launch { merge( @@ -695,8 +673,7 @@ constructor( isRunning = true when (clockFace.config.tickRate) { ClockTickRate.PER_MINUTE -> { - // Handled by KeyguardClockSwitchController and - // by KeyguardUpdateMonitorCallback#onTimeChanged. + // Handled by KeyguardUpdateMonitorCallback#onTimeChanged. } ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable) ClockTickRate.PER_FRAME -> { diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt index df77a58c3b34..3f332f769c6e 100644 --- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt +++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt @@ -23,14 +23,11 @@ import android.graphics.Rect import android.os.Bundle import android.view.Display import android.view.Gravity -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowManager import android.widget.FrameLayout import android.widget.FrameLayout.LayoutParams -import com.android.keyguard.dagger.KeyguardStatusViewComponent -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.res.R @@ -45,7 +42,6 @@ class ConnectedDisplayKeyguardPresentation constructor( @Assisted display: Display, context: Context, - private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val clockRegistry: ClockRegistry, private val clockEventController: ClockEventController, ) : @@ -53,12 +49,11 @@ constructor( context, display, R.style.Theme_SystemUI_KeyguardPresentation, - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, ) { private lateinit var rootView: FrameLayout private var clock: View? = null - private lateinit var keyguardStatusViewController: KeyguardStatusViewController private lateinit var faceController: ClockFaceController private lateinit var clockFrame: FrameLayout @@ -82,7 +77,7 @@ constructor( oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int + oldBottom: Int, ) { clock?.let { faceController.events.onTargetRegionChanged( @@ -95,11 +90,7 @@ constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (MigrateClocksToBlueprint.isEnabled) { - onCreateV2() - } else { - onCreate() - } + onCreateV2() } fun onCreateV2() { @@ -112,39 +103,15 @@ constructor( setClock(clockRegistry.createCurrentClock()) } - fun onCreate() { - setContentView( - LayoutInflater.from(context) - .inflate(R.layout.keyguard_clock_presentation, /* root= */ null) - ) - - setFullscreen() - - clock = requireViewById(R.id.clock) - keyguardStatusViewController = - keyguardStatusViewComponentFactory - .build(clock as KeyguardStatusView, display) - .keyguardStatusViewController - .apply { - setDisplayedOnSecondaryDisplay() - init() - } - } - override fun onAttachedToWindow() { - if (MigrateClocksToBlueprint.isEnabled) { - clockRegistry.registerClockChangeListener(clockChangedListener) - clockEventController.registerListeners(clock!!) - - faceController.animations.enter() - } + clockRegistry.registerClockChangeListener(clockChangedListener) + clockEventController.registerListeners(clock!!) + faceController.animations.enter() } override fun onDetachedFromWindow() { - if (MigrateClocksToBlueprint.isEnabled) { - clockEventController.unregisterListeners() - clockRegistry.unregisterClockChangeListener(clockChangedListener) - } + clockEventController.unregisterListeners() + clockRegistry.unregisterClockChangeListener(clockChangedListener) super.onDetachedFromWindow() } @@ -166,7 +133,7 @@ constructor( context.resources.getDimensionPixelSize(R.dimen.keyguard_presentation_width), WRAP_CONTENT, Gravity.CENTER, - ) + ), ) clockEventController.clock = clockController @@ -190,8 +157,6 @@ constructor( @AssistedFactory interface Factory { /** Creates a new [Presentation] for the given [display]. */ - fun create( - display: Display, - ): ConnectedDisplayKeyguardPresentation + fun create(display: Display): ConnectedDisplayKeyguardPresentation } } diff --git a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt deleted file mode 100644 index 306d68217e50..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.keyguard - -import android.view.MotionEvent -import android.view.View -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.ui.view.KeyguardRootView -import com.android.systemui.res.R -import dagger.Lazy -import javax.inject.Inject - -/** - * Lock icon view logic now lives in DeviceEntryIconViewBinder and ViewModels. Icon is positioned in - * [com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection]. - * - * This class is to bridge the gap between the logic when the DeviceEntryUdfpsRefactor is enabled - * and the KeyguardBottomAreaRefactor is NOT enabled. This class can and should be removed when both - * flags are enabled. - */ -@SysUISingleton -class EmptyLockIconViewController -@Inject -constructor(private val keyguardRootView: Lazy<KeyguardRootView>) : LockIconViewController { - private val deviceEntryIconViewId = R.id.device_entry_icon_view - - override fun setLockIconView(lockIconView: View) { - // no-op - } - - override fun getTop(): Float { - return keyguardRootView.get().getViewById(deviceEntryIconViewId)?.top?.toFloat() ?: 0f - } - - override fun getBottom(): Float { - return keyguardRootView.get().getViewById(deviceEntryIconViewId)?.bottom?.toFloat() ?: 0f - } - - override fun dozeTimeTick() { - // no-op - } - - override fun setAlpha(alpha: Float) { - // no-op - } - - override fun willHandleTouchWhileDozing(event: MotionEvent): Boolean { - return false - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 36afe1e0fe18..63d4fe3f1b01 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -51,6 +51,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BlendMode; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; @@ -97,6 +98,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.settingslib.Utils; import com.android.settingslib.drawable.CircleFramedDrawable; +import com.android.systemui.Flags; import com.android.systemui.Gefingerpoken; import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.plugins.FalsingManager; @@ -346,8 +348,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { setPadding(getPaddingLeft(), getPaddingTop() + getResources().getDimensionPixelSize( R.dimen.keyguard_security_container_padding_top), getPaddingRight(), getPaddingBottom()); - setBackgroundColor( - getContext().getColor(com.android.internal.R.color.materialColorSurfaceDim)); + reloadBackgroundColor(); } void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { @@ -812,10 +813,20 @@ public class KeyguardSecurityContainer extends ConstraintLayout { mDisappearAnimRunning = false; } + private void reloadBackgroundColor() { + if (Flags.bouncerUiRevamp()) { + // Keep the background transparent, otherwise the background color looks like a box + // while scaling the bouncer for back animation or while transitioning to the bouncer. + setBackgroundColor(Color.TRANSPARENT); + } else { + setBackgroundColor( + getContext().getColor(com.android.internal.R.color.materialColorSurfaceDim)); + } + } + void reloadColors() { mViewMode.reloadColors(); - setBackgroundColor(getContext().getColor( - com.android.internal.R.color.materialColorSurfaceDim)); + reloadBackgroundColor(); } /** Handles density or font scale changes. */ diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 0305b5e5ab63..e76f38c8c75c 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -26,7 +26,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.clocks.ClockMessageBuffers; import com.android.systemui.res.R; @@ -70,7 +69,7 @@ public abstract class ClockRegistryModule { context, layoutInflater, resources, - MigrateClocksToBlueprint.isEnabled(), + com.android.systemui.Flags.clockReactiveVariants() ), context.getString(R.string.lockscreen_clock_id_fallback), diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index db4b0f2522ed..54c52b533da4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -43,7 +43,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieCompositionFactory -import com.android.systemui.Flags.bpIconA11y +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.biometrics.Utils.ellipsize import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality @@ -63,7 +63,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch private const val TAG = "BiometricViewBinder" @@ -327,33 +326,32 @@ object BiometricViewBinder { // reuse the icon as a confirm button launch { - if (bpIconA11y()) { - viewModel.isIconConfirmButton.collect { isButton -> - if (isButton) { - iconView.onTouchListener { _: View, event: MotionEvent -> - viewModel.onOverlayTouch(event) - } - iconView.setOnClickListener { viewModel.confirmAuthenticated() } - } else { - iconView.setOnTouchListener(null) - iconView.setOnClickListener(null) + viewModel.isIconConfirmButton.collect { isButton -> + if (isButton && !accessibilityManager.isEnabled) { + iconView.onTouchListener { _: View, event: MotionEvent -> + viewModel.onOverlayTouch(event) } + } else { + iconView.setOnTouchListener(null) } - } else { - viewModel.isIconConfirmButton - .map { isPending -> - when { - isPending && modalities.hasFaceAndFingerprint -> - View.OnTouchListener { _: View, event: MotionEvent -> - viewModel.onOverlayTouch(event) - } - else -> null - } - } - .collect { onTouch -> iconView.setOnTouchListener(onTouch) } } } + launch { + combine(viewModel.isIconConfirmButton, viewModel.isAuthenticated, ::Pair) + .collect { (isIconConfirmButton, authState) -> + // Only use the icon as a button for talkback when coex and pending + // confirmation + if ( + accessibilityManager.isEnabled && + isIconConfirmButton && + authState.isAuthenticated + ) { + iconView.setOnClickListener { viewModel.confirmAuthenticated() } + } + } + } + // dismiss prompt when authenticated and confirmed launch { viewModel.isAuthenticated.collect { authState -> @@ -365,22 +363,8 @@ object BiometricViewBinder { backgroundView.setOnClickListener(null) backgroundView.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO - - // Allow icon to be used as confirmation button with udfps and a11y - // enabled - if ( - !bpIconA11y() && - accessibilityManager.isTouchExplorationEnabled && - modalities.hasUdfps - ) { - iconView.setOnClickListener { viewModel.confirmAuthenticated() } - } } if (authState.isAuthenticatedAndConfirmed) { - view.announceForAccessibility( - view.resources.getString(R.string.biometric_dialog_authenticated) - ) - launch { delay(authState.delay) if (authState.isAuthenticatedAndExplicitlyConfirmed) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt index 574c40da226f..788c792fdd91 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt @@ -43,7 +43,7 @@ class PromptIconViewModel( enum class AuthType { Fingerprint, Face, - Coex + Coex, } /** @@ -53,7 +53,7 @@ class PromptIconViewModel( val activeAuthType: Flow<AuthType> = combine( promptViewModel.modalities.distinctUntilChanged(), - promptViewModel.faceMode.distinctUntilChanged() + promptViewModel.faceMode.distinctUntilChanged(), ) { modalities, faceMode -> if (modalities.hasFaceAndFingerprint && !faceMode) { AuthType.Coex @@ -102,7 +102,7 @@ class PromptIconViewModel( promptSelectorInteractor.fingerprintSensorType, promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, - promptViewModel.showingError + promptViewModel.showingError, ) { rotation: DisplayRotation, isInRearDisplayMode: Boolean, @@ -117,13 +117,13 @@ class PromptIconViewModel( isInRearDisplayMode, authState.isAuthenticated, isAuthenticating, - showingError + showingError, ) else -> getFingerprintIconViewAsset( authState.isAuthenticated, isAuthenticating, - showingError + showingError, ) } } @@ -132,7 +132,7 @@ class PromptIconViewModel( promptViewModel.isAuthenticated.distinctUntilChanged(), promptViewModel.isAuthenticating.distinctUntilChanged(), promptViewModel.isPendingConfirmation.distinctUntilChanged(), - promptViewModel.showingError.distinctUntilChanged() + promptViewModel.showingError.distinctUntilChanged(), ) { authState: PromptAuthState, isAuthenticating: Boolean, @@ -142,7 +142,7 @@ class PromptIconViewModel( authState, isAuthenticating, isPendingConfirmation, - showingError + showingError, ) } AuthType.Coex -> @@ -170,14 +170,14 @@ class PromptIconViewModel( authState, isAuthenticating, isPendingConfirmation, - showingError + showingError, ) else -> getCoexIconViewAsset( authState, isAuthenticating, isPendingConfirmation, - showingError + showingError, ) } } @@ -187,7 +187,7 @@ class PromptIconViewModel( private fun getFingerprintIconViewAsset( isAuthenticated: Boolean, isAuthenticating: Boolean, - showingError: Boolean + showingError: Boolean, ): Int { return if (isAuthenticated) { if (_previousIconWasError.value) { @@ -214,7 +214,7 @@ class PromptIconViewModel( isInRearDisplayMode: Boolean, isAuthenticated: Boolean, isAuthenticating: Boolean, - showingError: Boolean + showingError: Boolean, ): Int { return if (isAuthenticated) { if (_previousIconWasError.value) { @@ -240,7 +240,7 @@ class PromptIconViewModel( authState: PromptAuthState, isAuthenticating: Boolean, isPendingConfirmation: Boolean, - showingError: Boolean + showingError: Boolean, ): Int { return if (authState.isAuthenticated && isPendingConfirmation) { R.raw.face_dialog_wink_from_dark @@ -262,7 +262,7 @@ class PromptIconViewModel( authState: PromptAuthState, isAuthenticating: Boolean, isPendingConfirmation: Boolean, - showingError: Boolean + showingError: Boolean, ): Int { return if (authState.isAuthenticatedAndExplicitlyConfirmed) { R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie @@ -298,7 +298,7 @@ class PromptIconViewModel( authState: PromptAuthState, isAuthenticating: Boolean, isPendingConfirmation: Boolean, - showingError: Boolean + showingError: Boolean, ): Int { return if (authState.isAuthenticatedAndExplicitlyConfirmed) { R.raw.biometricprompt_sfps_unlock_to_success @@ -338,7 +338,7 @@ class PromptIconViewModel( promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, promptViewModel.isPendingConfirmation, - promptViewModel.showingError + promptViewModel.showingError, ) { sensorType: FingerprintSensorType, authState: PromptAuthState, @@ -350,7 +350,7 @@ class PromptIconViewModel( authState.isAuthenticated, isAuthenticating, isPendingConfirmation, - showingError + showingError, ) } AuthType.Face -> @@ -370,7 +370,7 @@ class PromptIconViewModel( isAuthenticated: Boolean, isAuthenticating: Boolean, isPendingConfirmation: Boolean, - showingError: Boolean + showingError: Boolean, ): Int = if (isPendingConfirmation) { when (sensorType) { @@ -381,7 +381,7 @@ class PromptIconViewModel( when (sensorType) { FingerprintSensorType.POWER_BUTTON -> R.string.security_settings_sfps_enroll_find_sensor_message - else -> R.string.fingerprint_dialog_touch_sensor + else -> R.string.accessibility_fingerprint_label } } else if (showingError) { R.string.biometric_dialog_try_again @@ -392,7 +392,7 @@ class PromptIconViewModel( private fun getFaceIconContentDescriptionId( authState: PromptAuthState, isAuthenticating: Boolean, - showingError: Boolean + showingError: Boolean, ): Int = if (authState.isAuthenticatedAndExplicitlyConfirmed) { R.string.biometric_dialog_face_icon_description_confirmed @@ -415,7 +415,7 @@ class PromptIconViewModel( promptSelectorInteractor.fingerprintSensorType, promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, - promptViewModel.showingError + promptViewModel.showingError, ) { sensorType: FingerprintSensorType, authState: PromptAuthState, @@ -427,7 +427,7 @@ class PromptIconViewModel( shouldAnimateFingerprintIconView( authState.isAuthenticated, isAuthenticating, - showingError + showingError, ) } } @@ -435,7 +435,7 @@ class PromptIconViewModel( combine( promptViewModel.isAuthenticated, promptViewModel.isAuthenticating, - promptViewModel.showingError + promptViewModel.showingError, ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean -> isAuthenticating || @@ -463,7 +463,7 @@ class PromptIconViewModel( authState.isAuthenticated, isAuthenticating, isPendingConfirmation, - showingError + showingError, ) } } @@ -483,14 +483,14 @@ class PromptIconViewModel( private fun shouldAnimateFingerprintIconView( isAuthenticated: Boolean, isAuthenticating: Boolean, - showingError: Boolean + showingError: Boolean, ) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError private fun shouldAnimateCoexIconView( isAuthenticated: Boolean, isAuthenticating: Boolean, isPendingConfirmation: Boolean, - showingError: Boolean + showingError: Boolean, ) = (isAuthenticating && _previousIconWasError.value) || isPendingConfirmation || @@ -522,7 +522,7 @@ class PromptIconViewModel( listOf( R.raw.biometricprompt_sfps_fingerprint_authenticating, R.raw.biometricprompt_sfps_rear_display_fingerprint_authenticating, - R.raw.biometricprompt_sfps_rear_display_fingerprint_authenticating + R.raw.biometricprompt_sfps_rear_display_fingerprint_authenticating, ) /** Called on configuration changes */ @@ -579,7 +579,7 @@ class PromptIconViewModel( R.raw.fingerprint_dialogue_error_to_success_lottie, R.raw.fingerprint_dialogue_fingerprint_to_success_lottie, R.raw.fingerprint_dialogue_error_to_fingerprint_lottie, - R.raw.fingerprint_dialogue_fingerprint_to_error_lottie + R.raw.fingerprint_dialogue_fingerprint_to_error_lottie, ) } @@ -620,7 +620,7 @@ class PromptIconViewModel( R.raw.fingerprint_dialogue_error_to_fingerprint_lottie, R.raw.fingerprint_dialogue_error_to_success_lottie, R.raw.fingerprint_dialogue_fingerprint_to_error_lottie, - R.raw.fingerprint_dialogue_fingerprint_to_success_lottie + R.raw.fingerprint_dialogue_fingerprint_to_success_lottie, ) } @@ -632,7 +632,7 @@ class PromptIconViewModel( R.raw.face_dialog_dark_to_error, R.raw.face_dialog_error_to_idle, R.raw.face_dialog_idle_static, - R.raw.face_dialog_authenticating + R.raw.face_dialog_authenticating, ) private fun getSfpsAsset_fingerprintAuthenticating(isInRearDisplayMode: Boolean): Int = @@ -644,7 +644,7 @@ class PromptIconViewModel( private fun getSfpsAsset_fingerprintToError( rotation: DisplayRotation, - isInRearDisplayMode: Boolean + isInRearDisplayMode: Boolean, ): Int = if (isInRearDisplayMode) { when (rotation) { @@ -668,7 +668,7 @@ class PromptIconViewModel( private fun getSfpsAsset_errorToFingerprint( rotation: DisplayRotation, - isInRearDisplayMode: Boolean + isInRearDisplayMode: Boolean, ): Int = if (isInRearDisplayMode) { when (rotation) { @@ -692,7 +692,7 @@ class PromptIconViewModel( private fun getSfpsAsset_fingerprintToUnlock( rotation: DisplayRotation, - isInRearDisplayMode: Boolean + isInRearDisplayMode: Boolean, ): Int = if (isInRearDisplayMode) { when (rotation) { @@ -716,7 +716,7 @@ class PromptIconViewModel( private fun getSfpsAsset_fingerprintToSuccess( rotation: DisplayRotation, - isInRearDisplayMode: Boolean + isInRearDisplayMode: Boolean, ): Int = if (isInRearDisplayMode) { when (rotation) { diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt new file mode 100644 index 000000000000..9dd3b6de423e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bluetooth.qsdialog + +import android.view.LayoutInflater +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import com.android.systemui.plugins.qs.TileDetailsViewModel +import com.android.systemui.res.R + +class BluetoothDetailsViewModel(onLongClick: () -> Unit) : TileDetailsViewModel() { + private val _onLongClick = onLongClick + + @Composable + override fun GetContentView() { + AndroidView( + modifier = Modifier.fillMaxWidth().fillMaxHeight(), + factory = { context -> + // Inflate with the existing dialog xml layout + LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null) + // TODO: b/378513956 - Implement the bluetooth details view + }, + ) + } + + override fun clickOnSettingsButton() { + _onLongClick() + } + + override fun getTitle(): String { + // TODO: b/378513956 Update the placeholder text + return "Bluetooth" + } + + override fun getSubTitle(): String { + // TODO: b/378513956 Update the placeholder text + return "Tap to connect or disconnect a device" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt index 9cfb5be478ed..b294dd1b0b71 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt @@ -41,6 +41,7 @@ import com.android.internal.R as InternalR import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.time.SystemClock import dagger.assisted.Assisted @@ -68,6 +69,7 @@ internal constructor( private val uiEventLogger: UiEventLogger, private val logger: BluetoothTileDialogLogger, private val systemuiDialogFactory: SystemUIDialog.Factory, + private val shadeDialogContextInteractor: ShadeDialogContextInteractor, ) : SystemUIDialog.Delegate { private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null) @@ -105,7 +107,7 @@ internal constructor( } override fun createDialog(): SystemUIDialog { - return systemuiDialogFactory.create(this) + return systemuiDialogFactory.create(this, shadeDialogContextInteractor.context) } override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { @@ -405,10 +407,11 @@ internal constructor( } // updating icon colors - val tintColor = context.getColor( - if (item.isActive) InternalR.color.materialColorOnPrimaryContainer - else InternalR.color.materialColorOnSurface - ) + val tintColor = + context.getColor( + if (item.isActive) InternalR.color.materialColorOnPrimaryContainer + else InternalR.color.materialColorOnSurface + ) // update icons iconView.apply { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt index a9f8f37fffa9..4246430e8034 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingStartModule.kt @@ -27,5 +27,5 @@ interface FalsingStartModule { @Binds @IntoMap @ClassKey(FalsingCoreStartable::class) - fun bindFalsingCoreStartable(falsingCoreStartable: FalsingCoreStartable?): CoreStartable? + fun bindFalsingCoreStartable(falsingCoreStartable: FalsingCoreStartable): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt index 4d804d06fe87..747a2a9bd887 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt @@ -53,8 +53,12 @@ interface ConfigurationRepository { val onConfigurationChange: Flow<Unit> val scaleForResolution: Flow<Float> + val configurationValues: Flow<Configuration> + /** Emits the latest display this configuration controller has been moved to. */ + val onMovedToDisplay: Flow<Int> + fun getResolutionScale(): Float /** Convenience to context.resources.getDimensionPixelSize() */ @@ -117,6 +121,20 @@ constructor( configurationController.addCallback(callback) awaitClose { configurationController.removeCallback(callback) } } + override val onMovedToDisplay: Flow<Int> + get() = conflatedCallbackFlow { + val callback = + object : ConfigurationController.ConfigurationListener { + override fun onMovedToDisplay( + newDisplayId: Int, + newConfiguration: Configuration?, + ) { + trySend(newDisplayId) + } + } + configurationController.addCallback(callback) + awaitClose { configurationController.removeCallback(callback) } + } override val scaleForResolution: StateFlow<Float> = onConfigurationChange diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt index 26abb48ce7db..73c0179cf8ec 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -55,6 +55,8 @@ interface CommunalSettingsRepository { /** A [CommunalEnabledState] for the specified user. */ fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> + fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> + /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * @@ -138,6 +140,20 @@ constructor( .flowOn(bgDispatcher) } + override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> = + secureSettings + .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED)) + // Force an update + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser( + Settings.Secure.SCREENSAVER_ENABLED, + SCREENSAVER_ENABLED_SETTING_DEFAULT, + user.id, + ) == 1 + } + .flowOn(bgDispatcher) + override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = broadcastDispatcher .broadcastFlow( @@ -182,6 +198,7 @@ constructor( companion object { const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background" private const val ENABLED_SETTING_DEFAULT = 1 + private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0 } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index 862b05bc9b5d..c1f21e4046a3 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -69,6 +69,12 @@ constructor( // Start this eagerly since the value is accessed synchronously in many places. .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) + /** Whether or not screensaver (dreams) is enabled for the currently selected user. */ + val isScreensaverEnabled: Flow<Boolean> = + userInteractor.selectedUserInfo.flatMapLatest { user -> + repository.getScreensaverEnabledState(user) + } + /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt deleted file mode 100644 index 71bfe0c057e4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.communal.ui.binder - -import android.content.Context -import android.os.Bundle -import android.util.SizeF -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.compose.ui.unit.IntSize -import androidx.core.view.doOnLayout -import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.systemui.Flags.communalWidgetResizing -import com.android.systemui.common.ui.view.onLayoutChanged -import com.android.systemui.communal.domain.model.CommunalContentModel -import com.android.systemui.communal.util.WidgetViewFactory -import com.android.systemui.util.kotlin.DisposableHandles -import com.android.systemui.util.kotlin.toDp -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DisposableHandle -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOn - -object CommunalAppWidgetHostViewBinder { - private const val TAG = "CommunalAppWidgetHostViewBinder" - - fun bind( - context: Context, - applicationScope: CoroutineScope, - mainContext: CoroutineContext, - backgroundContext: CoroutineContext, - container: FrameLayout, - model: CommunalContentModel.WidgetContent.Widget, - size: SizeF?, - factory: WidgetViewFactory, - ): DisposableHandle { - val disposables = DisposableHandles() - - val loadingJob = - applicationScope.launch("$TAG#createWidgetView") { - val widget = factory.createWidget(context, model, size) - waitForLayout(container) - container.post { container.setView(widget) } - if (communalWidgetResizing()) { - // Update the app widget size in the background. - launch("$TAG#updateSize", backgroundContext) { - container.sizeFlow().flowOn(mainContext).distinctUntilChanged().collect { - (width, height) -> - widget.updateAppWidgetSize( - /* newOptions = */ Bundle(), - /* minWidth = */ width, - /* minHeight = */ height, - /* maxWidth = */ width, - /* maxHeight = */ height, - /* ignorePadding = */ true, - ) - } - } - } - } - - disposables += DisposableHandle { loadingJob.cancel() } - disposables += DisposableHandle { container.removeAllViews() } - - return disposables - } - - private suspend fun waitForLayout(container: FrameLayout) = suspendCoroutine { cont -> - container.doOnLayout { cont.resume(Unit) } - } -} - -private fun ViewGroup.setView(view: View) { - if (view.parent == this) { - return - } - (view.parent as? ViewGroup)?.removeView(view) - addView(view) -} - -private fun View.sizeAsDp(): IntSize = IntSize(width.toDp(context), height.toDp(context)) - -private fun View.sizeFlow(): Flow<IntSize> = conflatedCallbackFlow { - if (isLaidOut && !isLayoutRequested) { - trySend(sizeAsDp()) - } - val disposable = onLayoutChanged { trySend(sizeAsDp()) } - awaitClose { disposable.dispose() } -} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt index 2e12bad744f0..9f19562bb668 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt @@ -16,98 +16,132 @@ package com.android.systemui.communal.ui.view.layout.sections +import android.os.Bundle import android.util.SizeF +import android.view.View import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS -import android.widget.FrameLayout +import android.view.accessibility.AccessibilityNodeInfo import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.systemui.Flags.communalWidgetResizing +import com.android.systemui.Flags.communalHubUseThreadPoolForWidgets import com.android.systemui.communal.domain.model.CommunalContentModel -import com.android.systemui.communal.ui.binder.CommunalAppWidgetHostViewBinder -import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel -import com.android.systemui.communal.util.WidgetViewFactory -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.communal.ui.viewmodel.CommunalAppWidgetViewModel +import com.android.systemui.communal.widgets.CommunalAppWidgetHostView +import com.android.systemui.communal.widgets.WidgetInteractionHandler import com.android.systemui.dagger.qualifiers.UiBackground +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R +import java.util.concurrent.Executor +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit import javax.inject.Inject -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DisposableHandle class CommunalAppWidgetSection @Inject constructor( - @Application private val applicationScope: CoroutineScope, - @Main private val mainContext: CoroutineContext, - @UiBackground private val backgroundContext: CoroutineContext, - private val factory: WidgetViewFactory, + @UiBackground private val uiBgExecutor: Executor, + private val interactionHandler: WidgetInteractionHandler, + private val viewModelFactory: CommunalAppWidgetViewModel.Factory, ) { private companion object { - val DISPOSABLE_TAG = R.id.communal_widget_disposable_tag + const val TAG = "CommunalAppWidgetSection" + val LISTENER_TAG = R.id.communal_widget_listener_tag + + val poolSize by lazy { Runtime.getRuntime().availableProcessors().coerceAtLeast(2) } + + /** + * This executor is used for widget inflation. Parameters match what launcher uses. See + * [com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR]. + */ + val widgetExecutor by lazy { + ThreadPoolExecutor( + /*corePoolSize*/ poolSize, + /*maxPoolSize*/ poolSize, + /*keepAlive*/ 1, + /*unit*/ TimeUnit.SECONDS, + /*workQueue*/ LinkedBlockingQueue(), + ) + } } @Composable fun Widget( - viewModel: BaseCommunalViewModel, + isFocusable: Boolean, + openWidgetEditor: () -> Unit, model: CommunalContentModel.WidgetContent.Widget, size: SizeF, modifier: Modifier = Modifier, ) { - val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false) + val viewModel = rememberViewModel("$TAG#viewModel") { viewModelFactory.create() } + val longClickLabel = stringResource(R.string.accessibility_action_label_edit_widgets) + val accessibilityDelegate = + remember(longClickLabel, openWidgetEditor) { + WidgetAccessibilityDelegate(longClickLabel, openWidgetEditor) + } AndroidView( factory = { context -> - FrameLayout(context).apply { - layoutParams = - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT, - ) - - // Need to attach the disposable handle to the view here instead of storing - // the state in the composable in order to properly support lazy lists. In a - // lazy list, when the composable is no longer in view - it will exit - // composition and any state inside the composable will be lost. However, - // the View instance will be re-used. Therefore we can store data on the view - // in order to preserve it. - setTag( - DISPOSABLE_TAG, - CommunalAppWidgetHostViewBinder.bind( - context = context, - container = this, - model = model, - size = if (!communalWidgetResizing()) size else null, - factory = factory, - applicationScope = applicationScope, - mainContext = mainContext, - backgroundContext = backgroundContext, - ), - ) - - accessibilityDelegate = viewModel.widgetAccessibilityDelegate + CommunalAppWidgetHostView(context, interactionHandler).apply { + if (communalHubUseThreadPoolForWidgets()) { + setExecutor(widgetExecutor) + } else { + setExecutor(uiBgExecutor) + } } }, - update = { container -> - container.importantForAccessibility = + update = { view -> + view.accessibilityDelegate = accessibilityDelegate + view.importantForAccessibility = if (isFocusable) { IMPORTANT_FOR_ACCESSIBILITY_AUTO } else { IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS } - }, - onRelease = { view -> - val disposable = (view.getTag(DISPOSABLE_TAG) as? DisposableHandle) - disposable?.dispose() + view.setAppWidget(model.appWidgetId, model.providerInfo) + // To avoid calling the expensive setListener method on every recomposition if + // the appWidgetId hasn't changed, we store the current appWidgetId of the view in + // a tag. + if ((view.getTag(LISTENER_TAG) as? Int) != model.appWidgetId) { + viewModel.setListener(model.appWidgetId, view) + view.setTag(LISTENER_TAG, model.appWidgetId) + } + viewModel.updateSize(size, view) }, modifier = modifier, // For reusing composition in lazy lists. onReset = {}, ) } + + private class WidgetAccessibilityDelegate( + private val longClickLabel: String, + private val longClickAction: () -> Unit, + ) : View.AccessibilityDelegate() { + override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) { + super.onInitializeAccessibilityNodeInfo(host, info) + // Hint user to long press in order to enter edit mode + info.addAction( + AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id, + longClickLabel.lowercase(), + ) + ) + } + + override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean { + when (action) { + AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id -> { + longClickAction() + return true + } + } + return super.performAccessibilityAction(host, action, args) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index a339af3694e7..099a85926020 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -19,7 +19,6 @@ package com.android.systemui.communal.ui.viewmodel import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName import android.os.UserHandle -import android.view.View import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey @@ -80,9 +79,6 @@ abstract class BaseCommunalViewModel( */ val glanceableTouchAvailable: Flow<Boolean> = anyOf(not(isTouchConsumed), isNestedScrolling) - /** Accessibility delegate to be set on CommunalAppWidgetHostView. */ - open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null - /** * The up-to-date value of the grid scroll offset. persisted to interactor on * {@link #persistScrollPosition} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt new file mode 100644 index 000000000000..6bafd14f9359 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import android.appwidget.AppWidgetHost.AppWidgetHostListener +import android.appwidget.AppWidgetHostView +import android.os.Bundle +import android.util.SizeF +import com.android.app.tracing.coroutines.coroutineScopeTraced +import com.android.app.tracing.coroutines.withContextTraced +import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper +import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate +import com.android.systemui.communal.widgets.CommunalAppWidgetHost +import com.android.systemui.communal.widgets.GlanceableHubWidgetManager +import com.android.systemui.dagger.qualifiers.UiBackground +import com.android.systemui.lifecycle.ExclusiveActivatable +import dagger.Lazy +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow + +/** View model for showing a widget. */ +class CommunalAppWidgetViewModel +@AssistedInject +constructor( + @UiBackground private val backgroundContext: CoroutineContext, + private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>, + private val listenerDelegateFactory: AppWidgetHostListenerDelegate.Factory, + private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>, + private val multiUserHelper: GlanceableHubMultiUserHelper, +) : ExclusiveActivatable() { + + private companion object { + const val TAG = "CommunalAppWidgetViewModel" + const val CHANNEL_CAPACITY = 10 + } + + @AssistedFactory + interface Factory { + fun create(): CommunalAppWidgetViewModel + } + + private val requests = + Channel<Request>(capacity = CHANNEL_CAPACITY, onBufferOverflow = BufferOverflow.DROP_OLDEST) + + fun setListener(appWidgetId: Int, listener: AppWidgetHostListener) { + requests.trySend(SetListener(appWidgetId, listener)) + } + + fun updateSize(size: SizeF, view: AppWidgetHostView) { + requests.trySend(UpdateSize(size, view)) + } + + override suspend fun onActivated(): Nothing { + coroutineScopeTraced("$TAG#onActivated") { + requests.receiveAsFlow().collect { request -> + when (request) { + is SetListener -> handleSetListener(request.appWidgetId, request.listener) + is UpdateSize -> handleUpdateSize(request.size, request.view) + } + } + } + + awaitCancellation() + } + + private suspend fun handleSetListener(appWidgetId: Int, listener: AppWidgetHostListener) = + withContextTraced("$TAG#setListenerInner", backgroundContext) { + if ( + multiUserHelper.glanceableHubHsumFlagEnabled && + multiUserHelper.isInHeadlessSystemUser() + ) { + // If the widget view is created in the headless system user, the widget host lives + // remotely in the foreground user, and therefore the host listener needs to be + // registered through the widget manager. + with(glanceableHubWidgetManagerLazy.get()) { + setAppWidgetHostListener(appWidgetId, listenerDelegateFactory.create(listener)) + } + } else { + // Instead of setting the view as the listener directly, we wrap the view in a + // delegate which ensures the callbacks always get called on the main thread. + with(appWidgetHostLazy.get()) { + setListener(appWidgetId, listenerDelegateFactory.create(listener)) + } + } + } + + private suspend fun handleUpdateSize(size: SizeF, view: AppWidgetHostView) = + withContextTraced("$TAG#updateSizeInner", backgroundContext) { + view.updateAppWidgetSize( + /* newOptions = */ Bundle(), + /* minWidth = */ size.width.toInt(), + /* minHeight = */ size.height.toInt(), + /* maxWidth = */ size.width.toInt(), + /* maxHeight = */ size.height.toInt(), + /* ignorePadding = */ true, + ) + } +} + +private sealed interface Request + +/** + * [Request] to call [CommunalAppWidgetHost.setListener] to tie this view to a particular widget. + * Since this is involves an IPC to system_server, the call is asynchronous and happens in the + * background. + */ +private data class SetListener(val appWidgetId: Int, val listener: AppWidgetHostListener) : Request + +/** + * [Request] to call [AppWidgetHostView.updateAppWidgetSize] to notify the widget provider of the + * new size. Since this is involves an IPC to system_server, the call is asynchronous and happens in + * the background. + */ +private data class UpdateSize(val size: SizeF, val view: AppWidgetHostView) : Request diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt index 7d5b196dfaa8..c6f96e198b91 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt @@ -18,10 +18,15 @@ package com.android.systemui.communal.ui.viewmodel import android.annotation.SuppressLint import android.app.DreamManager +import android.content.Intent +import android.provider.Settings +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.util.kotlin.isDevicePluggedIn +import com.android.systemui.util.kotlin.sample import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlin.coroutines.CoroutineContext @@ -31,7 +36,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -41,6 +45,8 @@ class CommunalToDreamButtonViewModel constructor( @Background private val backgroundContext: CoroutineContext, batteryController: BatteryController, + private val settingsInteractor: CommunalSettingsInteractor, + private val activityStarter: ActivityStarter, private val dreamManager: DreamManager, ) : ExclusiveActivatable() { @@ -49,11 +55,7 @@ constructor( /** Whether we should show a button on hub to switch to dream. */ @SuppressLint("MissingPermission") val shouldShowDreamButtonOnHub = - batteryController - .isDevicePluggedIn() - .distinctUntilChanged() - .map { isPluggedIn -> isPluggedIn && dreamManager.canStartDreaming(true) } - .flowOn(backgroundContext) + batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(backgroundContext) /** Handle a tap on the "show dream" button. */ fun onShowDreamButtonTap() { @@ -63,9 +65,21 @@ constructor( @SuppressLint("MissingPermission") override suspend fun onActivated(): Nothing = coroutineScope { launch { - _requests.receiveAsFlow().collectLatest { - withContext(backgroundContext) { dreamManager.startDream() } - } + _requests + .receiveAsFlow() + .sample(settingsInteractor.isScreensaverEnabled) + .collectLatest { enabled -> + withContext(backgroundContext) { + if (enabled) { + dreamManager.startDream() + } else { + activityStarter.postStartActivityDismissingKeyguard( + Intent(Settings.ACTION_DREAM_SETTINGS), + 0, + ) + } + } + } } awaitCancellation() diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt index 63a497213255..ce3a2be9229e 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt @@ -17,21 +17,17 @@ package com.android.systemui.communal.ui.viewmodel import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf /** View model for communal tutorial indicator on keyguard */ class CommunalTutorialIndicatorViewModel @Inject -constructor( - private val communalTutorialInteractor: CommunalTutorialInteractor, - bottomAreaInteractor: KeyguardBottomAreaInteractor, -) { +constructor(private val communalTutorialInteractor: CommunalTutorialInteractor) { /** * An observable for whether the tutorial indicator view should be visible. * @@ -46,5 +42,6 @@ constructor( } /** An observable for the alpha level for the tutorial indicator. */ - val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged() + // TODO("b/383587536") find replacement for keyguardBottomAreaInteractor alpha + val alpha: Flow<Float> = flowOf(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 83bd265db5f6..ddc4d1c10690 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -17,10 +17,6 @@ package com.android.systemui.communal.ui.viewmodel import android.content.ComponentName -import android.content.res.Resources -import android.os.Bundle -import android.view.View -import android.view.accessibility.AccessibilityNodeInfo import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags import com.android.systemui.communal.domain.interactor.CommunalInteractor @@ -45,7 +41,6 @@ import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.media.dagger.MediaModule -import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.KeyguardIndicationController @@ -85,7 +80,6 @@ constructor( @Main val mainDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, @Background private val bgScope: CoroutineScope, - @Main private val resources: Resources, keyguardTransitionInteractor: KeyguardTransitionInteractor, keyguardInteractor: KeyguardInteractor, private val keyguardIndicationController: KeyguardIndicationController, @@ -219,39 +213,6 @@ constructor( } .distinctUntilChanged() - override val widgetAccessibilityDelegate = - object : View.AccessibilityDelegate() { - override fun onInitializeAccessibilityNodeInfo( - host: View, - info: AccessibilityNodeInfo, - ) { - super.onInitializeAccessibilityNodeInfo(host, info) - // Hint user to long press in order to enter edit mode - info.addAction( - AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id, - resources - .getString(R.string.accessibility_action_label_edit_widgets) - .lowercase(), - ) - ) - } - - override fun performAccessibilityAction( - host: View, - action: Int, - args: Bundle?, - ): Boolean { - when (action) { - AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id -> { - onOpenWidgetEditor() - return true - } - } - return super.performAccessibilityAction(host, action, args) - } - } - private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false) val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt deleted file mode 100644 index 50d86a24be96..000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.communal.util - -import android.content.Context -import android.os.Bundle -import android.util.SizeF -import com.android.app.tracing.coroutines.withContextTraced as withContext -import com.android.systemui.Flags -import com.android.systemui.communal.domain.model.CommunalContentModel -import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper -import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate -import com.android.systemui.communal.widgets.CommunalAppWidgetHost -import com.android.systemui.communal.widgets.CommunalAppWidgetHostView -import com.android.systemui.communal.widgets.GlanceableHubWidgetManager -import com.android.systemui.communal.widgets.WidgetInteractionHandler -import com.android.systemui.dagger.qualifiers.UiBackground -import dagger.Lazy -import java.util.concurrent.Executor -import java.util.concurrent.LinkedBlockingQueue -import java.util.concurrent.ThreadPoolExecutor -import java.util.concurrent.TimeUnit -import javax.inject.Inject -import kotlin.coroutines.CoroutineContext - -/** Factory for creating [CommunalAppWidgetHostView] in a background thread. */ -class WidgetViewFactory -@Inject -constructor( - @UiBackground private val uiBgContext: CoroutineContext, - @UiBackground private val uiBgExecutor: Executor, - private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>, - private val interactionHandler: WidgetInteractionHandler, - private val listenerFactory: AppWidgetHostListenerDelegate.Factory, - private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>, - private val multiUserHelper: GlanceableHubMultiUserHelper, -) { - suspend fun createWidget( - context: Context, - model: CommunalContentModel.WidgetContent.Widget, - size: SizeF?, - ): CommunalAppWidgetHostView = - withContext("$TAG#createWidget", uiBgContext) { - val view = - CommunalAppWidgetHostView(context, interactionHandler).apply { - if (Flags.communalHubUseThreadPoolForWidgets()) { - setExecutor(widgetExecutor) - } else { - setExecutor(uiBgExecutor) - } - setAppWidget(model.appWidgetId, model.providerInfo) - } - - if ( - multiUserHelper.glanceableHubHsumFlagEnabled && - multiUserHelper.isInHeadlessSystemUser() - ) { - // If the widget view is created in the headless system user, the widget host lives - // remotely in the foreground user, and therefore the host listener needs to be - // registered through the widget manager. - with(glanceableHubWidgetManagerLazy.get()) { - setAppWidgetHostListener(model.appWidgetId, listenerFactory.create(view)) - } - } else { - // Instead of setting the view as the listener directly, we wrap the view in a - // delegate which ensures the callbacks always get called on the main thread. - with(appWidgetHostLazy.get()) { - setListener(model.appWidgetId, listenerFactory.create(view)) - } - } - - if (size != null) { - view.updateAppWidgetSize( - /* newOptions = */ Bundle(), - /* minWidth = */ size.width.toInt(), - /* minHeight = */ size.height.toInt(), - /* maxWidth = */ size.width.toInt(), - /* maxHeight = */ size.height.toInt(), - /* ignorePadding = */ true, - ) - } - view - } - - private companion object { - const val TAG = "WidgetViewFactory" - - val poolSize = Runtime.getRuntime().availableProcessors().coerceAtLeast(2) - - /** - * This executor is used for widget inflation. Parameters match what launcher uses. See - * [com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR]. - */ - val widgetExecutor = - ThreadPoolExecutor( - /*corePoolSize*/ poolSize, - /*maxPoolSize*/ poolSize, - /*keepAlive*/ 1, - /*unit*/ TimeUnit.SECONDS, - /*workQueue*/ LinkedBlockingQueue(), - ) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt index f3416216afdd..7d80acd1f439 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt @@ -36,7 +36,7 @@ constructor( ) : AppWidgetHostListener { @AssistedFactory - interface Factory { + fun interface Factory { fun create(listener: AppWidgetHostListener): AppWidgetHostListenerDelegate } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt index 6c335e71cfde..0ab9661c0b4e 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -16,18 +16,13 @@ package com.android.systemui.deviceentry -import com.android.keyguard.EmptyLockIconViewController -import com.android.keyguard.LockIconViewController import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import dagger.Binds -import dagger.Lazy import dagger.Module -import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import dagger.multibindings.Multibinds @@ -45,14 +40,4 @@ abstract class DeviceEntryModule { abstract fun deviceUnlockedInteractorActivator( activator: DeviceUnlockedInteractor.Activator ): CoreStartable - - companion object { - @Provides - @SysUISingleton - fun provideLockIconViewController( - emptyLockIconViewController: Lazy<EmptyLockIconViewController> - ): LockIconViewController { - return emptyLockIconViewController.get() - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 91b44e7a6202..e1ebf7cdf472 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -20,6 +20,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; @@ -120,6 +121,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository; +import com.android.systemui.display.shared.model.DisplayWindowProperties; import com.android.systemui.globalactions.domain.interactor.GlobalActionsInteractor; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; @@ -127,6 +130,7 @@ import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.scrim.ScrimDrawable; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; +import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.LightBarController; @@ -149,6 +153,8 @@ import java.util.concurrent.Executor; import javax.inject.Inject; +import dagger.Lazy; + /** * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending * on whether the keyguard is showing, and whether the device is provisioned. @@ -194,6 +200,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene // See NotificationManagerService.LONG_DELAY private static final int TOAST_VISIBLE_TIME = 3500; + private static final int DIALOG_WINDOW_TYPE = TYPE_STATUS_BAR_SUB_PANEL; + private final Context mContext; private final GlobalActionsManager mWindowManagerFuncs; private final AudioManager mAudioManager; @@ -261,6 +269,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final DialogTransitionAnimator mDialogTransitionAnimator; private final UserLogoutInteractor mLogoutInteractor; private final GlobalActionsInteractor mInteractor; + private final Lazy<DisplayWindowPropertiesRepository> mDisplayWindowPropertiesRepositoryLazy; @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @@ -376,7 +385,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene DialogTransitionAnimator dialogTransitionAnimator, SelectedUserInteractor selectedUserInteractor, UserLogoutInteractor logoutInteractor, - GlobalActionsInteractor interactor) { + GlobalActionsInteractor interactor, + Lazy<DisplayWindowPropertiesRepository> displayWindowPropertiesRepository) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -413,6 +423,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mSelectedUserInteractor = selectedUserInteractor; mLogoutInteractor = logoutInteractor; mInteractor = interactor; + mDisplayWindowPropertiesRepositoryLazy = displayWindowPropertiesRepository; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -473,9 +484,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene * @param isDeviceProvisioned True if device is provisioned * @param expandable The expandable from which we should animate the dialog when * showing it + * @param displayId Display that should show the dialog */ public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, - @Nullable Expandable expandable) { + @Nullable Expandable expandable, int displayId) { mKeyguardShowing = keyguardShowing; mDeviceProvisioned = isDeviceProvisioned; if (mDialog != null && mDialog.isShowing()) { @@ -487,7 +499,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialog.dismiss(); mDialog = null; } else { - handleShow(expandable); + handleShow(expandable, displayId); } } @@ -507,8 +519,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mHandler.sendEmptyMessage(MESSAGE_DISMISS); } - protected void handleShow(@Nullable Expandable expandable) { - mDialog = createDialog(); + protected void handleShow(@Nullable Expandable expandable, int displayId) { + mDialog = createDialog(displayId); prepareDialog(); WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); @@ -686,16 +698,44 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mPowerAdapter = new MyPowerOptionsAdapter(); } + /** * Create the global actions dialog. * * @return A new dialog. */ protected ActionsDialogLite createDialog() { + return createDialog(mContext.getDisplayId()); + } + + private Context getContextForDisplay(int displayId) { + if (!ShadeWindowGoesAround.isEnabled()) { + Log.e(TAG, "Asked for the displayId=" + displayId + + " context but returning default display one as ShadeWindowGoesAround flag " + + "is disabled."); + return mContext; + } + try { + DisplayWindowProperties properties = mDisplayWindowPropertiesRepositoryLazy.get().get( + displayId, + DIALOG_WINDOW_TYPE); + return properties.getContext(); + } catch (Exception e) { + Log.e(TAG, "Couldn't get context for displayId=" + displayId); + return mContext; + } + } + /** + * Create the global actions dialog with a specific context. + * + * @return A new dialog. + */ + protected ActionsDialogLite createDialog(int displayId) { + final Context context = getContextForDisplay(displayId); initDialogItems(); ActionsDialogLite dialog = new ActionsDialogLite( - mContext, + context, com.android.systemui.res.R.style.Theme_SystemUI_Dialog_GlobalActionsLite, mAdapter, mOverflowAdapter, @@ -704,7 +744,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mLightBarController, mKeyguardStateController, mNotificationShadeWindowController, - mStatusBarWindowControllerStore.getDefaultDisplay(), + mStatusBarWindowControllerStore.forDisplay(context.getDisplayId()), this::onRefresh, mKeyguardShowing, mPowerAdapter, diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index c5027cc511a4..a6255d0be46a 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -63,7 +63,8 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks public void showGlobalActions(GlobalActionsManager manager) { if (mDisabled) return; mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(), - mDeviceProvisionedController.isDeviceProvisioned(), null /* view */); + mDeviceProvisionedController.isDeviceProvisioned(), null /* view */, + mContext.getDisplayId()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt index 3020e5dedd17..b59713696055 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt @@ -49,7 +49,7 @@ data class InternalKeyboardShortcutGroup( * @param isCustomShortcut If Shortcut is user customized or system defined. */ data class InternalKeyboardShortcutInfo( - val label: String, + val label: String = "", val keycode: Int, val modifiers: Int, val baseCharacter: Char = Char.MIN_VALUE, @@ -60,4 +60,4 @@ data class InternalKeyboardShortcutInfo( data class InternalGroupsSource( val groups: List<InternalKeyboardShortcutGroup>, val type: ShortcutCategoryType, -)
\ No newline at end of file +) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepository.kt new file mode 100644 index 000000000000..b029b03ec8b8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepository.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.data.repository + +import android.hardware.input.AppLaunchData +import android.hardware.input.InputGestureData.KeyTrigger +import android.hardware.input.InputManager +import android.util.Log +import android.view.InputDevice +import com.android.systemui.Flags.shortcutHelperKeyGlyph +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +@SysUISingleton +class AppLaunchDataRepository +@Inject +constructor( + private val inputManager: InputManager, + @Background private val backgroundScope: CoroutineScope, + private val shortcutCategoriesUtils: ShortcutCategoriesUtils, + inputDeviceRepository: ShortcutHelperInputDeviceRepository, +) { + + private val shortcutCommandToAppLaunchDataMap: + StateFlow<Map<ShortcutCommandKey, AppLaunchData>> = + inputDeviceRepository.activeInputDevice + .map { inputDevice -> + if (inputDevice == null) { + emptyMap() + } + else{ + buildCommandToAppLaunchDataMap(inputDevice) + } + } + .stateIn( + scope = backgroundScope, + started = SharingStarted.Eagerly, + initialValue = mapOf(), + ) + + fun getAppLaunchDataForShortcutWithCommand(shortcutCommand: ShortcutCommand): AppLaunchData? { + val shortcutCommandAsKey = ShortcutCommandKey(shortcutCommand) + return shortcutCommandToAppLaunchDataMap.value[shortcutCommandAsKey] + } + + private fun buildCommandToAppLaunchDataMap(inputDevice: InputDevice): + Map<ShortcutCommandKey, AppLaunchData> { + val commandToAppLaunchDataMap = + mutableMapOf<ShortcutCommandKey, AppLaunchData>() + val appLaunchInputGestures = inputManager.appLaunchBookmarks + appLaunchInputGestures.forEach { inputGesture -> + val keyGlyphMap = + if (shortcutHelperKeyGlyph()) { + inputManager.getKeyGlyphMap(inputDevice.id) + } else null + + val shortcutCommand = + shortcutCategoriesUtils.toShortcutCommand( + keyGlyphMap, + inputDevice.keyCharacterMap, + inputGesture.trigger as KeyTrigger, + ) + + if (shortcutCommand != null) { + commandToAppLaunchDataMap[ShortcutCommandKey(shortcutCommand)] = + inputGesture.action.appLaunchData()!! + } else { + Log.w( + TAG, + "could not get Shortcut Command. inputGesture: $inputGesture", + ) + } + } + + return commandToAppLaunchDataMap + } + + private data class ShortcutCommandKey(val keys: List<ShortcutKey>) { + constructor( + shortcutCommand: ShortcutCommand + ) : this(shortcutCommand.keys.sortedBy { it.toString() }) + } + + private companion object { + private const val TAG = "AppLaunchDataRepository" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt index 8afec04a621c..18ca877775df 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt @@ -30,48 +30,37 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.SingleShortcutCustomization import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.withContext import javax.inject.Inject -import kotlin.coroutines.CoroutineContext @SysUISingleton class CustomShortcutCategoriesRepository @Inject constructor( - stateRepository: ShortcutHelperStateRepository, + inputDeviceRepository: ShortcutHelperInputDeviceRepository, @Background private val backgroundScope: CoroutineScope, - @Background private val bgCoroutineContext: CoroutineContext, private val shortcutCategoriesUtils: ShortcutCategoriesUtils, private val inputGestureDataAdapter: InputGestureDataAdapter, private val customInputGesturesRepository: CustomInputGesturesRepository, - private val inputManager: InputManager + private val inputManager: InputManager, + private val appLaunchDataRepository: AppLaunchDataRepository, ) : ShortcutCategoriesRepository { private val _selectedKeyCombination = MutableStateFlow<KeyCombination?>(null) private val _shortcutBeingCustomized = mutableStateOf<ShortcutCustomizationRequestInfo?>(null) - private val activeInputDevice = - stateRepository.state.map { - if (it is Active) { - withContext(bgCoroutineContext) { inputManager.getInputDevice(it.deviceId) } - } else { - null - } - } - val pressedKeys = _selectedKeyCombination - .combine(activeInputDevice) { keyCombination, inputDevice -> + .combine(inputDeviceRepository.activeInputDevice) { keyCombination, inputDevice -> if (inputDevice == null || keyCombination == null) { return@combine emptyList() } else { @@ -105,8 +94,10 @@ constructor( ) override val categories: Flow<List<ShortcutCategory>> = - combine(activeInputDevice, customInputGesturesRepository.customInputGestures) - { inputDevice, inputGestures -> + combine( + inputDeviceRepository.activeInputDevice, + customInputGesturesRepository.customInputGestures, + ) { inputDevice, inputGestures -> if (inputDevice == null) { emptyList() } else { @@ -147,10 +138,10 @@ constructor( fun buildInputGestureDataForShortcutBeingCustomized(): InputGestureData? { try { return Builder() - .addKeyGestureTypeFromShortcutLabel() + .addKeyGestureTypeForShortcutBeingCustomized() .addTriggerFromSelectedKeyCombination() + .addAppLaunchDataFromShortcutBeingCustomized() .build() - // TODO(b/379648200) add app launch data after dynamic label/icon mapping implementation } catch (e: IllegalArgumentException) { Log.w(TAG, "could not add custom shortcut: $e") return null @@ -158,9 +149,10 @@ constructor( } private fun retrieveInputGestureDataForShortcutBeingDeleted(): InputGestureData? { - val keyGestureType = getKeyGestureTypeFromShortcutBeingDeletedLabel() - return customInputGesturesRepository.retrieveCustomInputGestures() - .firstOrNull { it.action.keyGestureType() == keyGestureType } + val keyGestureType = getKeyGestureTypeForShortcutBeingCustomized() + return customInputGesturesRepository.retrieveCustomInputGestures().firstOrNull { + it.action.keyGestureType() == keyGestureType + } } suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): @@ -183,8 +175,8 @@ constructor( return customInputGesturesRepository.resetAllCustomInputGestures() } - private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder { - val keyGestureType = getKeyGestureTypeFromShortcutBeingCustomizedLabel() + private fun Builder.addKeyGestureTypeForShortcutBeingCustomized(): Builder { + val keyGestureType = getKeyGestureTypeForShortcutBeingCustomized() if (keyGestureType == null) { Log.w( @@ -193,31 +185,28 @@ constructor( ) return this } - return setKeyGestureType(keyGestureType) } - @KeyGestureType - private fun getKeyGestureTypeFromShortcutBeingCustomizedLabel(): Int? { + private fun Builder.addAppLaunchDataFromShortcutBeingCustomized(): Builder { val shortcutBeingCustomized = - getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Add + (_shortcutBeingCustomized.value as? SingleShortcutCustomization) ?: return this - if (shortcutBeingCustomized == null) { - Log.w( - TAG, - "Requested key gesture type from label but shortcut being customized is null", - ) - return null + if (shortcutBeingCustomized.categoryType != ShortcutCategoryType.AppCategories) { + return this } - return inputGestureDataAdapter - .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label) + val defaultShortcutCommand = shortcutBeingCustomized.shortcutCommand + + val appLaunchData = + appLaunchDataRepository.getAppLaunchDataForShortcutWithCommand(defaultShortcutCommand) + + return if (appLaunchData == null) this else this.setAppLaunchData(appLaunchData) } @KeyGestureType - private fun getKeyGestureTypeFromShortcutBeingDeletedLabel(): Int? { - val shortcutBeingCustomized = - getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Delete + private fun getKeyGestureTypeForShortcutBeingCustomized(): Int? { + val shortcutBeingCustomized = getShortcutBeingCustomized() as? SingleShortcutCustomization if (shortcutBeingCustomized == null) { Log.w( @@ -227,8 +216,10 @@ constructor( return null } - return inputGestureDataAdapter - .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label) + return inputGestureDataAdapter.getKeyGestureTypeForShortcut( + shortcutLabel = shortcutBeingCustomized.label, + shortcutCategoryType = shortcutBeingCustomized.categoryType, + ) } private fun Builder.addTriggerFromSelectedKeyCombination(): Builder { diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt index 5bb7cdd03b8f..db35d49e7598 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt @@ -16,7 +16,6 @@ package com.android.systemui.keyboard.shortcut.data.repository -import android.hardware.input.InputManager import android.view.KeyboardShortcutGroup import android.view.KeyboardShortcutInfo import com.android.systemui.dagger.SysUISingleton @@ -36,29 +35,24 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType. import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.withContext @SysUISingleton class DefaultShortcutCategoriesRepository @Inject constructor( @Background private val backgroundScope: CoroutineScope, - @Background private val backgroundDispatcher: CoroutineDispatcher, @SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource, @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource, @AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource, @InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource, @CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource, - private val inputManager: InputManager, - stateRepository: ShortcutHelperStateRepository, + inputDeviceRepository: ShortcutHelperInputDeviceRepository, shortcutCategoriesUtils: ShortcutCategoriesUtils, ) : ShortcutCategoriesRepository { @@ -83,17 +77,8 @@ constructor( ), ) - private val activeInputDevice = - stateRepository.state.map { - if (it is Active) { - withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) } - } else { - null - } - } - override val categories: Flow<List<ShortcutCategory>> = - activeInputDevice + inputDeviceRepository.activeInputDevice .map { inputDevice -> if (inputDevice == null) { return@map emptyList() diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt index df7101e21cce..6e754a37ebca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt @@ -48,49 +48,54 @@ import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import javax.inject.Inject - +/** + * Serves as a bridge for converting InputGestureData API Models to Shortcut Helper Data Layer + * Models and vice versa. + */ class InputGestureDataAdapter @Inject constructor( private val userTracker: UserTracker, private val inputGestureMaps: InputGestureMaps, - private val context: Context + private val context: Context, ) { private val userContext: Context get() = userTracker.createCurrentUserContext(userTracker.userContext) - fun toInternalGroupSources( - inputGestures: List<InputGestureData> - ): List<InternalGroupsSource> { + fun toInternalGroupSources(inputGestures: List<InputGestureData>): List<InternalGroupsSource> { val ungroupedInternalGroupSources = inputGestures.mapNotNull { gestureData -> val keyTrigger = gestureData.trigger as KeyTrigger val keyGestureType = gestureData.action.keyGestureType() val appLaunchData: AppLaunchData? = gestureData.action.appLaunchData() fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel -> - toInternalKeyboardShortcutInfo( - keyGestureType, - keyTrigger, - appLaunchData - )?.let { internalKeyboardShortcutInfo -> - val group = - InternalKeyboardShortcutGroup( - label = groupLabel, - items = listOf(internalKeyboardShortcutInfo), - ) - - fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let { - InternalGroupsSource(groups = listOf(group), type = it) + toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger, appLaunchData) + ?.let { internalKeyboardShortcutInfo -> + val group = + InternalKeyboardShortcutGroup( + label = groupLabel, + items = listOf(internalKeyboardShortcutInfo), + ) + + fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let { + InternalGroupsSource(groups = listOf(group), type = it) + } } - } } } return ungroupedInternalGroupSources } - fun getKeyGestureTypeFromShortcutLabel(label: String): Int? { - return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[label] + fun getKeyGestureTypeForShortcut( + shortcutLabel: String, + shortcutCategoryType: ShortcutCategoryType, + ): Int? { + if (shortcutCategoryType == ShortcutCategoryType.AppCategories) { + return KEY_GESTURE_TYPE_LAUNCH_APPLICATION + } + val result = inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutLabel] + return result } private fun toInternalKeyboardShortcutInfo( @@ -104,16 +109,14 @@ constructor( keycode = keyTrigger.keycode, modifiers = keyTrigger.modifierState, isCustomShortcut = true, - icon = appLaunchData?.let { fetchShortcutIconByAppLaunchData(appLaunchData) } + icon = appLaunchData?.let { fetchShortcutIconByAppLaunchData(appLaunchData) }, ) } return null } @SuppressLint("QueryPermissionsNeeded") - private fun fetchShortcutIconByAppLaunchData( - appLaunchData: AppLaunchData - ): Icon? { + private fun fetchShortcutIconByAppLaunchData(appLaunchData: AppLaunchData): Icon? { val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null val resolvedActivity = resolveSingleMatchingActivityFrom(intent) @@ -132,7 +135,7 @@ constructor( private fun fetchShortcutLabelByGestureType( @KeyGestureType keyGestureType: Int, - appLaunchData: AppLaunchData? + appLaunchData: AppLaunchData?, ): String? { inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let { return context.getString(it) @@ -152,16 +155,14 @@ constructor( return if (resolvedActivity == null) { getIntentCategoryLabel(intent.selector?.categories?.iterator()?.next()) } else resolvedActivity.loadLabel(userContext.packageManager).toString() - } @SuppressLint("QueryPermissionsNeeded") private fun resolveSingleMatchingActivityFrom(intent: Intent): ActivityInfo? { val packageManager = userContext.packageManager - val resolvedActivity = intent.resolveActivityInfo( - packageManager, - /* flags= */ MATCH_DEFAULT_ONLY - ) ?: return null + val resolvedActivity = + intent.resolveActivityInfo(packageManager, /* flags= */ MATCH_DEFAULT_ONLY) + ?: return null val matchesMultipleActivities = ResolverActivity::class.qualifiedName.equals(resolvedActivity.name) @@ -172,22 +173,26 @@ constructor( } private fun getIntentCategoryLabel(category: String?): String? { - val categoryLabelRes = when (category.toString()) { - Intent.CATEGORY_APP_BROWSER -> R.string.keyboard_shortcut_group_applications_browser - Intent.CATEGORY_APP_CONTACTS -> R.string.keyboard_shortcut_group_applications_contacts - Intent.CATEGORY_APP_EMAIL -> R.string.keyboard_shortcut_group_applications_email - Intent.CATEGORY_APP_CALENDAR -> R.string.keyboard_shortcut_group_applications_calendar - Intent.CATEGORY_APP_MAPS -> R.string.keyboard_shortcut_group_applications_maps - Intent.CATEGORY_APP_MUSIC -> R.string.keyboard_shortcut_group_applications_music - Intent.CATEGORY_APP_MESSAGING -> R.string.keyboard_shortcut_group_applications_sms - Intent.CATEGORY_APP_CALCULATOR -> R.string.keyboard_shortcut_group_applications_calculator - else -> { - Log.w(TAG, ("No label for app category $category")) - null + val categoryLabelRes = + when (category.toString()) { + Intent.CATEGORY_APP_BROWSER -> R.string.keyboard_shortcut_group_applications_browser + Intent.CATEGORY_APP_CONTACTS -> + R.string.keyboard_shortcut_group_applications_contacts + Intent.CATEGORY_APP_EMAIL -> R.string.keyboard_shortcut_group_applications_email + Intent.CATEGORY_APP_CALENDAR -> + R.string.keyboard_shortcut_group_applications_calendar + Intent.CATEGORY_APP_MAPS -> R.string.keyboard_shortcut_group_applications_maps + Intent.CATEGORY_APP_MUSIC -> R.string.keyboard_shortcut_group_applications_music + Intent.CATEGORY_APP_MESSAGING -> R.string.keyboard_shortcut_group_applications_sms + Intent.CATEGORY_APP_CALCULATOR -> + R.string.keyboard_shortcut_group_applications_calculator + else -> { + Log.w(TAG, ("No label for app category $category")) + null + } } - } - return if (categoryLabelRes == null){ + return if (categoryLabelRes == null) { return null } else { context.getString(categoryLabelRes) @@ -196,41 +201,48 @@ constructor( private fun fetchIntentFromAppLaunchData(appLaunchData: AppLaunchData): Intent? { return when (appLaunchData) { - is CategoryData -> Intent.makeMainSelectorActivity( - /* selectorAction= */ ACTION_MAIN, - /* selectorCategory= */ appLaunchData.category - ) + is CategoryData -> + Intent.makeMainSelectorActivity( + /* selectorAction= */ ACTION_MAIN, + /* selectorCategory= */ appLaunchData.category, + ) is RoleData -> getRoleLaunchIntent(appLaunchData.role) - is ComponentData -> resolveComponentNameIntent( - packageName = appLaunchData.packageName, - className = appLaunchData.className - ) + is ComponentData -> + resolveComponentNameIntent( + packageName = appLaunchData.packageName, + className = appLaunchData.className, + ) else -> null } } private fun resolveComponentNameIntent(packageName: String, className: String): Intent? { - buildIntentFromComponentName(ComponentName(packageName, className))?.let { return it } - buildIntentFromComponentName(ComponentName( - userContext.packageManager.canonicalToCurrentPackageNames(arrayOf(packageName))[0], - className - ))?.let { return it } + buildIntentFromComponentName(ComponentName(packageName, className))?.let { + return it + } + buildIntentFromComponentName( + ComponentName( + userContext.packageManager + .canonicalToCurrentPackageNames(arrayOf(packageName))[0], + className, + ) + ) + ?.let { + return it + } return null } private fun buildIntentFromComponentName(componentName: ComponentName): Intent? { - try{ + try { val flags = MATCH_DIRECT_BOOT_UNAWARE or MATCH_DIRECT_BOOT_AWARE or MATCH_UNINSTALLED_PACKAGES // attempt to retrieve activity info to see if a NameNotFoundException is thrown. userContext.packageManager.getActivityInfo(componentName, flags) } catch (e: NameNotFoundException) { - Log.w( - TAG, - "Unable to find activity info for componentName: $componentName" - ) + Log.w(TAG, "Unable to find activity info for componentName: $componentName") return null } @@ -246,8 +258,9 @@ constructor( val roleManager = userContext.getSystemService(RoleManager::class.java)!! if (roleManager.isRoleAvailable(role)) { roleManager.getDefaultApplication(role)?.let { rolePackage -> - packageManager.getLaunchIntentForPackage(rolePackage)?.let { return it } - ?: Log.w(TAG, "No launch intent for role $role") + packageManager.getLaunchIntentForPackage(rolePackage)?.let { + return it + } ?: Log.w(TAG, "No launch intent for role $role") } ?: Log.w(TAG, "No default application for role $role, user= ${userContext.user}") } else { Log.w(TAG, "Role $role is not available.") @@ -264,4 +277,4 @@ constructor( private companion object { private const val TAG = "InputGestureDataUtils" } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt index 4a725ec8abad..cf5460fef0e2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyboard.shortcut.data.repository import android.content.Context import android.graphics.drawable.Icon +import android.hardware.input.InputGestureData.KeyTrigger import android.hardware.input.InputManager import android.hardware.input.KeyGlyphMap import android.util.Log @@ -137,8 +138,7 @@ constructor( label = shortcutInfo.label, icon = toShortcutIcon(keepIcon, shortcutInfo), commands = listOf(shortcutCommand), - isCustomizable = - shortcutHelperExclusions.isShortcutCustomizable(shortcutInfo.label), + isCustomizable = shortcutHelperExclusions.isShortcutCustomizable(shortcutInfo.label), ) } @@ -158,6 +158,22 @@ constructor( return ShortcutIcon(packageName = icon.resPackage, resourceId = icon.resId) } + fun toShortcutCommand( + keyGlyphMap: KeyGlyphMap?, + keyCharacterMap: KeyCharacterMap, + keyTrigger: KeyTrigger, + ): ShortcutCommand? { + return toShortcutCommand( + keyGlyphMap = keyGlyphMap, + keyCharacterMap = keyCharacterMap, + info = + InternalKeyboardShortcutInfo( + keycode = keyTrigger.keycode, + modifiers = keyTrigger.modifierState, + ), + ) + } + private fun toShortcutCommand( keyGlyphMap: KeyGlyphMap?, keyCharacterMap: KeyCharacterMap, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepository.kt new file mode 100644 index 000000000000..13613733c2bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepository.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.data.repository + +import android.hardware.input.InputManager +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +class ShortcutHelperInputDeviceRepository +@Inject +constructor( + stateRepository: ShortcutHelperStateRepository, + @Background private val backgroundScope: CoroutineScope, + @Background private val bgCoroutineContext: CoroutineContext, + private val inputManager: InputManager, +) { + val activeInputDevice = + stateRepository.state + .map { + if (it is Active) { + withContext(bgCoroutineContext) { inputManager.getInputDevice(it.deviceId) } + } else { + null + } + } + .stateIn(scope = backgroundScope, started = SharingStarted.Lazily, initialValue = null) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt index c7e6b43b9624..d8bad2590280 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt @@ -18,7 +18,10 @@ package com.android.systemui.keyboard.shortcut.shared.model import androidx.annotation.DrawableRes -data class ShortcutCommand(val keys: List<ShortcutKey>, val isCustom: Boolean = false) +data class ShortcutCommand( + val keys: List<ShortcutKey> = emptyList(), + val isCustom: Boolean = false, +) class ShortcutCommandBuilder { private val keys = mutableListOf<ShortcutKey>() diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt index 095de41237cf..f183247bb355 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt @@ -17,17 +17,27 @@ package com.android.systemui.keyboard.shortcut.shared.model sealed interface ShortcutCustomizationRequestInfo { - data class Add( - val label: String = "", - val categoryType: ShortcutCategoryType = ShortcutCategoryType.System, - val subCategoryLabel: String = "", - ) : ShortcutCustomizationRequestInfo - data class Delete( - val label: String = "", - val categoryType: ShortcutCategoryType = ShortcutCategoryType.System, - val subCategoryLabel: String = "", - ) : ShortcutCustomizationRequestInfo + sealed interface SingleShortcutCustomization: ShortcutCustomizationRequestInfo { + val label: String + val categoryType: ShortcutCategoryType + val subCategoryLabel: String + val shortcutCommand: ShortcutCommand + + data class Add( + override val label: String = "", + override val categoryType: ShortcutCategoryType = ShortcutCategoryType.System, + override val subCategoryLabel: String = "", + override val shortcutCommand: ShortcutCommand = ShortcutCommand(), + ) : SingleShortcutCustomization + + data class Delete( + override val label: String = "", + override val categoryType: ShortcutCategoryType = ShortcutCategoryType.System, + override val subCategoryLabel: String = "", + override val shortcutCommand: ShortcutCommand = ShortcutCommand(), + ) : SingleShortcutCustomization + } data object Reset : ShortcutCustomizationRequestInfo } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt index af6f0cb2ec83..aea583d67289 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -115,7 +115,6 @@ import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachIndexed import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.painter.rememberDrawablePainter -import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo @@ -127,6 +126,7 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState import com.android.systemui.res.R import kotlinx.coroutines.delay +import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel @Composable fun ShortcutHelper( @@ -505,10 +505,10 @@ private fun EndSidePanel( isCustomizing = isCustomizing and category.type.includeInCustomization, onCustomizationRequested = { requestInfo -> when (requestInfo) { - is ShortcutCustomizationRequestInfo.Add -> + is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add -> onCustomizationRequested(requestInfo.copy(categoryType = category.type)) - is ShortcutCustomizationRequestInfo.Delete -> + is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete -> onCustomizationRequested(requestInfo.copy(categoryType = category.type)) ShortcutCustomizationRequestInfo.Reset -> @@ -568,12 +568,12 @@ private fun SubCategoryContainerDualPane( isCustomizing = isCustomizing && shortcut.isCustomizable, onCustomizationRequested = { requestInfo -> when (requestInfo) { - is ShortcutCustomizationRequestInfo.Add -> + is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add -> onCustomizationRequested( requestInfo.copy(subCategoryLabel = subCategory.label) ) - is ShortcutCustomizationRequestInfo.Delete -> + is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete -> onCustomizationRequested( requestInfo.copy(subCategoryLabel = subCategory.label) ) @@ -644,12 +644,18 @@ private fun Shortcut( isCustomizing = isCustomizing, onAddShortcutRequested = { onCustomizationRequested( - ShortcutCustomizationRequestInfo.Add(label = shortcut.label) + ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add( + label = shortcut.label, + shortcutCommand = shortcut.commands.first(), + ) ) }, onDeleteShortcutRequested = { onCustomizationRequested( - ShortcutCustomizationRequestInfo.Delete(label = shortcut.label) + ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete( + label = shortcut.label, + shortcutCommand = shortcut.commands.first(), + ) ) }, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt index 92e25929fe4f..373eb250d61d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt @@ -66,8 +66,9 @@ constructor( } fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { + shortcutCustomizationInteractor.onCustomizationRequested(requestInfo) when (requestInfo) { - is ShortcutCustomizationRequestInfo.Add -> { + is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add -> { _shortcutCustomizationUiState.value = AddShortcutDialog( shortcutLabel = requestInfo.label, @@ -75,12 +76,10 @@ constructor( shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(), pressedKeys = emptyList(), ) - shortcutCustomizationInteractor.onCustomizationRequested(requestInfo) } - is ShortcutCustomizationRequestInfo.Delete -> { + is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete -> { _shortcutCustomizationUiState.value = DeleteShortcutDialog - shortcutCustomizationInteractor.onCustomizationRequested(requestInfo) } ShortcutCustomizationRequestInfo.Reset -> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardBottomAreaRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardBottomAreaRefactor.kt deleted file mode 100644 index 779b27b25375..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardBottomAreaRefactor.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.keyguard - -import com.android.systemui.Flags -import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils - -/** Helper for reading or using the keyguard bottom area refactor flag. */ -@Suppress("NOTHING_TO_INLINE") -object KeyguardBottomAreaRefactor { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - - /** Is the refactor enabled */ - @JvmStatic - inline val isEnabled - get() = Flags.keyguardBottomAreaRefactor() - - /** - * Called to ensure code is only run when the flag is enabled. This protects users from the - * unintended behaviors caused by accidentally running new logic, while also crashing on an eng - * build to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) - - /** - * Called to ensure code is only run when the flag is disabled. This will throw an exception if - * the flag is enabled to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java index a0b25b930d15..984541bcc60b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java @@ -27,7 +27,7 @@ import android.view.View; * Data class containing display information (message, icon, styling) for indication to show at * the bottom of the keyguard. * - * See {@link com.android.systemui.statusbar.phone.KeyguardBottomAreaView}. + * See {@link com.android.systemui.keyguard.ui.view.KeyguardRootView}. */ public class KeyguardIndication { @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 5ec6d37207b5..e8eb4976194a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -41,7 +41,6 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInte import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder -import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder import com.android.systemui.keyguard.ui.composable.LockscreenContent @@ -50,7 +49,6 @@ import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel @@ -59,7 +57,6 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessage import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.KeyguardIndicationController @@ -86,9 +83,6 @@ class KeyguardViewConfigurator constructor( private val keyguardRootView: KeyguardRootView, private val keyguardRootViewModel: KeyguardRootViewModel, - private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel, - private val notificationShadeWindowView: NotificationShadeWindowView, - private val indicationController: KeyguardIndicationController, private val screenOffAnimationController: ScreenOffAnimationController, private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, private val chipbarCoordinator: ChipbarCoordinator, @@ -163,23 +157,6 @@ constructor( } } - fun bindIndicationArea() { - indicationAreaHandle?.dispose() - - if (!KeyguardBottomAreaRefactor.isEnabled) { - keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let { - keyguardRootView.removeView(it) - } - } - - indicationAreaHandle = - KeyguardIndicationAreaBinder.bind( - notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area), - keyguardIndicationAreaViewModel, - indicationController, - ) - } - /** Initialize views so that corresponding controllers have a view set. */ private fun initializeViews() { val indicationArea = KeyguardIndicationArea(context, null) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 9f131607cb99..63ac5094c400 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -4058,7 +4058,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { - mRunner = mActivityTransitionAnimator.get().createRunner(mActivityLaunchController); + mRunner = mActivityTransitionAnimator.get() + .createEphemeralRunner(mActivityLaunchController); mRunner.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index a137d6cf91ec..5b28a3fa08de 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -131,8 +131,18 @@ constructor( Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()") activityTaskManagerService.keyguardGoingAway(0) isKeyguardGoingAway = true - } else { - // Hide the surface by setting the lockscreen showing. + } else if (isLockscreenShowing == true) { + // Re-show the lockscreen if the surface was visible and we want to make it invisible, + // and the lockscreen is currently showing (this is the usual case of the going away + // animation). Re-showing the lockscreen will cancel the going away animation. If we + // want to hide the surface, but the lockscreen is not currently showing, do nothing and + // wait for lockscreenVisibility to emit if it's appropriate to show the lockscreen (it + // might be disabled/suppressed). + Log.d( + TAG, + "setLockscreenShown(true) because we're setting the surface invisible " + + "and lockscreen is already showing.", + ) setLockscreenShown(true) } } @@ -153,6 +163,10 @@ constructor( nonApps: Array<RemoteAnimationTarget>, finishedCallback: IRemoteAnimationFinishedCallback, ) { + // Make sure this is true - we set it true when requesting keyguardGoingAway, but there are + // cases where WM starts this transition on its own. + isKeyguardGoingAway = true + // Ensure that we've started a dismiss keyguard transition. WindowManager can start the // going away animation on its own, if an activity launches and then requests dismissing the // keyguard. In this case, this is the first and only signal we'll receive to start diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt index 4bac8f7a1b47..a1fb1a7bb113 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt @@ -110,7 +110,7 @@ constructor( apps: Array<RemoteAnimationTarget>, wallpapers: Array<RemoteAnimationTarget>, nonApps: Array<RemoteAnimationTarget>, - finishedCallback: IRemoteAnimationFinishedCallback? + finishedCallback: IRemoteAnimationFinishedCallback?, ) { Log.d(TAG, "occludeAnimationRunner#onAnimationStart") // Wrap the callback so that it's guaranteed to be nulled out once called. @@ -126,7 +126,7 @@ constructor( taskInfo = apps.firstOrNull()?.taskInfo, ) activityTransitionAnimator - .createRunner(occludeAnimationController) + .createEphemeralRunner(occludeAnimationController) .onAnimationStart( transit, apps, @@ -161,7 +161,7 @@ constructor( apps: Array<RemoteAnimationTarget>, wallpapers: Array<RemoteAnimationTarget>, nonApps: Array<RemoteAnimationTarget>, - finishedCallback: IRemoteAnimationFinishedCallback? + finishedCallback: IRemoteAnimationFinishedCallback?, ) { Log.d(TAG, "unoccludeAnimationRunner#onAnimationStart") // Wrap the callback so that it's guaranteed to be nulled out once called. @@ -179,14 +179,14 @@ constructor( interactionJankMonitor.begin( createInteractionJankMonitorConf( InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION, - "UNOCCLUDE" + "UNOCCLUDE", ) ) if (apps.isEmpty()) { Log.d( TAG, "No apps provided to unocclude runner; " + - "skipping animation and unoccluding." + "skipping animation and unoccluding.", ) unoccludeAnimationFinishedCallback?.onAnimationFinished() return @@ -210,7 +210,7 @@ constructor( 0f, (1f - animatedValue) * surfaceHeight * - UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT + UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT, ) SurfaceParams.Builder(target.leash) @@ -313,12 +313,12 @@ constructor( private fun createInteractionJankMonitorConf( cuj: Int, - tag: String? + tag: String?, ): InteractionJankMonitor.Configuration.Builder { val builder = InteractionJankMonitor.Configuration.Builder.withView( cuj, - keyguardViewController.get().getViewRootImpl().view + keyguardViewController.get().getViewRootImpl().view, ) return if (tag != null) builder.setTag(tag) else builder } 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 096439b1008d..4370abf9ce5a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -62,6 +62,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor; import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule; import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule; +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransitionModule; import com.android.systemui.keyguard.ui.view.AlternateBouncerWindowViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModelModule; import com.android.systemui.log.SessionTracker; @@ -112,6 +113,7 @@ import java.util.concurrent.Executor; includes = { DeviceEntryIconTransitionModule.class, FalsingModule.class, + PrimaryBouncerTransitionModule.class, KeyguardDataQuickAffordanceModule.class, KeyguardQuickAffordancesCombinedViewModelModule.class, KeyguardRepositoryModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index d3c17ccd2d18..ac04dd5a7ec1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -75,12 +75,6 @@ interface KeyguardRepository { */ val animateBottomAreaDozingTransitions: StateFlow<Boolean> - /** - * Observable for the current amount of alpha that should be used for rendering the bottom area. - * UI. - */ - val bottomAreaAlpha: StateFlow<Float> - val keyguardAlpha: StateFlow<Float> val panelAlpha: MutableStateFlow<Float> @@ -283,9 +277,6 @@ interface KeyguardRepository { /** Sets whether the bottom area UI should animate the transition out of doze state. */ fun setAnimateDozingTransitions(animate: Boolean) - /** Sets the current amount of alpha that should be used for rendering the bottom area. */ - @Deprecated("Deprecated as part of b/278057014") fun setBottomAreaAlpha(alpha: Float) - /** Sets the current amount of alpha that should be used for rendering the keyguard. */ fun setKeyguardAlpha(alpha: Float) @@ -392,9 +383,6 @@ constructor( override val animateBottomAreaDozingTransitions = _animateBottomAreaDozingTransitions.asStateFlow() - private val _bottomAreaAlpha = MutableStateFlow(1f) - override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow() - private val _keyguardAlpha = MutableStateFlow(1f) override val keyguardAlpha = _keyguardAlpha.asStateFlow() @@ -675,10 +663,6 @@ constructor( _animateBottomAreaDozingTransitions.value = animate } - override fun setBottomAreaAlpha(alpha: Float) { - _bottomAreaAlpha.value = alpha - } - override fun setKeyguardAlpha(alpha: Float) { _keyguardAlpha.value = alpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt deleted file mode 100644 index 53f241684a62..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt +++ /dev/null @@ -1,61 +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.keyguard.domain.interactor - -import com.android.systemui.common.shared.model.Position -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.data.repository.KeyguardRepository -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow - -/** Encapsulates business-logic specifically related to the keyguard bottom area. */ -@SysUISingleton -class KeyguardBottomAreaInteractor -@Inject -constructor( - private val repository: KeyguardRepository, -) { - /** Whether to animate the next doze mode transition. */ - val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions - /** The amount of alpha for the UI components of the bottom area. */ - val alpha: Flow<Float> = repository.bottomAreaAlpha - /** The position of the keyguard clock. */ - private val _clockPosition = MutableStateFlow(Position(0, 0)) - /** See [ClockSection] */ - @Deprecated("with MigrateClocksToBlueprint.isEnabled") - val clockPosition: Flow<Position> = _clockPosition.asStateFlow() - - fun setClockPosition(x: Int, y: Int) { - _clockPosition.value = Position(x, y) - } - - fun setAlpha(alpha: Float) { - repository.setBottomAreaAlpha(alpha) - } - - fun setAnimateDozingTransitions(animate: Boolean) { - repository.setAnimateDozingTransitions(animate) - } - - /** - * Returns whether the keyguard bottom area should be constrained to the top of the lock icon - */ - fun shouldConstrainToTopOfLockIcon(): Boolean = repository.isUdfpsSupported() -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt index 8641dfa5a183..a133f06b3f41 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt @@ -90,6 +90,7 @@ constructor( private val selectedUserInteractor: SelectedUserInteractor, keyguardEnabledInteractor: KeyguardEnabledInteractor, keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor, + keyguardInteractor: KeyguardInteractor, ) { /** @@ -106,13 +107,18 @@ constructor( .onStart { emit(false) } /** - * Whether we can wake from AOD/DOZING directly to GONE, bypassing LOCKSCREEN/BOUNCER states. + * Whether we can wake from AOD/DOZING or DREAMING directly to GONE, bypassing + * LOCKSCREEN/BOUNCER states. * * This is possible in the following cases: * - Keyguard is disabled, either from an app request or from security being set to "None". * - Keyguard is suppressed, via adb locksettings. * - We're wake and unlocking (fingerprint auth occurred while asleep). * - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing. + * - We're DREAMING and dismissible. + * - We're already GONE. Technically you're already awake when GONE, but this makes it easier to + * reason about this state (for example, if canWakeDirectlyToGone, don't tell WM to pause the + * top activity - something you should never do while GONE as well). */ val canWakeDirectlyToGone = combine( @@ -120,14 +126,19 @@ constructor( shouldSuppressKeyguard, repository.biometricUnlockState, repository.canIgnoreAuthAndReturnToGone, + transitionInteractor.currentKeyguardState, ) { keyguardEnabled, shouldSuppressKeyguard, biometricUnlockState, - canIgnoreAuthAndReturnToGone -> + canIgnoreAuthAndReturnToGone, + currentState -> (!keyguardEnabled || shouldSuppressKeyguard) || BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) || - canIgnoreAuthAndReturnToGone + canIgnoreAuthAndReturnToGone || + (currentState == KeyguardState.DREAMING && + keyguardInteractor.isKeyguardDismissible.value) || + currentState == KeyguardState.GONE } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index 3b99bb4c40fa..184f30237e8d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -34,7 +34,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor -import com.android.systemui.util.kotlin.Utils.Companion.toTriple +import com.android.systemui.util.kotlin.Utils.Companion.toQuad import com.android.systemui.util.kotlin.sample import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import dagger.Lazy @@ -240,10 +240,11 @@ constructor( combine( transitionInteractor.currentKeyguardState, wakeToGoneInteractor.canWakeDirectlyToGone, - ::Pair, + surfaceBehindVisibility, + ::Triple, ) - .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple) - .map { (currentState, canWakeDirectlyToGone, startedWithPrev) -> + .sample(transitionInteractor.startedStepWithPrecedingStep, ::toQuad) + .map { (currentState, canWakeDirectlyToGone, surfaceBehindVis, startedWithPrev) -> val startedFromStep = startedWithPrev.previousValue val startedStep = startedWithPrev.newValue val returningToGoneAfterCancellation = @@ -296,6 +297,11 @@ constructor( // we should simply tell WM that the lockscreen is no longer visible, and // *not* play the going away animation or related animations. false + } else if (!surfaceBehindVis) { + // If the surface behind is not visible, then the lockscreen has to be visible + // since there's nothing to show. The surface behind will never be invisible if + // the lockscreen is disabled or suppressed. + true } else { currentState != KeyguardState.GONE } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 5bad0168fe05..261c130d0d82 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -24,7 +24,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.customization.R as customR -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition @@ -51,26 +50,11 @@ object KeyguardBlueprintViewBinder { (prevBlueprint, blueprint) -> val config = Config.DEFAULT val transition = - if ( - !KeyguardBottomAreaRefactor.isEnabled && - prevBlueprint != null && - prevBlueprint != blueprint - ) { - BaseBlueprintTransition(clockViewModel) - .addTransition( - IntraBlueprintTransition( - config, - clockViewModel, - smartspaceViewModel, - ) - ) - } else { - IntraBlueprintTransition( - config, - clockViewModel, - smartspaceViewModel, - ) - } + IntraBlueprintTransition( + config, + clockViewModel, + smartspaceViewModel, + ) viewModel.runTransition(constraintLayout, transition, config) { // Replace sections from the previous blueprint with the new ones diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt deleted file mode 100644 index c59fe5357ccb..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ /dev/null @@ -1,586 +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.keyguard.ui.binder - -import android.annotation.SuppressLint -import android.content.res.ColorStateList -import android.graphics.Rect -import android.graphics.drawable.Animatable2 -import android.util.Size -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.MarginLayoutParams -import android.view.WindowInsets -import android.widget.ImageView -import androidx.core.animation.CycleInterpolator -import androidx.core.animation.ObjectAnimator -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import androidx.core.view.marginLeft -import androidx.core.view.marginRight -import androidx.core.view.marginTop -import androidx.core.view.updateLayoutParams -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.systemui.animation.ActivityTransitionAnimator -import com.android.systemui.animation.Expandable -import com.android.systemui.animation.view.LaunchableLinearLayout -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.ui.binder.IconViewBinder -import com.android.systemui.common.ui.binder.TextViewBinder -import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel -import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils -import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.res.R -import com.android.systemui.statusbar.VibratorHelper -import com.android.systemui.util.doOnEnd -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map - -/** - * Binds a keyguard bottom area view to its view-model. - * - * To use this properly, users should maintain a one-to-one relationship between the [View] and the - * view-binding, binding each view only once. It is okay and expected for the same instance of the - * view-model to be reused for multiple view/view-binder bindings. - */ -@OptIn(ExperimentalCoroutinesApi::class) -@Deprecated("Deprecated as part of b/278057014") -object KeyguardBottomAreaViewBinder { - - private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L - private const val SCALE_SELECTED_BUTTON = 1.23f - private const val DIM_ALPHA = 0.3f - private const val TAG = "KeyguardBottomAreaViewBinder" - - /** - * Defines interface for an object that acts as the binding between the view and its view-model. - * - * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after - * it is bound. - */ - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - @Deprecated("Deprecated as part of b/278057014") - interface Binding { - /** Notifies that device configuration has changed. */ - fun onConfigurationChanged() - - /** - * Returns whether the keyguard bottom area should be constrained to the top of the lock - * icon - */ - fun shouldConstrainToTopOfLockIcon(): Boolean - - /** Destroys this binding, releases resources, and cancels any coroutines. */ - fun destroy() - } - - /** Binds the view to the view-model, continuing to update the former based on the latter. */ - @Deprecated("Deprecated as part of b/278057014") - @SuppressLint("ClickableViewAccessibility") - @JvmStatic - fun bind( - view: ViewGroup, - viewModel: KeyguardBottomAreaViewModel, - falsingManager: FalsingManager?, - vibratorHelper: VibratorHelper?, - activityStarter: ActivityStarter?, - messageDisplayer: (Int) -> Unit, - ): Binding { - val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container) - val startButton: ImageView = view.requireViewById(R.id.start_button) - val endButton: ImageView = view.requireViewById(R.id.end_button) - val overlayContainer: View = view.requireViewById(R.id.overlay_container) - val settingsMenu: LaunchableLinearLayout = - view.requireViewById(R.id.keyguard_settings_button) - - startButton.setOnApplyWindowInsetsListener { inView, windowInsets -> - val bottomInset = windowInsets.displayCutout?.safeInsetBottom ?: 0 - val marginBottom = - inView.resources.getDimension(R.dimen.keyguard_affordance_vertical_offset).toInt() - inView.layoutParams = - (inView.layoutParams as MarginLayoutParams).apply { - setMargins( - inView.marginLeft, - inView.marginTop, - inView.marginRight, - marginBottom + bottomInset - ) - } - WindowInsets.CONSUMED - } - - endButton.setOnApplyWindowInsetsListener { inView, windowInsets -> - val bottomInset = windowInsets.displayCutout?.safeInsetBottom ?: 0 - val marginBottom = - inView.resources.getDimension(R.dimen.keyguard_affordance_vertical_offset).toInt() - inView.layoutParams = - (inView.layoutParams as MarginLayoutParams).apply { - setMargins( - inView.marginLeft, - inView.marginTop, - inView.marginRight, - marginBottom + bottomInset - ) - } - WindowInsets.CONSUMED - } - - view.clipChildren = false - view.clipToPadding = false - view.setOnTouchListener { _, event -> - if (settingsMenu.isVisible) { - val hitRect = Rect() - settingsMenu.getHitRect(hitRect) - if (!hitRect.contains(event.x.toInt(), event.y.toInt())) { - viewModel.onTouchedOutsideLockScreenSettingsMenu() - } - } - - false - } - - val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) - - val disposableHandle = - view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - launch("$TAG#viewModel.startButton") { - viewModel.startButton.collect { buttonModel -> - updateButton( - view = startButton, - viewModel = buttonModel, - falsingManager = falsingManager, - messageDisplayer = messageDisplayer, - vibratorHelper = vibratorHelper, - ) - } - } - - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - launch("$TAG#viewModel.endButton") { - viewModel.endButton.collect { buttonModel -> - updateButton( - view = endButton, - viewModel = buttonModel, - falsingManager = falsingManager, - messageDisplayer = messageDisplayer, - vibratorHelper = vibratorHelper, - ) - } - } - - launch("$TAG#viewModel.isOverlayContainerVisible") { - viewModel.isOverlayContainerVisible.collect { isVisible -> - overlayContainer.visibility = - if (isVisible) { - View.VISIBLE - } else { - View.INVISIBLE - } - } - } - - launch("$TAG#viewModel.alpha") { - viewModel.alpha.collect { alpha -> - ambientIndicationArea?.apply { - this.importantForAccessibility = - if (alpha == 0f) { - View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - } else { - View.IMPORTANT_FOR_ACCESSIBILITY_AUTO - } - this.alpha = alpha - } - } - } - - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - launch("$TAG#updateButtonAlpha") { - updateButtonAlpha( - view = startButton, - viewModel = viewModel.startButton, - alphaFlow = viewModel.alpha, - ) - } - - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - launch("$TAG#updateButtonAlpha") { - updateButtonAlpha( - view = endButton, - viewModel = viewModel.endButton, - alphaFlow = viewModel.alpha, - ) - } - - launch("$TAG#viewModel.indicationAreaTranslationX") { - viewModel.indicationAreaTranslationX.collect { translationX -> - ambientIndicationArea?.translationX = translationX - } - } - - launch("$TAG#viewModel.indicationAreaTranslationY") { - configurationBasedDimensions - .map { it.defaultBurnInPreventionYOffsetPx } - .flatMapLatest { defaultBurnInOffsetY -> - viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) - } - .collect { translationY -> - ambientIndicationArea?.translationY = translationY - } - } - - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - launch("$TAG#startButton.updateLayoutParams<ViewGroup") { - configurationBasedDimensions.collect { dimensions -> - startButton.updateLayoutParams<ViewGroup.LayoutParams> { - width = dimensions.buttonSizePx.width - height = dimensions.buttonSizePx.height - } - endButton.updateLayoutParams<ViewGroup.LayoutParams> { - width = dimensions.buttonSizePx.width - height = dimensions.buttonSizePx.height - } - } - } - - launch("$TAG#viewModel.settingsMenuViewModel") { - viewModel.settingsMenuViewModel.isVisible.distinctUntilChanged().collect { - isVisible -> - settingsMenu.animateVisibility(visible = isVisible) - if (isVisible) { - vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated) - settingsMenu.setOnTouchListener( - KeyguardSettingsButtonOnTouchListener( - viewModel = viewModel.settingsMenuViewModel, - ) - ) - IconViewBinder.bind( - icon = viewModel.settingsMenuViewModel.icon, - view = settingsMenu.requireViewById(R.id.icon), - ) - TextViewBinder.bind( - view = settingsMenu.requireViewById(R.id.text), - viewModel = viewModel.settingsMenuViewModel.text, - ) - } - } - } - - // activityStarter will only be null when rendering the preview that - // shows up in the Wallpaper Picker app. If we do that, then the - // settings menu should never be visible. - if (activityStarter != null) { - launch("$TAG#viewModel.settingsMenuViewModel") { - viewModel.settingsMenuViewModel.shouldOpenSettings - .filter { it } - .collect { - navigateToLockScreenSettings( - activityStarter = activityStarter, - view = settingsMenu, - ) - viewModel.settingsMenuViewModel.onSettingsShown() - } - } - } - } - } - - return object : Binding { - override fun onConfigurationChanged() { - configurationBasedDimensions.value = loadFromResources(view) - } - - override fun shouldConstrainToTopOfLockIcon(): Boolean = - viewModel.shouldConstrainToTopOfLockIcon() - - override fun destroy() { - disposableHandle.dispose() - } - } - } - - @Deprecated("Deprecated as part of b/278057014") - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - @SuppressLint("ClickableViewAccessibility") - private fun updateButton( - view: ImageView, - viewModel: KeyguardQuickAffordanceViewModel, - falsingManager: FalsingManager?, - messageDisplayer: (Int) -> Unit, - vibratorHelper: VibratorHelper?, - ) { - if (!viewModel.isVisible) { - view.isInvisible = true - return - } - - if (!view.isVisible) { - view.isVisible = true - if (viewModel.animateReveal) { - view.alpha = 0f - view.translationY = view.height / 2f - view - .animate() - .alpha(1f) - .translationY(0f) - .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) - .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS) - .start() - } - } - - IconViewBinder.bind(viewModel.icon, view) - - (view.drawable as? Animatable2)?.let { animatable -> - (viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId -> - // Always start the animation (we do call stop() below, if we need to skip it). - animatable.start() - - if (view.tag != iconResourceId) { - // Here when we haven't run the animation on a previous update. - // - // Save the resource ID for next time, so we know not to re-animate the same - // animation again. - view.tag = iconResourceId - } else { - // Here when we've already done this animation on a previous update and want to - // skip directly to the final frame of the animation to avoid running it. - // - // By calling stop after start, we go to the final frame of the animation. - animatable.stop() - } - } - } - - view.isActivated = viewModel.isActivated - view.drawable.setTint( - view.context.getColor( - if (viewModel.isActivated) { - com.android.internal.R.color.materialColorOnPrimaryFixed - } else { - com.android.internal.R.color.materialColorOnSurface - } - ) - ) - - view.backgroundTintList = - if (!viewModel.isSelected) { - ColorStateList.valueOf( - view.context.getColor( - if (viewModel.isActivated) { - com.android.internal.R.color.materialColorPrimaryFixed - } else { - com.android.internal.R.color.materialColorSurfaceContainerHigh - } - ) - ) - } else { - null - } - view - .animate() - .scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f) - .scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f) - .start() - - view.isClickable = viewModel.isClickable - if (viewModel.isClickable) { - if (viewModel.useLongPress) { - val onTouchListener = - KeyguardQuickAffordanceOnTouchListener( - view, - viewModel, - messageDisplayer, - vibratorHelper, - falsingManager, - ) - view.setOnTouchListener(onTouchListener) - view.setOnClickListener { - messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short) - val amplitude = - view.context.resources - .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude) - .toFloat() - val shakeAnimator = - ObjectAnimator.ofFloat( - view, - "translationX", - -amplitude / 2, - amplitude / 2, - ) - shakeAnimator.duration = - KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds - shakeAnimator.interpolator = - CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles) - shakeAnimator.doOnEnd { view.translationX = 0f } - shakeAnimator.start() - - vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) - } - view.onLongClickListener = - OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener) - } else { - view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager))) - } - } else { - view.onLongClickListener = null - view.setOnClickListener(null) - view.setOnTouchListener(null) - } - - view.isSelected = viewModel.isSelected - } - - @Deprecated("Deprecated as part of b/278057014") - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - private suspend fun updateButtonAlpha( - view: View, - viewModel: Flow<KeyguardQuickAffordanceViewModel>, - alphaFlow: Flow<Float>, - ) { - combine(viewModel.map { it.isDimmed }, alphaFlow) { isDimmed, alpha -> - if (isDimmed) DIM_ALPHA else alpha - } - .collect { view.alpha = it } - } - - @Deprecated("Deprecated as part of b/278057014") - private fun View.animateVisibility(visible: Boolean) { - animate() - .withStartAction { - if (visible) { - alpha = 0f - isVisible = true - } - } - .alpha(if (visible) 1f else 0f) - .withEndAction { - if (!visible) { - isVisible = false - } - } - .start() - } - - @Deprecated("Deprecated as part of b/278057014") - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - private class OnLongClickListener( - private val falsingManager: FalsingManager?, - private val viewModel: KeyguardQuickAffordanceViewModel, - private val vibratorHelper: VibratorHelper?, - private val onTouchListener: KeyguardQuickAffordanceOnTouchListener - ) : View.OnLongClickListener { - override fun onLongClick(view: View): Boolean { - if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) { - return true - } - - if (viewModel.configKey != null) { - viewModel.onClicked( - KeyguardQuickAffordanceViewModel.OnClickedParameters( - configKey = viewModel.configKey, - expandable = Expandable.fromView(view), - slotId = viewModel.slotId, - ) - ) - vibratorHelper?.vibrate( - if (viewModel.isActivated) { - KeyguardBottomAreaVibrations.Activated - } else { - KeyguardBottomAreaVibrations.Deactivated - } - ) - } - - onTouchListener.cancel() - return true - } - - override fun onLongClickUseDefaultHapticFeedback(view: View) = false - } - - @Deprecated("Deprecated as part of b/278057014") - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - private class OnClickListener( - private val viewModel: KeyguardQuickAffordanceViewModel, - private val falsingManager: FalsingManager, - ) : View.OnClickListener { - override fun onClick(view: View) { - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return - } - - if (viewModel.configKey != null) { - viewModel.onClicked( - KeyguardQuickAffordanceViewModel.OnClickedParameters( - configKey = viewModel.configKey, - expandable = Expandable.fromView(view), - slotId = viewModel.slotId, - ) - ) - } - } - } - - @Deprecated("Deprecated as part of b/278057014") - private fun loadFromResources(view: View): ConfigurationBasedDimensions { - return ConfigurationBasedDimensions( - defaultBurnInPreventionYOffsetPx = - view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset), - buttonSizePx = - Size( - view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), - view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), - ), - ) - } - - @Deprecated("Deprecated as part of b/278057014") - /** Opens the wallpaper picker screen after the device is unlocked by the user. */ - private fun navigateToLockScreenSettings( - activityStarter: ActivityStarter, - view: View, - ) { - activityStarter.postStartActivityDismissingKeyguard( - WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD), - /* delay= */ 0, - /* animationController= */ ActivityTransitionAnimator.Controller.fromView(view), - /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls) - ) - } - - @Deprecated("Deprecated as part of b/278057014") - // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] - private data class ConfigurationBasedDimensions( - val defaultBurnInPreventionYOffsetPx: Int, - val buttonSizePx: Size, - ) -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index 8b947a3bcb1e..92b49ed6156c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -23,8 +23,6 @@ import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R @@ -75,16 +73,6 @@ object KeyguardIndicationAreaBinder { disposables += view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { - launch("$TAG#viewModel.alpha") { - // Do not independently apply alpha, as [KeyguardRootViewModel] should work - // for this and all its children - if ( - !(MigrateClocksToBlueprint.isEnabled || - KeyguardBottomAreaRefactor.isEnabled) - ) { - viewModel.alpha.collect { alpha -> view.alpha = alpha } - } - } launch("$TAG#viewModel.indicationAreaTranslationX") { viewModel.indicationAreaTranslationX.collect { translationX -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt index c0b3d8345249..1964cb2f811f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt @@ -136,8 +136,16 @@ object KeyguardPreviewClockViewBinder { viewModel: KeyguardPreviewClockViewModel, ) { val cs = ConstraintSet().apply { clone(rootView) } - previewClock.largeClock.layout.applyPreviewConstraints(clockPreviewConfig, cs) - previewClock.smallClock.layout.applyPreviewConstraints(clockPreviewConfig, cs) + + val configWithUpdatedLockId = + if (rootView.getViewById(lockId) != null) { + clockPreviewConfig.copy(lockId = lockId) + } else { + clockPreviewConfig + } + + previewClock.largeClock.layout.applyPreviewConstraints(configWithUpdatedLockId, cs) + previewClock.smallClock.layout.applyPreviewConstraints(configWithUpdatedLockId, cs) // When selectedClockSize is the initial value, make both clocks invisible to avoid // flickering diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index 5c8a234ec6c4..8725cdd273df 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -71,9 +71,6 @@ constructor( /** * Defines interface for an object that acts as the binding between the view and its view-model. - * - * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after - * it is bound. */ interface Binding { /** Notifies that device configuration has changed. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index cd4d9dcf366c..a2ce4ec5ce9b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -52,7 +52,6 @@ import com.android.systemui.common.ui.view.onLayoutChanged import com.android.systemui.common.ui.view.onTouchListener import com.android.systemui.customization.R as customR import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -119,35 +118,33 @@ object KeyguardRootViewBinder { val disposables = DisposableHandles() val childViews = mutableMapOf<Int, View>() - if (KeyguardBottomAreaRefactor.isEnabled) { - disposables += - view.onTouchListener { _, event -> - var consumed = false - if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) { - // signifies a primary button click down has reached keyguardrootview - // we need to return true here otherwise an ACTION_UP will never arrive - if (Flags.nonTouchscreenDevicesBypassFalsing()) { - if ( - event.action == MotionEvent.ACTION_DOWN && - event.buttonState == MotionEvent.BUTTON_PRIMARY && - !event.isTouchscreenSource() - ) { - consumed = true - } else if ( - event.action == MotionEvent.ACTION_UP && - !event.isTouchscreenSource() - ) { - statusBarKeyguardViewManager?.showBouncer(true) - consumed = true - } + disposables += + view.onTouchListener { _, event -> + var consumed = false + if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) { + // signifies a primary button click down has reached keyguardrootview + // we need to return true here otherwise an ACTION_UP will never arrive + if (Flags.nonTouchscreenDevicesBypassFalsing()) { + if ( + event.action == MotionEvent.ACTION_DOWN && + event.buttonState == MotionEvent.BUTTON_PRIMARY && + !event.isTouchscreenSource() + ) { + consumed = true + } else if ( + event.action == MotionEvent.ACTION_UP && + !event.isTouchscreenSource() + ) { + statusBarKeyguardViewManager?.showBouncer(true) + consumed = true } - viewModel.setRootViewLastTapPosition( - Point(event.x.toInt(), event.y.toInt()) - ) } - consumed + viewModel.setRootViewLastTapPosition( + Point(event.x.toInt(), event.y.toInt()) + ) } - } + consumed + } val burnInParams = MutableStateFlow(BurnInParameters()) val viewState = ViewStateAccessor(alpha = { view.alpha }) @@ -175,10 +172,8 @@ object KeyguardRootViewBinder { launch("$TAG#alpha") { viewModel.alpha(viewState).collect { alpha -> view.alpha = alpha - if (KeyguardBottomAreaRefactor.isEnabled) { - childViews[statusViewId]?.alpha = alpha - childViews[burnInLayerId]?.alpha = alpha - } + childViews[statusViewId]?.alpha = alpha + childViews[burnInLayerId]?.alpha = alpha } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 85725d24758d..090b65922d2d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -48,39 +48,28 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.core.view.isInvisible -import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.policy.SystemBarUtils import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel import com.android.systemui.coroutines.newTracingContext -import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder -import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection -import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel -import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.plugins.clocks.ClockController @@ -97,9 +86,6 @@ import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordance import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController -import com.android.systemui.statusbar.phone.KeyguardBottomAreaView -import com.android.systemui.statusbar.phone.ScreenOffAnimationController -import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.util.kotlin.DisposableHandles import com.android.systemui.util.settings.SecureSettings import dagger.assisted.Assisted @@ -115,6 +101,8 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.json.JSONException import org.json.JSONObject +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.systemui.customization.R as customR /** Renders the preview of the lock screen. */ class KeyguardPreviewRenderer @@ -128,29 +116,20 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, private val clockViewModel: KeyguardPreviewClockViewModel, private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel, - private val bottomAreaViewModel: KeyguardBottomAreaViewModel, private val quickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, displayManager: DisplayManager, private val windowManager: WindowManager, - private val configuration: ConfigurationState, private val clockController: ClockEventController, private val clockRegistry: ClockRegistry, private val broadcastDispatcher: BroadcastDispatcher, private val lockscreenSmartspaceController: LockscreenSmartspaceController, private val udfpsOverlayInteractor: UdfpsOverlayInteractor, private val indicationController: KeyguardIndicationController, - private val keyguardRootViewModel: KeyguardRootViewModel, - private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel, @Assisted bundle: Bundle, - private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, - private val chipbarCoordinator: ChipbarCoordinator, - private val screenOffAnimationController: ScreenOffAnimationController, private val shadeInteractor: ShadeInteractor, private val secureSettings: SecureSettings, private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel, private val defaultShortcutsSection: DefaultShortcutsSection, - private val keyguardClockInteractor: KeyguardClockInteractor, - private val keyguardClockViewModel: KeyguardClockViewModel, private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder, ) { val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) @@ -201,20 +180,12 @@ constructor( disposables += DisposableHandle { coroutineScope.cancel() } clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData()) - if (KeyguardBottomAreaRefactor.isEnabled) { - quickAffordancesCombinedViewModel.enablePreviewMode( - initiallySelectedSlotId = - bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID) - ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, - ) - } else { - bottomAreaViewModel.enablePreviewMode( - initiallySelectedSlotId = - bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID), - shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, - ) - } + quickAffordancesCombinedViewModel.enablePreviewMode( + initiallySelectedSlotId = + bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID) + ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, + ) if (MigrateClocksToBlueprint.isEnabled) { clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance } @@ -241,10 +212,6 @@ constructor( setupKeyguardRootView(previewContext, rootView) - if (!KeyguardBottomAreaRefactor.isEnabled) { - setUpBottomArea(rootView) - } - var displayInfo: DisplayInfo? = null display?.let { displayInfo = DisplayInfo() @@ -292,11 +259,7 @@ constructor( } fun onSlotSelected(slotId: String) { - if (KeyguardBottomAreaRefactor.isEnabled) { - quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId) - } else { - bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId) - } + quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId) } fun onPreviewQuickAffordanceSelected(slotId: String, quickAffordanceId: String) { @@ -322,9 +285,7 @@ constructor( isDestroyed = true lockscreenSmartspaceController.disconnect() disposables.dispose() - if (KeyguardBottomAreaRefactor.isEnabled) { - shortcutsBindings.forEach { it.destroy() } - } + shortcutsBindings.forEach { it.destroy() } } /** @@ -387,47 +348,8 @@ constructor( smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f } - @Deprecated("Deprecated as part of b/278057014") - private fun setUpBottomArea(parentView: ViewGroup) { - val bottomAreaView = - LayoutInflater.from(context).inflate(R.layout.keyguard_bottom_area, parentView, false) - as KeyguardBottomAreaView - bottomAreaView.init(viewModel = bottomAreaViewModel) - parentView.addView( - bottomAreaView, - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT, - ), - ) - } - - @OptIn(ExperimentalCoroutinesApi::class) private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) { val keyguardRootView = KeyguardRootView(previewContext, null) - if (!KeyguardBottomAreaRefactor.isEnabled) { - disposables += - KeyguardRootViewBinder.bind( - keyguardRootView, - keyguardRootViewModel, - keyguardBlueprintViewModel, - configuration, - occludingAppDeviceEntryMessageViewModel, - chipbarCoordinator, - screenOffAnimationController, - shadeInteractor, - keyguardClockInteractor, - keyguardClockViewModel, - null, // jank monitor not required for preview mode - null, // device entry haptics not required preview mode - null, // device entry haptics not required for preview mode - null, // falsing manager not required for preview mode - null, // keyguard view mediator is not required for preview mode - null, // primary bouncer interactor is not required for preview mode - mainDispatcher, - null, - ) - } rootView.addView( keyguardRootView, FrameLayout.LayoutParams( @@ -441,9 +363,7 @@ constructor( if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView, ) - if (KeyguardBottomAreaRefactor.isEnabled) { - setupShortcuts(keyguardRootView) - } + setupShortcuts(keyguardRootView) if (!shouldHideClock) { setUpClock(previewContext, rootView) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt new file mode 100644 index 000000000000..cafc90930432 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.transitions + +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToDozingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGlanceableHubTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Each PrimaryBouncerTransition is responsible for updating various UI states based on the nature + * of the transition. + * + * MUST list implementing classes in dagger module [PrimaryBouncerTransitionModule]. + */ +interface PrimaryBouncerTransition { + /** Radius of blur applied to the window's root view. */ + val windowBlurRadius: Flow<Float> + + companion object { + const val MAX_BACKGROUND_BLUR_RADIUS = 150f + const val MIN_BACKGROUND_BLUR_RADIUS = 0f + } +} + +/** + * Module that installs all the transitions from different keyguard states to and away from the + * primary bouncer. + */ +@ExperimentalCoroutinesApi +@Module +interface PrimaryBouncerTransitionModule { + @Binds + @IntoSet + fun fromAod(impl: AodToPrimaryBouncerTransitionViewModel): PrimaryBouncerTransition + + @Binds + @IntoSet + fun fromAlternateBouncer( + impl: AlternateBouncerToPrimaryBouncerTransitionViewModel + ): PrimaryBouncerTransition + + @Binds + @IntoSet + fun fromDozing(impl: DozingToPrimaryBouncerTransitionViewModel): PrimaryBouncerTransition + + @Binds + @IntoSet + fun fromLockscreen( + impl: LockscreenToPrimaryBouncerTransitionViewModel + ): PrimaryBouncerTransition + + @Binds + @IntoSet + fun toAod(impl: PrimaryBouncerToAodTransitionViewModel): PrimaryBouncerTransition + + @Binds + @IntoSet + fun toLockscreen(impl: PrimaryBouncerToLockscreenTransitionViewModel): PrimaryBouncerTransition + + @Binds + @IntoSet + fun toDozing(impl: PrimaryBouncerToDozingTransitionViewModel): PrimaryBouncerTransition + + @Binds + @IntoSet + fun toGlanceableHub( + impl: PrimaryBouncerToGlanceableHubTransitionViewModel + ): PrimaryBouncerTransition + + @Binds + @IntoSet + fun toGone(impl: PrimaryBouncerToGoneTransitionViewModel): PrimaryBouncerTransition +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index 160380bb09bc..57fe15d4f52c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -24,7 +24,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel @@ -50,9 +49,6 @@ constructor( } override fun addViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } if (emptyView.parent != null) { // As emptyView is lazy, it might be already attached. (emptyView.parent as? ViewGroup)?.removeView(emptyView) @@ -68,17 +64,10 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } clockViewModel.burnInLayer = burnInLayer } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - constraintSet.apply { // The empty view should not occupy any space constrainHeight(R.id.burn_in_layer_empty_view, 1) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index 70bf8bca55b9..738fb73a4918 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -32,7 +32,6 @@ import androidx.constraintlayout.widget.ConstraintSet.VISIBLE import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection @@ -82,9 +81,6 @@ constructor( override fun addViews(constraintLayout: ConstraintLayout) {} override fun bindData(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } disposableHandle?.dispose() disposableHandle = KeyguardClockViewBinder.bind( @@ -99,20 +95,12 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - keyguardClockViewModel.currentClock.value?.let { clock -> constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet)) } } override fun removeViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - disposableHandle?.dispose() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index aa7eb2933992..e8fce9ca4aa8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -22,7 +22,6 @@ import android.graphics.Point import android.graphics.Rect import android.util.DisplayMetrics import android.util.Log -import android.view.View import android.view.WindowManager import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintLayout @@ -32,8 +31,6 @@ import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder import com.android.systemui.keyguard.ui.view.DeviceEntryIconView @@ -76,10 +73,6 @@ constructor( private var disposableHandle: DisposableHandle? = null override fun addViews(constraintLayout: ConstraintLayout) { - if (!KeyguardBottomAreaRefactor.isEnabled && !MigrateClocksToBlueprint.isEnabled) { - return - } - val view = DeviceEntryIconView( context, @@ -194,38 +187,6 @@ constructor( sensorRect.left, ) } - - // This is only intended to be here until the KeyguardBottomAreaRefactor flag is enabled - // Without this logic, the lock icon location changes but the KeyguardBottomAreaView is not - // updated and visible ui layout jank occurs. This is due to AmbientIndicationContainer - // being in NPVC and laying out prior to the KeyguardRootView. - // Remove when KeyguardBottomAreaRefactor is enabled. - if (!KeyguardBottomAreaRefactor.isEnabled) { - with(notificationPanelView) { - val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value - val bottomAreaViewRight = findViewById<View>(R.id.keyguard_bottom_area)?.right ?: 0 - findViewById<View>(R.id.ambient_indication_container)?.let { - val (ambientLeft, ambientTop) = it.locationOnScreen - if (isUdfpsSupported) { - // make top of ambient indication view the bottom of the lock icon - it.layout( - ambientLeft, - sensorRect.bottom, - bottomAreaViewRight - ambientLeft, - ambientTop + it.measuredHeight, - ) - } else { - // make bottom of ambient indication view the top of the lock icon - it.layout( - ambientLeft, - sensorRect.top - it.measuredHeight, - bottomAreaViewRight - ambientLeft, - sensorRect.top, - ) - } - } - } - } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt index 2d9dac41ba6e..5bf56e8de9a7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt @@ -21,7 +21,6 @@ import android.content.Context import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea @@ -43,21 +42,17 @@ constructor( private var indicationAreaHandle: DisposableHandle? = null override fun addViews(constraintLayout: ConstraintLayout) { - if (KeyguardBottomAreaRefactor.isEnabled) { - val view = KeyguardIndicationArea(context, null) - constraintLayout.addView(view) - } + val view = KeyguardIndicationArea(context, null) + constraintLayout.addView(view) } override fun bindData(constraintLayout: ConstraintLayout) { - if (KeyguardBottomAreaRefactor.isEnabled) { - indicationAreaHandle = - KeyguardIndicationAreaBinder.bind( - constraintLayout.requireViewById(R.id.keyguard_indication_area), - keyguardIndicationAreaViewModel, - indicationController, - ) - } + indicationAreaHandle = + KeyguardIndicationAreaBinder.bind( + constraintLayout.requireViewById(R.id.keyguard_indication_area), + keyguardIndicationAreaViewModel, + indicationController, + ) } override fun applyConstraints(constraintSet: ConstraintSet) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index 3a791fd45528..4bfe5f0458c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -24,7 +24,6 @@ import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.res.R import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.shade.NotificationPanelView @@ -54,32 +53,25 @@ constructor( sharedNotificationContainerBinder, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } constraintSet.apply { val bottomMargin = context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin) - if (MigrateClocksToBlueprint.isEnabled) { - val useLargeScreenHeader = - context.resources.getBoolean(R.bool.config_use_large_screen_shade_header) - val marginTopLargeScreen = - largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight() - connect( - R.id.nssl_placeholder, - TOP, - R.id.smart_space_barrier_bottom, - BOTTOM, - bottomMargin + - if (useLargeScreenHeader) { - marginTopLargeScreen - } else { - 0 - } - ) - } else { - connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin) - } + val useLargeScreenHeader = + context.resources.getBoolean(R.bool.config_use_large_screen_shade_header) + val marginTopLargeScreen = + largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight() + connect( + R.id.nssl_placeholder, + TOP, + R.id.smart_space_barrier_bottom, + BOTTOM, + bottomMargin + + if (useLargeScreenHeader) { + marginTopLargeScreen + } else { + 0 + }, + ) connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt index 5cd5172d1851..f973ced59dcf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt @@ -31,7 +31,6 @@ import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import androidx.core.view.isVisible import com.android.systemui.animation.view.LaunchableLinearLayout import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel @@ -56,9 +55,6 @@ constructor( private var settingsPopupMenuHandle: DisposableHandle? = null override fun addViews(constraintLayout: ConstraintLayout) { - if (!KeyguardBottomAreaRefactor.isEnabled) { - return - } val view = LayoutInflater.from(constraintLayout.context) .inflate(R.layout.keyguard_settings_popup_menu, constraintLayout, false) @@ -71,17 +67,15 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (KeyguardBottomAreaRefactor.isEnabled) { - settingsPopupMenuHandle = - KeyguardSettingsViewBinder.bind( - constraintLayout.requireViewById<View>(R.id.keyguard_settings_button), - keyguardSettingsMenuViewModel, - keyguardTouchHandlingViewModel, - keyguardRootViewModel, - vibratorHelper, - activityStarter, - ) - } + settingsPopupMenuHandle = + KeyguardSettingsViewBinder.bind( + constraintLayout.requireViewById<View>(R.id.keyguard_settings_button), + keyguardSettingsMenuViewModel, + keyguardTouchHandlingViewModel, + keyguardRootViewModel, + vibratorHelper, + activityStarter, + ) } override fun applyConstraints(constraintSet: ConstraintSet) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt index d3895def28e0..82f142b03323 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt @@ -28,7 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet.RIGHT import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder @@ -49,7 +48,6 @@ constructor( @Named(LOCKSCREEN_INSTANCE) private val keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, - private val keyguardRootViewModel: KeyguardRootViewModel, private val indicationController: KeyguardIndicationController, private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>, private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder, @@ -60,46 +58,42 @@ constructor( private var safeInsetBottom = 0 override fun addViews(constraintLayout: ConstraintLayout) { - if (KeyguardBottomAreaRefactor.isEnabled) { - addLeftShortcut(constraintLayout) - addRightShortcut(constraintLayout) + addLeftShortcut(constraintLayout) + addRightShortcut(constraintLayout) - constraintLayout - .requireViewById<LaunchableImageView>(R.id.start_button) - .setOnApplyWindowInsetsListener { _, windowInsets -> - val tempSafeInset = windowInsets?.displayCutout?.safeInsetBottom ?: 0 - if (safeInsetBottom != tempSafeInset) { - safeInsetBottom = tempSafeInset - keyguardBlueprintInteractor - .get() - .refreshBlueprint(IntraBlueprintTransition.Type.DefaultTransition) - } - WindowInsets.CONSUMED + constraintLayout + .requireViewById<LaunchableImageView>(R.id.start_button) + .setOnApplyWindowInsetsListener { _, windowInsets -> + val tempSafeInset = windowInsets?.displayCutout?.safeInsetBottom ?: 0 + if (safeInsetBottom != tempSafeInset) { + safeInsetBottom = tempSafeInset + keyguardBlueprintInteractor + .get() + .refreshBlueprint(IntraBlueprintTransition.Type.DefaultTransition) } - } + WindowInsets.CONSUMED + } } override fun bindData(constraintLayout: ConstraintLayout) { - if (KeyguardBottomAreaRefactor.isEnabled) { - leftShortcutHandle?.destroy() - leftShortcutHandle = - keyguardQuickAffordanceViewBinder.bind( - constraintLayout.requireViewById(R.id.start_button), - keyguardQuickAffordancesCombinedViewModel.startButton, - keyguardQuickAffordancesCombinedViewModel.transitionAlpha, - ) { - indicationController.showTransientIndication(it) - } - rightShortcutHandle?.destroy() - rightShortcutHandle = - keyguardQuickAffordanceViewBinder.bind( - constraintLayout.requireViewById(R.id.end_button), - keyguardQuickAffordancesCombinedViewModel.endButton, - keyguardQuickAffordancesCombinedViewModel.transitionAlpha, - ) { - indicationController.showTransientIndication(it) - } - } + leftShortcutHandle?.destroy() + leftShortcutHandle = + keyguardQuickAffordanceViewBinder.bind( + constraintLayout.requireViewById(R.id.start_button), + keyguardQuickAffordancesCombinedViewModel.startButton, + keyguardQuickAffordancesCombinedViewModel.transitionAlpha, + ) { + indicationController.showTransientIndication(it) + } + rightShortcutHandle?.destroy() + rightShortcutHandle = + keyguardQuickAffordanceViewBinder.bind( + constraintLayout.requireViewById(R.id.end_button), + keyguardQuickAffordancesCombinedViewModel.endButton, + keyguardQuickAffordancesCombinedViewModel.transitionAlpha, + ) { + indicationController.showTransientIndication(it) + } } override fun applyConstraints(constraintSet: ConstraintSet) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt index 0ae1400b1906..8186aa3746cf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt @@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import com.android.systemui.shade.ShadeDisplayAware @@ -67,16 +66,12 @@ constructor( ConstraintSet.BOTTOM, ) - if (KeyguardBottomAreaRefactor.isEnabled) { - connect( - viewId, - ConstraintSet.BOTTOM, - R.id.keyguard_indication_area, - ConstraintSet.TOP, - ) - } else { - connect(viewId, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM) - } + connect( + viewId, + ConstraintSet.BOTTOM, + R.id.keyguard_indication_area, + ConstraintSet.TOP, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index 620cc13a0c3a..fc26d18fde6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -25,7 +25,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.TOP -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -62,9 +61,6 @@ constructor( } override fun addViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } // This moves the existing NSSL view to a different parent, as the controller is a // singleton and recreating it has other bad side effects. // In the SceneContainer, this is done by the NotificationSection composable. @@ -78,10 +74,6 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - disposableHandle?.dispose() disposableHandle = sharedNotificationContainerBinder.bind( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 73e14b1524f3..cd038d799f42 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -26,7 +26,6 @@ import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection @@ -70,7 +69,6 @@ constructor( } override fun addViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) return if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return smartspaceView = smartspaceController.buildAndConnectView(constraintLayout) weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout) @@ -98,7 +96,6 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) return if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return disposableHandle?.dispose() disposableHandle = @@ -111,13 +108,11 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!MigrateClocksToBlueprint.isEnabled) return if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return val dateWeatherPaddingStart = KeyguardSmartspaceViewModel.getDateWeatherStartMargin(context) val smartspaceHorizontalPadding = KeyguardSmartspaceViewModel.getSmartspaceHorizontalMargin(context) constraintSet.apply { - // migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) connect( @@ -128,7 +123,6 @@ constructor( dateWeatherPaddingStart, ) - // migrate addSmartspaceView from KeyguardClockSwitchController constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT) constrainWidth(sharedR.id.bc_smartspace_view, ConstraintSet.MATCH_CONSTRAINT) connect( @@ -182,7 +176,6 @@ constructor( } override fun removeViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) return if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return listOf(smartspaceView, dateWeatherView).forEach { it?.let { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt index 85ce5cd0f9fd..8af5b5fb652a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -23,12 +24,17 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCE import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION +import com.android.systemui.window.flag.WindowBlurFlag import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow /** * Breaks down ALTERNATE BOUNCER->PRIMARY BOUNCER transition into discrete steps for corresponding @@ -38,7 +44,10 @@ import kotlinx.coroutines.flow.Flow @SysUISingleton class AlternateBouncerToPrimaryBouncerTransitionViewModel @Inject -constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition { +constructor( + animationFlow: KeyguardTransitionAnimationFlow, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition, PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup( @@ -57,12 +66,30 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra else -> { step -> 1f - step } } - val lockscreenAlpha: Flow<Float> = + private val alphaFlow = transitionAnimation.sharedFlow( duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, onStep = alphaForAnimationStep, ) + val lockscreenAlpha: Flow<Float> = if (WindowBlurFlag.isEnabled) alphaFlow else emptyFlow() + + val notificationAlpha: Flow<Float> = alphaFlow + override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) + + override val windowBlurRadius: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = + transitionAnimation.immediatelyTransitionTo(MAX_BACKGROUND_BLUR_RADIUS), + flowWhenShadeIsNotExpanded = + transitionAnimation.sharedFlow( + duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + onStep = { step -> + MathUtils.lerp(MIN_BACKGROUND_BLUR_RADIUS, MAX_BACKGROUND_BLUR_RADIUS, step) + }, + onFinish = { MAX_BACKGROUND_BLUR_RADIUS }, + ), + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt index 35f05f55caa1..e6b796fb92c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt @@ -23,6 +23,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -36,19 +38,19 @@ import kotlinx.coroutines.flow.Flow @SysUISingleton class AodToPrimaryBouncerTransitionViewModel @Inject -constructor( - animationFlow: KeyguardTransitionAnimationFlow, -) : DeviceEntryIconTransition { +constructor(animationFlow: KeyguardTransitionAnimationFlow) : + DeviceEntryIconTransition, PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup( duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, edge = Edge.create(from = AOD, to = Scenes.Bouncer), ) - .setupWithoutSceneContainer( - edge = Edge.create(from = AOD, to = PRIMARY_BOUNCER), - ) + .setupWithoutSceneContainer(edge = Edge.create(from = AOD, to = PRIMARY_BOUNCER)) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) + + override val windowBlurRadius: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(MAX_BACKGROUND_BLUR_RADIUS) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt index 7ddf641e9e8e..c1670c3814dc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION import com.android.systemui.keyguard.shared.model.Edge @@ -23,6 +24,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -36,9 +40,8 @@ import kotlinx.coroutines.flow.Flow @SysUISingleton class DozingToPrimaryBouncerTransitionViewModel @Inject -constructor( - animationFlow: KeyguardTransitionAnimationFlow, -) : DeviceEntryIconTransition { +constructor(animationFlow: KeyguardTransitionAnimationFlow) : + DeviceEntryIconTransition, PrimaryBouncerTransition { private val transitionAnimation = animationFlow @@ -46,10 +49,17 @@ constructor( duration = TO_PRIMARY_BOUNCER_DURATION, edge = Edge.create(from = DOZING, to = Scenes.Bouncer), ) - .setupWithoutSceneContainer( - edge = Edge.create(from = DOZING, to = PRIMARY_BOUNCER), - ) + .setupWithoutSceneContainer(edge = Edge.create(from = DOZING, to = PRIMARY_BOUNCER)) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) + + override val windowBlurRadius: Flow<Float> = + transitionAnimation.sharedFlow( + TO_PRIMARY_BOUNCER_DURATION, + onStep = { step -> + MathUtils.lerp(MIN_BACKGROUND_BLUR_RADIUS, MAX_BACKGROUND_BLUR_RADIUS, step) + }, + onFinish = { MAX_BACKGROUND_BLUR_RADIUS }, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt deleted file mode 100644 index 6fe51ae885be..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ /dev/null @@ -1,252 +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.keyguard.ui.viewmodel - -import androidx.annotation.VisibleForTesting -import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel -import com.android.systemui.keyguard.shared.quickaffordance.ActivationState -import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition -import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map - -/** View-model for the keyguard bottom area view */ -@OptIn(ExperimentalCoroutinesApi::class) -class KeyguardBottomAreaViewModel -@Inject -constructor( - private val keyguardInteractor: KeyguardInteractor, - private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor, - private val bottomAreaInteractor: KeyguardBottomAreaInteractor, - private val burnInHelperWrapper: BurnInHelperWrapper, - private val keyguardTouchHandlingViewModel: KeyguardTouchHandlingViewModel, - val settingsMenuViewModel: KeyguardSettingsMenuViewModel, -) { - data class PreviewMode( - val isInPreviewMode: Boolean = false, - val shouldHighlightSelectedAffordance: Boolean = false, - ) - - /** - * Whether this view-model instance is powering the preview experience that renders exclusively - * in the wallpaper picker application. This should _always_ be `false` for the real lock screen - * experience. - */ - val previewMode = MutableStateFlow(PreviewMode()) - - /** - * ID of the slot that's currently selected in the preview that renders exclusively in the - * wallpaper picker application. This is ignored for the actual, real lock screen experience. - */ - private val selectedPreviewSlotId = - MutableStateFlow(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START) - - /** - * Whether quick affordances are "opaque enough" to be considered visible to and interactive by - * the user. If they are not interactive, user input should not be allowed on them. - * - * Note that there is a margin of error, where we allow very, very slightly transparent views to - * be considered "fully opaque" for the purpose of being interactive. This is to accommodate the - * error margin of floating point arithmetic. - * - * A view that is visible but with an alpha of less than our threshold either means it's not - * fully done fading in or is fading/faded out. Either way, it should not be - * interactive/clickable unless "fully opaque" to avoid issues like in b/241830987. - */ - private val areQuickAffordancesFullyOpaque: Flow<Boolean> = - bottomAreaInteractor.alpha - .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD } - .distinctUntilChanged() - - /** An observable for the view-model of the "start button" quick affordance. */ - val startButton: Flow<KeyguardQuickAffordanceViewModel> = - button(KeyguardQuickAffordancePosition.BOTTOM_START) - /** An observable for the view-model of the "end button" quick affordance. */ - val endButton: Flow<KeyguardQuickAffordanceViewModel> = - button(KeyguardQuickAffordancePosition.BOTTOM_END) - /** An observable for whether the overlay container should be visible. */ - val isOverlayContainerVisible: Flow<Boolean> = - keyguardInteractor.isDozing.map { !it }.distinctUntilChanged() - /** An observable for the alpha level for the entire bottom area. */ - val alpha: Flow<Float> = - previewMode.flatMapLatest { - if (it.isInPreviewMode) { - flowOf(1f) - } else { - bottomAreaInteractor.alpha.distinctUntilChanged() - } - } - /** An observable for the x-offset by which the indication area should be translated. */ - val indicationAreaTranslationX: Flow<Float> = - bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() - - /** Returns an observable for the y-offset by which the indication area should be translated. */ - fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> { - return keyguardInteractor.dozeAmount - .map { dozeAmount -> - dozeAmount * - (burnInHelperWrapper.burnInOffset( - /* amplitude = */ defaultBurnInOffset * 2, - /* xAxis= */ false, - ) - defaultBurnInOffset) - } - .distinctUntilChanged() - } - - /** - * Returns whether the keyguard bottom area should be constrained to the top of the lock icon - */ - fun shouldConstrainToTopOfLockIcon(): Boolean = - bottomAreaInteractor.shouldConstrainToTopOfLockIcon() - - /** - * Puts this view-model in "preview mode", which means it's being used for UI that is rendering - * the lock screen preview in wallpaper picker / settings and not the real experience on the - * lock screen. - * - * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one. - * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be - * highlighted (while all others are dimmed to make the selected one stand out). - */ - fun enablePreviewMode( - initiallySelectedSlotId: String?, - shouldHighlightSelectedAffordance: Boolean, - ) { - previewMode.value = - PreviewMode( - isInPreviewMode = true, - shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, - ) - onPreviewSlotSelected( - initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START - ) - } - - /** - * Notifies that a slot with the given ID has been selected in the preview experience that is - * rendering in the wallpaper picker. This is ignored for the real lock screen experience. - * - * @see enablePreviewMode - */ - fun onPreviewSlotSelected(slotId: String) { - selectedPreviewSlotId.value = slotId - } - - /** - * Notifies that some input gesture has started somewhere in the bottom area that's outside of - * the lock screen settings menu item pop-up. - */ - fun onTouchedOutsideLockScreenSettingsMenu() { - keyguardTouchHandlingViewModel.onTouchedOutside() - } - - private fun button( - position: KeyguardQuickAffordancePosition - ): Flow<KeyguardQuickAffordanceViewModel> { - return previewMode.flatMapLatest { previewMode -> - combine( - if (previewMode.isInPreviewMode) { - quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position) - } else { - quickAffordanceInteractor.quickAffordance(position = position) - }, - bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(), - areQuickAffordancesFullyOpaque, - selectedPreviewSlotId, - quickAffordanceInteractor.useLongPress(), - ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress -> - val slotId = position.toSlotId() - val isSelected = selectedPreviewSlotId == slotId - model.toViewModel( - animateReveal = !previewMode.isInPreviewMode && animateReveal, - isClickable = isFullyOpaque && !previewMode.isInPreviewMode, - isSelected = - previewMode.isInPreviewMode && - previewMode.shouldHighlightSelectedAffordance && - isSelected, - isDimmed = - previewMode.isInPreviewMode && - previewMode.shouldHighlightSelectedAffordance && - !isSelected, - forceInactive = previewMode.isInPreviewMode, - slotId = slotId, - useLongPress = useLongPress, - ) - } - .distinctUntilChanged() - } - } - - private fun KeyguardQuickAffordanceModel.toViewModel( - animateReveal: Boolean, - isClickable: Boolean, - isSelected: Boolean, - isDimmed: Boolean, - forceInactive: Boolean, - slotId: String, - useLongPress: Boolean, - ): KeyguardQuickAffordanceViewModel { - return when (this) { - is KeyguardQuickAffordanceModel.Visible -> - KeyguardQuickAffordanceViewModel( - configKey = configKey, - isVisible = true, - animateReveal = animateReveal, - icon = icon, - onClicked = { parameters -> - quickAffordanceInteractor.onQuickAffordanceTriggered( - configKey = parameters.configKey, - expandable = parameters.expandable, - slotId = parameters.slotId, - ) - }, - isClickable = isClickable, - isActivated = !forceInactive && activationState is ActivationState.Active, - isSelected = isSelected, - useLongPress = useLongPress, - isDimmed = isDimmed, - slotId = slotId, - ) - is KeyguardQuickAffordanceModel.Hidden -> - KeyguardQuickAffordanceViewModel( - slotId = slotId, - ) - } - } - - companion object { - // We select a value that's less than 1.0 because we want floating point math precision to - // not be a factor in determining whether the affordance UI is fully opaque. The number we - // choose needs to be close enough 1.0 such that the user can't easily tell the difference - // between the UI with an alpha at the threshold and when the alpha is 1.0. At the same - // time, we don't want the number to be too close to 1.0 such that there is a chance that we - // never treat the affordance UI as "fully opaque" as that would risk making it forever not - // clickable. - @VisibleForTesting const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt index bc3ef02a0ec5..4663a2b3c20c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt @@ -21,10 +21,8 @@ import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.BurnInInteractor -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.shared.model.BurnInModel @@ -48,8 +46,6 @@ class KeyguardIndicationAreaViewModel @Inject constructor( private val keyguardInteractor: KeyguardInteractor, - bottomAreaInteractor: KeyguardBottomAreaInteractor, - keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel, private val burnInHelperWrapper: BurnInHelperWrapper, burnInInteractor: BurnInInteractor, @Named(KeyguardQuickAffordancesCombinedViewModelModule.Companion.LOCKSCREEN_INSTANCE) @@ -64,9 +60,6 @@ constructor( /** Notifies when a new configuration is set */ val configurationChange: Flow<Unit> = configurationInteractor.onAnyConfigurationChange - /** An observable for the alpha level for the entire bottom area. */ - val alpha: Flow<Float> = keyguardBottomAreaViewModel.alpha - /** An observable for the visibility value for the indication area view. */ val visible: Flow<Boolean> = anyOf( @@ -76,22 +69,12 @@ constructor( /** An observable for whether the indication area should be padded. */ val isIndicationAreaPadded: Flow<Boolean> = - if (KeyguardBottomAreaRefactor.isEnabled) { - combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) { - startButtonModel, - endButtonModel -> - startButtonModel.isVisible || endButtonModel.isVisible - } - .distinctUntilChanged() - } else { - combine( - keyguardBottomAreaViewModel.startButton, - keyguardBottomAreaViewModel.endButton, - ) { startButtonModel, endButtonModel -> - startButtonModel.isVisible || endButtonModel.isVisible - } - .distinctUntilChanged() + combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) { + startButtonModel, + endButtonModel -> + startButtonModel.isVisible || endButtonModel.isVisible } + .distinctUntilChanged() @OptIn(ExperimentalCoroutinesApi::class) private val burnIn: Flow<BurnInModel> = @@ -114,11 +97,7 @@ constructor( /** An observable for the x-offset by which the indication area should be translated. */ val indicationAreaTranslationX: Flow<Float> = - if (MigrateClocksToBlueprint.isEnabled || KeyguardBottomAreaRefactor.isEnabled) { - burnIn.map { it.translationX.toFloat() }.flowOn(mainDispatcher) - } else { - bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() - } + burnIn.map { it.translationX.toFloat() }.flowOn(mainDispatcher) /** Returns an observable for the y-offset by which the indication area should be translated. */ fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 0d816041d1be..9066d466ceca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -92,6 +92,8 @@ constructor( AlternateBouncerToLockscreenTransitionViewModel, private val alternateBouncerToOccludedTransitionViewModel: AlternateBouncerToOccludedTransitionViewModel, + private val alternateBouncerToPrimaryBouncerTransitionViewModel: + AlternateBouncerToPrimaryBouncerTransitionViewModel, private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, @@ -238,6 +240,7 @@ constructor( alternateBouncerToAodTransitionViewModel.lockscreenAlpha(viewState), alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState), alternateBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState), + alternateBouncerToPrimaryBouncerTransitionViewModel.lockscreenAlpha, alternateBouncerToOccludedTransitionViewModel.lockscreenAlpha, aodToGoneTransitionViewModel.lockscreenAlpha(viewState), aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt index 914730e1ea4a..48cc8ad5321a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -23,6 +24,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION @@ -42,7 +46,7 @@ class LockscreenToPrimaryBouncerTransitionViewModel constructor( shadeDependentFlows: ShadeDependentFlows, animationFlow: KeyguardTransitionAnimationFlow, -) : DeviceEntryIconTransition { +) : DeviceEntryIconTransition, PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup( @@ -78,4 +82,16 @@ constructor( ), flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), ) + override val windowBlurRadius: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = + transitionAnimation.immediatelyTransitionTo(MAX_BACKGROUND_BLUR_RADIUS), + flowWhenShadeIsNotExpanded = + transitionAnimation.sharedFlow( + duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + onStep = { + MathUtils.lerp(MIN_BACKGROUND_BLUR_RADIUS, MAX_BACKGROUND_BLUR_RADIUS, it) + }, + ), + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt index 501feca8c4f7..f14144e36fb1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor @@ -24,6 +25,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -43,16 +47,14 @@ class PrimaryBouncerToAodTransitionViewModel constructor( deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, animationFlow: KeyguardTransitionAnimationFlow, -) : DeviceEntryIconTransition { +) : DeviceEntryIconTransition, PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup( duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, edge = Edge.create(from = Scenes.Bouncer, to = AOD), ) - .setupWithoutSceneContainer( - edge = Edge.create(from = PRIMARY_BOUNCER, to = AOD), - ) + .setupWithoutSceneContainer(edge = Edge.create(from = PRIMARY_BOUNCER, to = AOD)) val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) @@ -60,7 +62,7 @@ constructor( val lockscreenAlpha: Flow<Float> = transitionAnimation.sharedFlow( duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, - onStep = { it } + onStep = { it }, ) override val deviceEntryParentViewAlpha: Flow<Float> = @@ -77,4 +79,13 @@ constructor( emptyFlow() } } + + override val windowBlurRadius: Flow<Float> = + transitionAnimation.sharedFlow( + duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, + onStep = { step -> + MathUtils.lerp(MAX_BACKGROUND_BLUR_RADIUS, MIN_BACKGROUND_BLUR_RADIUS, step) + }, + onFinish = { MIN_BACKGROUND_BLUR_RADIUS }, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt index e5bb46432226..a24ed264e4c1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,7 +43,7 @@ class PrimaryBouncerToDozingTransitionViewModel constructor( deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, animationFlow: KeyguardTransitionAnimationFlow, -) : DeviceEntryIconTransition { +) : DeviceEntryIconTransition, PrimaryBouncerTransition { private val transitionAnimation = animationFlow @@ -50,9 +51,7 @@ constructor( duration = TO_DOZING_DURATION, edge = Edge.create(from = Scenes.Bouncer, to = DOZING), ) - .setupWithoutSceneContainer( - edge = Edge.create(from = PRIMARY_BOUNCER, to = DOZING), - ) + .setupWithoutSceneContainer(edge = Edge.create(from = PRIMARY_BOUNCER, to = DOZING)) val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) @@ -66,4 +65,9 @@ constructor( emptyFlow() } } + + override val windowBlurRadius: Flow<Float> = + transitionAnimation.immediatelyTransitionTo( + PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt index 9ec15dcff5f4..b52a3905a263 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt @@ -23,13 +23,16 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS import javax.inject.Inject import kotlinx.coroutines.flow.Flow @SysUISingleton class PrimaryBouncerToGlanceableHubTransitionViewModel @Inject -constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition { +constructor(animationFlow: KeyguardTransitionAnimationFlow) : + DeviceEntryIconTransition, PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup(duration = TO_GLANCEABLE_HUB_DURATION, edge = Edge.INVALID) @@ -37,4 +40,7 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) + + override val windowBlurRadius: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(MIN_BACKGROUND_BLUR_RADIUS) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt index 17c678e79d8b..713ac1527d5b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt @@ -16,16 +16,21 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_SHORT_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS import com.android.systemui.statusbar.SysuiStatusBarStateController import dagger.Lazy import javax.inject.Inject @@ -48,16 +53,11 @@ constructor( keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>, bouncerToGoneFlows: BouncerToGoneFlows, animationFlow: KeyguardTransitionAnimationFlow, -) { +) : PrimaryBouncerTransition { private val transitionAnimation = animationFlow - .setup( - duration = TO_GONE_DURATION, - edge = Edge.INVALID, - ) - .setupWithoutSceneContainer( - edge = Edge.create(from = PRIMARY_BOUNCER, to = GONE), - ) + .setup(duration = TO_GONE_DURATION, edge = Edge.INVALID) + .setupWithoutSceneContainer(edge = Edge.create(from = PRIMARY_BOUNCER, to = GONE)) private var leaveShadeOpen: Boolean = false private var willRunDismissFromKeyguard: Boolean = false @@ -96,7 +96,7 @@ constructor( private fun createBouncerAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> { return transitionAnimation.sharedFlow( - duration = 200.milliseconds, + duration = TO_GONE_SHORT_DURATION, onStart = { willRunDismissFromKeyguard = willRunAnimationOnKeyguard() }, onStep = { if (willRunDismissFromKeyguard) { @@ -108,6 +108,22 @@ constructor( ) } + private fun createBouncerWindowBlurFlow( + willRunAnimationOnKeyguard: () -> Boolean + ): Flow<Float> { + return transitionAnimation.sharedFlow( + duration = TO_GONE_SHORT_DURATION, + onStart = { willRunDismissFromKeyguard = willRunAnimationOnKeyguard() }, + onStep = { + if (willRunDismissFromKeyguard) { + MIN_BACKGROUND_BLUR_RADIUS + } else { + MathUtils.lerp(MAX_BACKGROUND_BLUR_RADIUS, MIN_BACKGROUND_BLUR_RADIUS, it) + } + }, + ) + } + /** Lockscreen alpha */ val lockscreenAlpha: Flow<Float> = if (ComposeBouncerFlags.isEnabled) { @@ -137,6 +153,16 @@ constructor( ) } + override val windowBlurRadius: Flow<Float> = + if (ComposeBouncerFlags.isEnabled) { + keyguardDismissActionInteractor + .get() + .willAnimateDismissActionOnLockscreen + .flatMapLatest { createBouncerWindowBlurFlow { it } } + } else { + createBouncerWindowBlurFlow(primaryBouncerInteractor::willRunDismissFromKeyguard) + } + val scrimAlpha: Flow<ScrimAlpha> = bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, PRIMARY_BOUNCER) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt index d29f5129bd59..e737fcebe211 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -25,6 +25,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -41,22 +44,21 @@ class PrimaryBouncerToLockscreenTransitionViewModel @Inject constructor( animationFlow: KeyguardTransitionAnimationFlow, -) : DeviceEntryIconTransition { + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition, PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup( duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN), ) - .setupWithoutSceneContainer( - edge = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN), - ) + .setupWithoutSceneContainer(edge = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN)) val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( duration = 250.milliseconds, interpolator = EMPHASIZED_ACCELERATE, - onStep = { it } + onStep = { it }, ) fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { @@ -72,4 +74,17 @@ constructor( transitionAnimation.immediatelyTransitionTo(1f) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) + + override val windowBlurRadius: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = + transitionAnimation.immediatelyTransitionTo(MAX_BACKGROUND_BLUR_RADIUS), + flowWhenShadeIsNotExpanded = + transitionAnimation.sharedFlow( + duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, + onStep = { + MathUtils.lerp(MAX_BACKGROUND_BLUR_RADIUS, MIN_BACKGROUND_BLUR_RADIUS, it) + }, + ), + ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index f0f8a9592b6f..4e97eb5bc9d1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -32,6 +32,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.PathInterpolator +import android.widget.ImageView import android.widget.LinearLayout import androidx.annotation.VisibleForTesting import androidx.lifecycle.Lifecycle @@ -207,7 +208,7 @@ constructor( val mediaFrame: ViewGroup @VisibleForTesting - lateinit var settingsButton: View + lateinit var settingsButton: ImageView private set private val mediaContent: ViewGroup @@ -650,7 +651,7 @@ constructor( private fun inflateSettingsButton() { val settings = LayoutInflater.from(context) - .inflate(R.layout.media_carousel_settings_button, mediaFrame, false) as View + .inflate(R.layout.media_carousel_settings_button, mediaFrame, false) as ImageView if (this::settingsButton.isInitialized) { mediaFrame.removeView(settingsButton) } @@ -1493,6 +1494,17 @@ constructor( this.desiredHostState = it currentlyExpanded = it.expansion > 0 + // Set color of the settings button to material "on primary" color when media is on + // communal for aesthetic and accessibility purposes since the background of + // Glanceable Hub is a dynamic color. + if (desiredLocation == MediaHierarchyManager.LOCATION_COMMUNAL_HUB) { + settingsButton.setColorFilter( + context.getColor(com.android.internal.R.color.materialColorOnPrimary) + ) + } else { + settingsButton.setColorFilter(context.getColor(R.color.notification_gear_color)) + } + val shouldCloseGuts = !currentlyExpanded && !mediaManager.hasActiveMediaOrRecommendation() && diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 574ccee28faa..ab998d10287f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -366,7 +366,6 @@ public abstract class MediaOutputBaseAdapter extends / (double) seekBar.getMax()); mVolumeValueText.setText(mContext.getResources().getString( R.string.media_output_dialog_volume_percentage, percentage)); - mVolumeValueText.setVisibility(View.VISIBLE); if (mStartFromMute) { updateUnmutedVolumeIcon(device); mStartFromMute = false; diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt index a19c9b30f68d..debb667bbb15 100644 --- a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt @@ -16,7 +16,6 @@ package com.android.systemui.mediarouter.data.repository -import android.media.projection.StopReason import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer @@ -41,7 +40,7 @@ interface MediaRouterRepository { val castDevices: StateFlow<List<CastDevice>> /** Stops the cast to the given device. */ - fun stopCasting(device: CastDevice, @StopReason stopReason: Int) + fun stopCasting(device: CastDevice) } @SysUISingleton @@ -68,8 +67,8 @@ constructor( .map { it.filter { device -> device.origin == CastDevice.CastOrigin.MediaRouter } } .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList()) - override fun stopCasting(device: CastDevice, @StopReason stopReason: Int) { - castController.stopCasting(device, stopReason) + override fun stopCasting(device: CastDevice) { + castController.stopCasting(device) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index 91a3120ec770..1e608af14568 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -67,6 +67,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.DeviceConfigProxy @@ -141,7 +142,6 @@ interface FgsManagerController { class FgsManagerControllerImpl @Inject constructor( - @ShadeDisplayAware private val context: Context, @ShadeDisplayAware private val resources: Resources, @Main private val mainExecutor: Executor, @Background private val backgroundExecutor: Executor, @@ -155,6 +155,7 @@ constructor( private val broadcastDispatcher: BroadcastDispatcher, private val dumpManager: DumpManager, private val systemUIDialogFactory: SystemUIDialog.Factory, + private val shadeDialogContextRepository: ShadeDialogContextInteractor, ) : Dumpable, FgsManagerController { companion object { @@ -388,7 +389,7 @@ constructor( override fun showDialog(expandable: Expandable?) { synchronized(lock) { if (dialog == null) { - val dialog = systemUIDialogFactory.create(context) + val dialog = systemUIDialogFactory.create(shadeDialogContextRepository.context) dialog.setTitle(R.string.fgs_manager_dialog_title) dialog.setMessage(R.string.fgs_manager_dialog_message) diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt index c912bd59c19f..790793eab258 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt @@ -17,14 +17,16 @@ package com.android.systemui.qs.composefragment.ui import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.ClipOp -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.asAndroidPath -import androidx.compose.ui.graphics.drawscope.ContentDrawScope -import androidx.compose.ui.graphics.drawscope.clipPath -import androidx.compose.ui.node.DrawModifierNode -import androidx.compose.ui.node.ModifierNodeElement -import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.graphics.layer.CompositingStrategy +import androidx.compose.ui.graphics.layer.drawLayer /** * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out @@ -32,65 +34,30 @@ import androidx.compose.ui.platform.InspectorInfo * from the QS container. */ fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier { - return this then NotificationScrimClipElement(clipParams) -} - -private class NotificationScrimClipNode(var clipParams: () -> NotificationScrimClipParams) : - DrawModifierNode, Modifier.Node() { - private val path = Path() - - private var lastClipParams = NotificationScrimClipParams() - - override fun ContentDrawScope.draw() { - val newClipParams = clipParams() - if (newClipParams != lastClipParams) { - lastClipParams = newClipParams - applyClipParams(path, lastClipParams) - } - clipPath(path, ClipOp.Difference) { this@draw.drawContent() } - } - - private fun ContentDrawScope.applyClipParams( - path: Path, - clipParams: NotificationScrimClipParams, - ) { - with(clipParams) { - path.rewind() - path - .asAndroidPath() - .addRoundRect( - -leftInset.toFloat(), - top.toFloat(), - size.width + rightInset, - bottom.toFloat(), - radius.toFloat(), - radius.toFloat(), - android.graphics.Path.Direction.CW, - ) - } - } -} - -private data class NotificationScrimClipElement(val clipParams: () -> NotificationScrimClipParams) : - ModifierNodeElement<NotificationScrimClipNode>() { - override fun create(): NotificationScrimClipNode { - return NotificationScrimClipNode(clipParams) - } - - override fun update(node: NotificationScrimClipNode) { - node.clipParams = clipParams - } - - override fun InspectorInfo.inspectableProperties() { - name = "notificationScrimClip" - with(clipParams()) { - properties["leftInset"] = leftInset - properties["top"] = top - properties["rightInset"] = rightInset - properties["bottom"] = bottom - properties["radius"] = radius + return this.drawWithCache { + val params = clipParams() + val left = -params.leftInset.toFloat() + val right = size.width + params.rightInset.toFloat() + val top = params.top.toFloat() + val bottom = params.bottom.toFloat() + val graphicsLayer = obtainGraphicsLayer() + graphicsLayer.compositingStrategy = CompositingStrategy.Offscreen + graphicsLayer.record { + drawContent() + clipRect { + drawRoundRect( + color = Color.Black, + cornerRadius = CornerRadius(params.radius.toFloat()), + blendMode = BlendMode.Clear, + topLeft = Offset(left, top), + size = Size(right - left, bottom - top), + ) + } + } + onDrawWithContent { + drawLayer(graphicsLayer) + } } - } } /** Params for [notificationScrimClip]. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt index 71615653236b..7b9f42c4e14d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt @@ -40,6 +40,7 @@ import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig import com.android.systemui.security.data.repository.SecurityRepository +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.data.repository.UserSwitcherRepository import com.android.systemui.user.domain.interactor.UserSwitcherInteractor @@ -108,6 +109,7 @@ constructor( userSwitcherRepository: UserSwitcherRepository, broadcastDispatcher: BroadcastDispatcher, @Background bgDispatcher: CoroutineDispatcher, + @ShadeDisplayAware private val context: Context, ) : FooterActionsInteractor { override val securityButtonConfig: Flow<SecurityButtonConfig?> = securityRepository.security.map { security -> @@ -157,6 +159,7 @@ constructor( /* keyguardShowing= */ false, /* isDeviceProvisioned= */ true, expandable, + context.displayId, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt index 85db95203b45..f3c06a481fc2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt @@ -54,7 +54,7 @@ fun EditModeButton( ) { Icon( imageVector = Icons.Default.Edit, - contentDescription = stringResource(id = R.string.qs_edit), + contentDescription = stringResource(id = R.string.accessibility_quick_settings_edit), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt index d8361f544bdc..03f0297e0d54 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt @@ -23,11 +23,9 @@ import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow @SysUISingleton -class DetailsViewModel -@Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) { +class DetailsViewModel @Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) { /** * The current active [TileDetailsViewModel]. If it's `null`, it means the qs overlay is not @@ -38,6 +36,7 @@ class DetailsViewModel /** * Update the active [TileDetailsViewModel] to `null`. + * * @see activeTileDetails */ fun closeDetailedView() { @@ -45,8 +44,9 @@ class DetailsViewModel } /** - * Update the active [TileDetailsViewModel] to the `spec`'s corresponding view model. - * Return if the [TileDetailsViewModel] is successfully found. + * Update the active [TileDetailsViewModel] to the `spec`'s corresponding view model. Return if + * the [TileDetailsViewModel] is successfully handled. + * * @see activeTileDetails */ fun onTileClicked(spec: TileSpec?): Boolean { @@ -55,11 +55,11 @@ class DetailsViewModel return false } - _activeTileDetails.value = currentTilesInteractor - .currentQSTiles - .firstOrNull { it.tileSpec == spec.spec } - ?.detailsViewModel + val currentTile = + currentTilesInteractor.currentQSTiles.firstOrNull { it.tileSpec == spec.spec } - return _activeTileDetails.value != null + return currentTile?.getDetailsViewModel { detailsViewModel -> + _activeTileDetails.value = detailsViewModel + } ?: false } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 7eb0aaabb7e1..0109e70a467e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -43,6 +43,7 @@ import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.satellite.SatelliteDialogUtils; import com.android.systemui.animation.Expandable; +import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsViewModel; import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -51,6 +52,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; +import com.android.systemui.plugins.qs.TileDetailsViewModel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; @@ -63,6 +65,7 @@ import kotlinx.coroutines.Job; import java.util.List; import java.util.concurrent.Executor; +import java.util.function.Consumer; import javax.inject.Inject; @@ -121,6 +124,21 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { @Override protected void handleClick(@Nullable Expandable expandable) { + handleClickWithSatelliteCheck(() -> handleClickEvent(expandable)); + } + + @Override + public boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) { + handleClickWithSatelliteCheck(() -> + callback.accept(new BluetoothDetailsViewModel(() -> { + longClick(null); + return null; + })) + ); + return true; + } + + private void handleClickWithSatelliteCheck(Runnable clickCallback) { if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) { if (mClickJob != null && !mClickJob.isCompleted()) { return; @@ -130,12 +148,12 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { if (!isAllowClick) { return null; } - handleClickEvent(expandable); + clickCallback.run(); return null; }); return; } - handleClickEvent(expandable); + clickCallback.run(); } private void handleClickEvent(@Nullable Expandable expandable) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 30c2adf89e9b..ad027b4346d0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -24,7 +24,6 @@ import android.annotation.NonNull; import android.app.Dialog; import android.content.Intent; import android.media.MediaRouter.RouteInfo; -import android.media.projection.StopReason; import android.os.Handler; import android.os.Looper; import android.provider.Settings; @@ -184,7 +183,7 @@ public class CastTile extends QSTileImpl<BooleanState> { }); } } else { - mController.stopCasting(activeDevices.get(0), StopReason.STOP_QS_TILE); + mController.stopCasting(activeDevices.get(0)); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 42a0cb1004f4..b7ff63cdc1fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -41,6 +41,7 @@ import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.DataSaverController; @@ -56,6 +57,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements private final DataSaverController mDataSaverController; private final DialogTransitionAnimator mDialogTransitionAnimator; private final SystemUIDialog.Factory mSystemUIDialogFactory; + private final ShadeDialogContextInteractor mShadeDialogContextInteractor; @Inject public DataSaverTile( @@ -70,13 +72,15 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements QSLogger qsLogger, DataSaverController dataSaverController, DialogTransitionAnimator dialogTransitionAnimator, - SystemUIDialog.Factory systemUIDialogFactory + SystemUIDialog.Factory systemUIDialogFactory, + ShadeDialogContextInteractor shadeDialogContextInteractor ) { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mDataSaverController = dataSaverController; mDialogTransitionAnimator = dialogTransitionAnimator; mSystemUIDialogFactory = systemUIDialogFactory; + mShadeDialogContextInteractor = shadeDialogContextInteractor; mDataSaverController.observe(getLifecycle(), this); } @@ -102,7 +106,8 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements // Show a dialog to confirm first. Dialogs shown by the DialogTransitionAnimator must be // created and shown on the main thread, so we post it to the UI handler. mUiHandler.post(() -> { - SystemUIDialog dialog = mSystemUIDialogFactory.create(mContext); + SystemUIDialog dialog = mSystemUIDialogFactory.create( + mShadeDialogContextInteractor.getContext()); dialog.setTitle(com.android.internal.R.string.data_saver_enable_title); dialog.setMessage(com.android.internal.R.string.data_saver_description); dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index ec8d30b01eab..fc825926c374 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -18,7 +18,6 @@ package com.android.systemui.qs.tiles; import android.app.Dialog; import android.content.Intent; -import android.media.projection.StopReason; import android.os.Handler; import android.os.Looper; import android.service.quicksettings.Tile; @@ -227,7 +226,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> } private void stopRecording() { - mController.stopRecording(StopReason.STOP_QS_TILE); + mController.stopRecording(); } private final class Callback implements RecordingController.RecordingStateChangeCallback { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java index 70c2a2a0d55a..5e9deec58c58 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java @@ -72,6 +72,7 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; import com.android.systemui.shade.ShadeDisplayAware; +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wifitrackerlib.WifiEntry; @@ -104,9 +105,9 @@ public class InternetDialogDelegate implements private final Handler mHandler; private final Executor mBackgroundExecutor; private final DialogTransitionAnimator mDialogTransitionAnimator; - private final Context mContext; private final boolean mAboveStatusBar; private final SystemUIDialog.Factory mSystemUIDialogFactory; + private final ShadeDialogContextInteractor mShadeDialogContextInteractor; @VisibleForTesting protected InternetAdapter mAdapter; @@ -204,10 +205,11 @@ public class InternetDialogDelegate implements @Main Handler handler, @Background Executor executor, KeyguardStateController keyguardStateController, - SystemUIDialog.Factory systemUIDialogFactory) { - mContext = context; + SystemUIDialog.Factory systemUIDialogFactory, + ShadeDialogContextInteractor shadeDialogContextInteractor) { mAboveStatusBar = aboveStatusBar; mSystemUIDialogFactory = systemUIDialogFactory; + mShadeDialogContextInteractor = shadeDialogContextInteractor; if (DEBUG) { Log.d(TAG, "Init InternetDialog"); } @@ -230,7 +232,8 @@ public class InternetDialogDelegate implements @Override public SystemUIDialog createDialog() { - SystemUIDialog dialog = mSystemUIDialogFactory.create(this, mContext); + SystemUIDialog dialog = mSystemUIDialogFactory.create(this, + mShadeDialogContextInteractor.getContext()); if (!mAboveStatusBar) { dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt index 94534479db57..85aa6745e438 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor -import android.media.projection.StopReason import android.util.Log import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj @@ -62,9 +61,7 @@ constructor( Log.d(TAG, "Cancelling countdown") withContext(backgroundContext) { recordingController.cancelCountdown() } } - is ScreenRecordModel.Recording -> { - screenRecordRepository.stopRecording(StopReason.STOP_QS_TILE) - } + is ScreenRecordModel.Recording -> screenRecordRepository.stopRecording() is ScreenRecordModel.DoingNothing -> withContext(mainContext) { showPrompt(action.expandable, user.identifier) diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt index 8c54ab40c680..862dba1e7294 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt @@ -17,7 +17,6 @@ package com.android.systemui.qs.user import android.app.Dialog -import android.content.Context import android.content.DialogInterface import android.content.DialogInterface.BUTTON_NEUTRAL import android.content.Intent @@ -34,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.qs.tiles.UserDetailView import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.user.ui.dialog.DialogShowerImpl import javax.inject.Inject @@ -50,6 +50,7 @@ constructor( private val dialogTransitionAnimator: DialogTransitionAnimator, private val uiEventLogger: UiEventLogger, private val dialogFactory: SystemUIDialog.Factory, + private val shadeDialogContextInteractor: ShadeDialogContextInteractor, ) { companion object { @@ -63,7 +64,8 @@ constructor( * Populate the dialog with information from and adapter obtained from * [userDetailViewAdapterProvider] and show it as launched from [expandable]. */ - fun showDialog(context: Context, expandable: Expandable) { + fun showDialog(expandable: Expandable) { + val context = shadeDialogContextInteractor.context with(dialogFactory.create(context)) { setShowForAllUsers(true) setCanceledOnTouchOutside(true) diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt index 6097ef53f8df..33bffc2e35ab 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt @@ -22,7 +22,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.Flags.sceneContainer import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun @@ -37,7 +36,6 @@ object SceneContainerFlag { inline val isEnabled get() = sceneContainer() && // mainAconfigFlag - KeyguardBottomAreaRefactor.isEnabled && KeyguardWmStateRefactor.isEnabled && MigrateClocksToBlueprint.isEnabled && NotificationThrottleHun.isEnabled && @@ -51,7 +49,6 @@ object SceneContainerFlag { /** The set of secondary flags which must be enabled for scene container to work properly */ inline fun getSecondaryFlags(): Sequence<FlagToken> = sequenceOf( - KeyguardBottomAreaRefactor.token, KeyguardWmStateRefactor.token, MigrateClocksToBlueprint.token, NotificationThrottleHun.token, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 9ee99e45ceeb..d7463f8f0c36 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -23,7 +23,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.media.projection.StopReason; import android.os.Bundle; import android.os.CountDownTimer; import android.os.Process; @@ -59,7 +58,6 @@ public class RecordingController private boolean mIsStarting; private boolean mIsRecording; private PendingIntent mStopIntent; - private @StopReason int mStopReason = StopReason.STOP_UNKNOWN; private final Bundle mInteractiveBroadcastOption; private CountDownTimer mCountDownTimer = null; private final Executor mMainExecutor; @@ -85,7 +83,7 @@ public class RecordingController new UserTracker.Callback() { @Override public void onUserChanged(int newUser, @NonNull Context userContext) { - stopRecording(StopReason.STOP_USER_SWITCH); + stopRecording(); } }; @@ -242,11 +240,9 @@ public class RecordingController } /** - * Stop the recording and sets the stop reason to be used by the RecordingService - * @param stopReason the method of the recording stopped (i.e. QS tile, status bar chip, etc.) + * Stop the recording */ - public void stopRecording(@StopReason int stopReason) { - mStopReason = stopReason; + public void stopRecording() { try { if (mStopIntent != null) { mRecordingControllerLogger.logRecordingStopped(); @@ -281,10 +277,6 @@ public class RecordingController } } - public @StopReason int getStopReason() { - return mStopReason; - } - @Override public void addCallback(@NonNull RecordingStateChangeCallback listener) { mListeners.add(listener); diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index f7b52719a4f4..8c207d13d50e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -26,7 +26,6 @@ import android.content.Context; import android.content.Intent; import android.graphics.drawable.Icon; import android.media.MediaRecorder; -import android.media.projection.StopReason; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -79,7 +78,6 @@ public class RecordingService extends Service implements ScreenMediaRecorderList private static final String EXTRA_SHOW_TAPS = "extra_showTaps"; private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget"; private static final String EXTRA_DISPLAY_ID = "extra_displayId"; - private static final String EXTRA_STOP_REASON = "extra_stopReason"; protected static final String ACTION_START = "com.android.systemui.screenrecord.START"; protected static final String ACTION_SHOW_START_NOTIF = @@ -244,8 +242,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList // Check user ID - we may be getting a stop intent after user switch, in which case // we want to post the notifications for that user, which is NOT current user int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED); - int stopReason = intent.getIntExtra(EXTRA_STOP_REASON, mController.getStopReason()); - stopService(userId, stopReason); + stopService(userId); break; case ACTION_SHARE: @@ -489,11 +486,11 @@ public class RecordingService extends Service implements ScreenMediaRecorderList getTag(), notificationIdForGroup, groupNotif, currentUser); } - private void stopService(@StopReason int stopReason) { - stopService(USER_ID_NOT_SPECIFIED, stopReason); + private void stopService() { + stopService(USER_ID_NOT_SPECIFIED); } - private void stopService(int userId, @StopReason int stopReason) { + private void stopService(int userId) { if (userId == USER_ID_NOT_SPECIFIED) { userId = mUserContextTracker.getUserContext().getUserId(); } @@ -502,7 +499,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList setTapsVisible(mOriginalShowTaps); try { if (getRecorder() != null) { - getRecorder().end(stopReason); + getRecorder().end(); } saveRecording(userId); } catch (RuntimeException exception) { @@ -601,8 +598,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList * @return */ protected Intent getNotificationIntent(Context context) { - return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF) - .putExtra(EXTRA_STOP_REASON, StopReason.STOP_HOST_APP); + return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF); } private Intent getShareIntent(Context context, Uri path) { @@ -614,17 +610,14 @@ public class RecordingService extends Service implements ScreenMediaRecorderList @Override public void onInfo(MediaRecorder mr, int what, int extra) { Log.d(getTag(), "Media recorder info: " + what); - // Stop due to record reaching size limits so log as stopping due to error - Intent stopIntent = getStopIntent(this); - stopIntent.putExtra(EXTRA_STOP_REASON, StopReason.STOP_ERROR); - onStartCommand(stopIntent, 0, 0); + onStartCommand(getStopIntent(this), 0, 0); } @Override - public void onStopped(@StopReason int stopReason) { + public void onStopped() { if (mController.isRecording()) { Log.d(getTag(), "Stopping recording because the system requested the stop"); - stopService(stopReason); + stopService(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index f4455bfb7048..2ca0621635a7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -41,7 +41,6 @@ import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionManager; import android.media.projection.MediaProjection; import android.media.projection.MediaProjectionManager; -import android.media.projection.StopReason; import android.net.Uri; import android.os.Handler; import android.os.IBinder; @@ -301,7 +300,7 @@ public class ScreenMediaRecorder extends MediaProjection.Callback { /** * End screen recording, throws an exception if stopping recording failed */ - void end(@StopReason int stopReason) throws IOException { + void end() throws IOException { Closer closer = new Closer(); // MediaRecorder might throw RuntimeException if stopped immediately after starting @@ -310,17 +309,7 @@ public class ScreenMediaRecorder extends MediaProjection.Callback { closer.register(mMediaRecorder::release); closer.register(mInputSurface::release); closer.register(mVirtualDisplay::release); - closer.register(() -> { - if (stopReason == StopReason.STOP_UNKNOWN) { - // Attempt to call MediaProjection#stop() even if it might have already been called. - // If projection has already been stopped, then nothing will happen. Else, stop - // will be logged as a manually requested stop from host app. - mMediaProjection.stop(); - } else { - // In any other case, the stop reason is related to the recorder, so pass it on here - mMediaProjection.stop(stopReason); - } - }); + closer.register(mMediaProjection::stop); closer.register(this::stopInternalAudioRecording); closer.close(); @@ -334,7 +323,7 @@ public class ScreenMediaRecorder extends MediaProjection.Callback { @Override public void onStop() { Log.d(TAG, "The system notified about stopping the projection"); - mListener.onStopped(StopReason.STOP_UNKNOWN); + mListener.onStopped(); } private void stopInternalAudioRecording() { @@ -464,7 +453,7 @@ public class ScreenMediaRecorder extends MediaProjection.Callback { * For example, this might happen when doing partial screen sharing of an app * and the app that is being captured is closed. */ - void onStopped(@StopReason int stopReason); + void onStopped(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt index b6b8ffa11495..9eeb3b9576d8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt @@ -16,7 +16,6 @@ package com.android.systemui.screenrecord.data.repository -import android.media.projection.StopReason import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.screenrecord.RecordingController @@ -42,7 +41,7 @@ interface ScreenRecordRepository { val screenRecordState: Flow<ScreenRecordModel> /** Stops the recording. */ - suspend fun stopRecording(@StopReason stopReason: Int) + suspend fun stopRecording() } @SysUISingleton @@ -96,7 +95,7 @@ constructor( } } - override suspend fun stopRecording(@StopReason stopReason: Int) { - withContext(bgCoroutineContext) { recordingController.stopRecording(stopReason) } + override suspend fun stopRecording() { + withContext(bgCoroutineContext) { recordingController.stopRecording() } } } diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java index a7b51faaed57..10ac2cf76763 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java @@ -16,6 +16,8 @@ package com.android.systemui.scrim; +import static com.android.systemui.Flags.notificationShadeBlur; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -214,8 +216,7 @@ public class ScrimDrawable extends Drawable { public void draw(@NonNull Canvas canvas) { mPaint.setColor(mMainColor); mPaint.setAlpha(mAlpha); - if (WindowBlurFlag.isEnabled()) { - // TODO(b/370555223): Match the alpha to the visual spec when it is finalized. + if (notificationShadeBlur() || WindowBlurFlag.isEnabled()) { // TODO (b/381263600), wire this at ScrimController, move it to PrimaryBouncerTransition mPaint.setAlpha((int) (0.5f * mAlpha)); } diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java index 4bfa61e9dcd4..0f80e7432a54 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java @@ -16,6 +16,8 @@ package com.android.systemui.scrim; +import static com.android.systemui.Flags.notificationShadeBlur; + import static java.lang.Float.isNaN; import android.annotation.NonNull; @@ -39,13 +41,12 @@ 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.res.R; import com.android.systemui.shade.TouchLogger; import com.android.systemui.util.LargeScreenUtils; import java.util.concurrent.Executor; -import static com.android.systemui.Flags.notificationShadeBlur; - /** * A view which can draw a scrim. This view maybe be used in multiple windows running on different * threads, but is controlled by {@link com.android.systemui.statusbar.phone.ScrimController} so we @@ -253,8 +254,11 @@ public class ScrimView extends View { mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, tintAmount); } if (notificationShadeBlur()) { - // TODO(b/370555223): Fix color and transparency to match visual spec exactly - mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), Color.GRAY, 0.5f); + int layerAbove = ColorUtils.setAlphaComponent( + getResources().getColor(R.color.shade_panel, null), + (int) (0.4f * 255)); + int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255)); + mainTinted = ColorUtils.compositeColors(layerAbove, layerBelow); } drawable.setColor(mainTinted, animated); } else { diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index e1631ccdcb06..bbb13d5c1dfe 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -61,9 +61,18 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { /** Callback for notifying of changes. */ @WeaklyReferencedCallback interface Callback { - /** Notifies that the current user will be changed. */ + /** + * Same as {@link onBeforeUserSwitching(Int, Runnable)} but the callback will be called + * automatically after the completion of this method. + */ fun onBeforeUserSwitching(newUser: Int) {} + /** Notifies that the current user will be changed. */ + fun onBeforeUserSwitching(newUser: Int, resultCallback: Runnable) { + onBeforeUserSwitching(newUser) + resultCallback.run() + } + /** * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be called * automatically after the completion of this method. diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index b7a3aedc565e..42d83637ec1a 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -196,8 +196,9 @@ internal constructor( private fun registerUserSwitchObserver() { iActivityManager.registerUserSwitchObserver( object : UserSwitchObserver() { - override fun onBeforeUserSwitching(newUserId: Int) { + override fun onBeforeUserSwitching(newUserId: Int, reply: IRemoteCallback?) { handleBeforeUserSwitching(newUserId) + reply?.sendResult(null) } override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { @@ -236,8 +237,7 @@ internal constructor( setUserIdInternal(newUserId) notifySubscribers { callback, resultCallback -> - callback.onBeforeUserSwitching(newUserId) - resultCallback.run() + callback.onBeforeUserSwitching(newUserId, resultCallback) } .await() } diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java index b24edd9beece..d78f4d8238b1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java @@ -24,7 +24,6 @@ import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; -import com.android.keyguard.LockIconViewController; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -39,7 +38,6 @@ public class DebugDrawable extends Drawable { private final NotificationPanelViewController mNotificationPanelViewController; private final NotificationPanelView mView; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; - private final LockIconViewController mLockIconViewController; private final QuickSettingsController mQsController; private final Set<Integer> mDebugTextUsedYPositions; private final Paint mDebugPaint; @@ -48,13 +46,11 @@ public class DebugDrawable extends Drawable { NotificationPanelViewController notificationPanelViewController, NotificationPanelView notificationPanelView, NotificationStackScrollLayoutController notificationStackScrollLayoutController, - LockIconViewController lockIconViewController, QuickSettingsController quickSettingsController ) { mNotificationPanelViewController = notificationPanelViewController; mView = notificationPanelView; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; - mLockIconViewController = lockIconViewController; mQsController = quickSettingsController; mDebugTextUsedYPositions = new HashSet<>(); mDebugPaint = new Paint(); @@ -91,8 +87,6 @@ public class DebugDrawable extends Drawable { } drawDebugInfo(canvas, mNotificationPanelViewController.getClockPositionResult().clockY, Color.GRAY, "mClockPositionResult.clockY"); - drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY, - "mLockIconViewController.getTop()"); if (mNotificationPanelViewController.isKeyguardShowing()) { // Notifications have the space between those two lines. diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index d347084f435d..9e8858318943 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -25,7 +25,6 @@ import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.android.systemui.Flags.msdlFeedback; import static com.android.systemui.Flags.predictiveBackAnimateShade; -import static com.android.systemui.Flags.smartspaceRelocateToBottom; import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; import static com.android.systemui.classifier.Classifier.GENERIC; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; @@ -64,6 +63,8 @@ import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; +import android.graphics.RenderEffect; +import android.graphics.Shader; import android.os.Bundle; import android.os.Handler; import android.os.Trace; @@ -105,7 +106,6 @@ import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUnfoldTransition; 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; @@ -128,11 +128,9 @@ import com.android.systemui.dump.DumpsysTableLogger; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.fragments.FragmentService; -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewConfigurator; import com.android.systemui.keyguard.MigrateClocksToBlueprint; -import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -141,9 +139,9 @@ import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; +import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition; 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.KeyguardTouchHandlingViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; @@ -187,7 +185,6 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; @@ -195,6 +192,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -211,8 +209,6 @@ import com.android.systemui.statusbar.phone.BounceInterpolator; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -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.KeyguardClockPositionAlgorithm; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; @@ -366,7 +362,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final TouchHandler mTouchHandler = new TouchHandler(); private long mDownTime; - private long mStatusBarLongPressDowntime; + private long mStatusBarLongPressDowntime = -1L; private boolean mTouchSlopExceededBeforeDown; private float mOverExpansion; private CentralSurfaces mCentralSurfaces; @@ -374,8 +370,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private float mExpandedHeight = 0; /** The current squish amount for the predictive back animation */ private float mCurrentBackProgress = 0.0f; - @Deprecated - private KeyguardBottomAreaView mKeyguardBottomArea; private boolean mExpanding; private boolean mSplitShadeEnabled; /** The bottom padding reserved for elements of the keyguard measuring notifications. */ @@ -391,11 +385,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private KeyguardStatusBarViewController mKeyguardStatusBarViewController; private KeyguardStatusViewController mKeyguardStatusViewController; - private final LockIconViewController mLockIconViewController; private NotificationsQuickSettingsContainer mNotificationContainerParent; private final NotificationsQSContainerController mNotificationsQSContainerController; - private final Provider<KeyguardBottomAreaViewController> - mKeyguardBottomAreaViewControllerProvider; private boolean mAnimateNextPositionUpdate; private final ScreenOffAnimationController mScreenOffAnimationController; private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @@ -547,8 +538,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final NotificationListContainer mNotificationListContainer; private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; private final NPVCDownEventState.Buffer mLastDownEvents; - private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; - private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; private final KeyguardClockInteractor mKeyguardClockInteractor; private float mMinExpandHeight; private boolean mPanelUpdateWhenAnimatorEnds; @@ -624,8 +613,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final SplitShadeStateController mSplitShadeStateController; private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */); - private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = - () -> mKeyguardBottomArea.setVisibility(View.GONE); private final Runnable mHeadsUpExistenceChangedRunnable = () -> { setHeadsUpAnimatingAway(false); updateExpansionAndVisibility(); @@ -714,7 +701,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump MediaDataManager mediaDataManager, NotificationShadeDepthController notificationShadeDepthController, AmbientState ambientState, - LockIconViewController lockIconViewController, KeyguardMediaController keyguardMediaController, TapAgainViewController tapAgainViewController, NavigationModeController navigationModeController, @@ -730,15 +716,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ShadeRepository shadeRepository, Optional<SysUIUnfoldComponent> unfoldComponent, SysUiState sysUiState, - Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider, KeyguardUnlockAnimationController keyguardUnlockAnimationController, KeyguardIndicationController keyguardIndicationController, NotificationListContainer notificationListContainer, NotificationStackSizeCalculator notificationStackSizeCalculator, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, SystemClock systemClock, - KeyguardBottomAreaViewModel keyguardBottomAreaViewModel, - KeyguardBottomAreaInteractor keyguardBottomAreaInteractor, KeyguardClockInteractor keyguardClockInteractor, AlternateBouncerInteractor alternateBouncerInteractor, DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel, @@ -852,7 +835,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mNotificationListContainer = notificationListContainer; mNotificationStackSizeCalculator = notificationStackSizeCalculator; mNavigationBarController = navigationBarController; - mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider; mNotificationsQSContainerController.init(); mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; @@ -908,7 +890,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT); mConversationNotificationManager = conversationNotificationManager; mAuthController = authController; - mLockIconViewController = lockIconViewController; mScreenOffAnimationController = screenOffAnimationController; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mLastDownEvents = new NPVCDownEventState.Buffer(MAX_DOWN_EVENT_BUFFER_SIZE); @@ -930,16 +911,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump if (DEBUG_DRAWABLE) { mView.getOverlay().add(new DebugDrawable(this, mView, - mNotificationStackScrollLayoutController, mLockIconViewController, - mQsController)); + mNotificationStackScrollLayoutController, mQsController)); } mKeyguardUnfoldTransition = unfoldComponent.map( SysUIUnfoldComponent::getKeyguardUnfoldTransition); updateUserSwitcherFlags(); - mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel; - mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; mKeyguardClockInteractor = keyguardClockInteractor; KeyguardLongPressViewBinder.bind( mView.requireViewById(R.id.keyguard_long_press), @@ -1062,12 +1040,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mQsController.init(); mShadeHeadsUpTracker.addTrackingHeadsUpListener( mNotificationStackScrollLayoutController::setTrackingHeadsUp); - if (!KeyguardBottomAreaRefactor.isEnabled()) { - setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area)); - } - - initBottomArea(); - mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController); mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() { @Override @@ -1183,6 +1155,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump }, mMainDispatcher); } + if (com.android.systemui.Flags.bouncerUiRevamp()) { + collectFlow(mView, mKeyguardInteractor.primaryBouncerShowing, + this::handleBouncerShowingChanged); + } + // Ensures that flags are updated when an activity launches collectFlow(mView, mShadeAnimationInteractor.isLaunchingActivity(), @@ -1232,6 +1209,22 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mQsController.loadDimens(); } + private void handleBouncerShowingChanged(Boolean isBouncerShowing) { + if (!com.android.systemui.Flags.bouncerUiRevamp()) return; + + if (isBouncerShowing && isExpanded()) { + // Blur the shade much lesser than the background surface so that the surface is + // distinguishable from the background. + float shadeBlurEffect = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS / 3; + mView.setRenderEffect(RenderEffect.createBlurEffect( + shadeBlurEffect, + shadeBlurEffect, + Shader.TileMode.MIRROR)); + } else { + mView.setRenderEffect(null); + } + } + private void updateViewControllers( FrameLayout userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView) { @@ -1421,23 +1414,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump showKeyguardUserSwitcher /* enabled */); updateViewControllers(userAvatarView, keyguardUserSwitcherView); - - if (!KeyguardBottomAreaRefactor.isEnabled()) { - // Update keyguard bottom area - int index = mView.indexOfChild(mKeyguardBottomArea); - mView.removeView(mKeyguardBottomArea); - KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea; - KeyguardBottomAreaViewController keyguardBottomAreaViewController = - mKeyguardBottomAreaViewControllerProvider.get(); - if (smartspaceRelocateToBottom()) { - keyguardBottomAreaViewController.init(); - } - setKeyguardBottomArea(keyguardBottomAreaViewController.getView()); - mKeyguardBottomArea.initFrom(oldBottomArea); - mView.addView(mKeyguardBottomArea, index); - - initBottomArea(); - } mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(), mStatusBarStateController.getInterpolatedDozeAmount()); @@ -1462,10 +1438,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump false, mBarState); } - - if (!KeyguardBottomAreaRefactor.isEnabled()) { - setKeyguardBottomAreaVisibility(mBarState, false); - } } private void attachSplitShadeMediaPlayerContainer(FrameLayout container) { @@ -1475,22 +1447,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardMediaController.attachSplitShadeContainer(container); } - private void initBottomArea() { - if (!KeyguardBottomAreaRefactor.isEnabled()) { - mKeyguardBottomArea.init( - mKeyguardBottomAreaViewModel, - mFalsingManager, - mLockIconViewController, - stringResourceId -> - mKeyguardIndicationController.showTransientIndication(stringResourceId), - mVibratorHelper, - mActivityStarter); - - // Rebind (for now), as a new bottom area and indication area may have been created - mKeyguardViewConfigurator.bindIndicationArea(); - } - } - @VisibleForTesting void setMaxDisplayedNotifications(int maxAllowed) { mMaxAllowedKeyguardNotifications = maxAllowed; @@ -1528,11 +1484,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return mUnlockedScreenOffAnimationController.isAnimationPlaying(); } - @Deprecated - private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) { - mKeyguardBottomArea = keyguardBottomArea; - } - /** Sets a listener to be notified when the shade starts opening or finishes closing. */ public void setOpenCloseListener(OpenCloseListener openCloseListener) { SceneContainerFlag.assertInLegacyMode(); @@ -1660,10 +1611,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.setLockscreenClockY( mClockPositionAlgorithm.getExpandedPreferredClockY()); } - if (!(MigrateClocksToBlueprint.isEnabled() || KeyguardBottomAreaRefactor.isEnabled())) { - mKeyguardBottomAreaInteractor.setClockPosition( - mClockPositionResult.clockX, mClockPositionResult.clockY); - } boolean animate = !SceneContainerFlag.isEnabled() && mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); @@ -2382,25 +2329,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } - @Deprecated - private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) { - mKeyguardBottomArea.animate().cancel(); - if (goingToFullShade) { - mKeyguardBottomArea.animate().alpha(0f).setStartDelay( - mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration( - mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator( - Interpolators.ALPHA_OUT).withEndAction( - mAnimateKeyguardBottomAreaInvisibleEndRunnable).start(); - } else if (statusBarState == KEYGUARD || statusBarState == StatusBarState.SHADE_LOCKED) { - mKeyguardBottomArea.setVisibility(View.VISIBLE); - if (!mIsOcclusionTransitionRunning) { - mKeyguardBottomArea.setAlpha(1f); - } - } else { - mKeyguardBottomArea.setVisibility(View.GONE); - } - } - /** * 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 @@ -2755,12 +2683,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction()); alpha *= mBottomAreaShadeAlpha; - if (KeyguardBottomAreaRefactor.isEnabled()) { - mKeyguardInteractor.setAlpha(alpha); - } else { - mKeyguardBottomAreaInteractor.setAlpha(alpha); - } - mLockIconViewController.setAlpha(alpha); + mKeyguardInteractor.setAlpha(alpha); } private void onExpandingFinished() { @@ -2967,11 +2890,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateDozingVisibilities(boolean animate) { - if (KeyguardBottomAreaRefactor.isEnabled()) { - mKeyguardInteractor.setAnimateDozingTransitions(animate); - } else { - mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate); - } + mKeyguardInteractor.setAnimateDozingTransitions(animate); if (!mDozing && animate) { mKeyguardStatusBarViewController.animateKeyguardStatusBarIn(); } @@ -3212,11 +3131,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mDozing = dozing; // TODO (b/) make listeners for this mNotificationStackScrollLayoutController.setDozing(mDozing, animate); - if (KeyguardBottomAreaRefactor.isEnabled()) { - mKeyguardInteractor.setAnimateDozingTransitions(animate); - } else { - mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate); - } + mKeyguardInteractor.setAnimateDozingTransitions(animate); mKeyguardStatusBarViewController.setDozing(mDozing); mQsController.setDozing(mDozing); @@ -3267,7 +3182,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } public void dozeTimeTick() { - mLockIconViewController.dozeTimeTick(); if (!MigrateClocksToBlueprint.isEnabled()) { mKeyguardStatusViewController.dozeTimeTick(); } @@ -3795,7 +3709,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { mShadeLog.logEndMotionEvent("endMotionEvent called", forceCancel, false); mTrackingPointer = -1; - mStatusBarLongPressDowntime = 0L; + mStatusBarLongPressDowntime = -1L; mAmbientState.setSwipingUp(false); if ((isTracking() && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop || Math.abs(y - mInitialExpandY) > mTouchSlop @@ -4544,10 +4458,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mBarState); } - if (!KeyguardBottomAreaRefactor.isEnabled()) { - setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); - } - // TODO: maybe add a listener for barstate mBarState = statusBarState; mQsController.setBarState(statusBarState); @@ -4813,12 +4723,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump stackScroller.setMaxAlphaForKeyguard(alpha, "NPVC.setTransitionAlpha()"); } - if (KeyguardBottomAreaRefactor.isEnabled()) { - mKeyguardInteractor.setAlpha(alpha); - } else { - mKeyguardBottomAreaInteractor.setAlpha(alpha); - } - mLockIconViewController.setAlpha(alpha); + mKeyguardInteractor.setAlpha(alpha); if (mKeyguardQsUserSwitchController != null) { mKeyguardQsUserSwitchController.setAlpha(alpha); @@ -5199,7 +5104,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mUpdateFlingOnLayout = false; mMotionAborted = false; mDownTime = mSystemClock.uptimeMillis(); - mStatusBarLongPressDowntime = 0L; + mStatusBarLongPressDowntime = -1L; mTouchAboveFalsingThreshold = false; mCollapsedAndHeadsUpOnDown = isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index bf672be3c8d0..48bbb0407ee3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -169,6 +169,10 @@ public class NotificationShadeWindowView extends WindowRootView { public void onMovedToDisplay(int displayId, Configuration config) { super.onMovedToDisplay(displayId, config); ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); + ShadeTraceLogger.logOnMovedToDisplay(displayId, config); + if (mConfigurationForwarder != null) { + mConfigurationForwarder.dispatchOnMovedToDisplay(displayId, config); + } // When the window is moved we're only receiving a call to this method instead of the // onConfigurationChange itself. Let's just trigegr a normal config change. onConfigurationChanged(config); @@ -177,6 +181,7 @@ public class NotificationShadeWindowView extends WindowRootView { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + ShadeTraceLogger.logOnConfigChanged(newConfig); if (mConfigurationForwarder != null) { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); mConfigurationForwarder.onConfigurationChanged(newConfig); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index f2c39063c867..839d4596bb7c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -48,7 +48,6 @@ import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.flags.Flags; import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.KeyguardState; @@ -367,9 +366,7 @@ public class NotificationShadeWindowViewController implements Dumpable { mTouchActive = true; mTouchCancelled = false; mDownEvent = ev; - if (MigrateClocksToBlueprint.isEnabled()) { - mService.userActivity(); - } + mService.userActivity(); } else if (ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { mTouchActive = false; @@ -443,8 +440,7 @@ public class NotificationShadeWindowViewController implements Dumpable { float x = ev.getRawX(); float y = ev.getRawY(); if (mStatusBarViewController.touchIsWithinView(x, y)) { - if (!(MigrateClocksToBlueprint.isEnabled() - && mPrimaryBouncerInteractor.isBouncerShowing())) { + if (!mPrimaryBouncerInteractor.isBouncerShowing()) { if (mStatusBarWindowStateController.windowIsShowing()) { mIsTrackingBarGesture = true; return logDownDispatch(ev, "sending touch to status bar", @@ -453,7 +449,7 @@ public class NotificationShadeWindowViewController implements Dumpable { return logDownDispatch(ev, "hidden or hiding", true); } } else { - mShadeLogger.d("NSWVC: bouncer not showing"); + mShadeLogger.d("NSWVC: bouncer showing"); } } else { mShadeLogger.d("NSWVC: touch not within view"); @@ -511,34 +507,24 @@ public class NotificationShadeWindowViewController implements Dumpable { && !bouncerShowing && !mStatusBarStateController.isDozing()) { if (mDragDownHelper.isDragDownEnabled()) { - if (MigrateClocksToBlueprint.isEnabled()) { - // When on lockscreen, if the touch originates at the top of the screen - // go directly to QS and not the shade - if (mStatusBarStateController.getState() == KEYGUARD - && mQuickSettingsController.shouldQuickSettingsIntercept( - ev.getX(), ev.getY(), 0)) { - mShadeLogger.d("NSWVC: QS intercepted"); - return true; - } + // When on lockscreen, if the touch originates at the top of the screen go + // directly to QS and not the shade + if (mStatusBarStateController.getState() == KEYGUARD + && mQuickSettingsController.shouldQuickSettingsIntercept( + ev.getX(), ev.getY(), 0)) { + mShadeLogger.d("NSWVC: QS intercepted"); + return true; } // This handles drag down over lockscreen boolean result = mDragDownHelper.onInterceptTouchEvent(ev); - if (MigrateClocksToBlueprint.isEnabled()) { - if (result) { - mLastInterceptWasDragDownHelper = true; - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mShadeLogger.d("NSWVC: drag down helper intercepted"); - } - } else if (didNotificationPanelInterceptEvent(ev)) { - return true; - } - } else { - if (result) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mShadeLogger.d("NSWVC: drag down helper intercepted"); - } + if (result) { + mLastInterceptWasDragDownHelper = true; + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mShadeLogger.d("NSWVC: drag down helper intercepted"); } + } else if (didNotificationPanelInterceptEvent(ev)) { + return true; } return result; } else { @@ -547,12 +533,10 @@ public class NotificationShadeWindowViewController implements Dumpable { return true; } } - } else if (MigrateClocksToBlueprint.isEnabled()) { + } else if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) { // This final check handles swipes on HUNs and when Pulsing - if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) { - mShadeLogger.d("NSWVC: intercepted for HUN/PULSING"); - return true; - } + mShadeLogger.d("NSWVC: intercepted for HUN/PULSING"); + return true; } return false; } @@ -562,9 +546,6 @@ public class NotificationShadeWindowViewController implements Dumpable { MotionEvent cancellation = MotionEvent.obtain(ev); cancellation.setAction(MotionEvent.ACTION_CANCEL); mStackScrollLayout.onInterceptTouchEvent(cancellation); - if (!MigrateClocksToBlueprint.isEnabled()) { - mShadeViewController.handleExternalInterceptTouch(cancellation); - } cancellation.recycle(); } @@ -574,22 +555,12 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarStateController.isDozing()) { handled = !mDozeServiceHost.isPulsing(); } - if (MigrateClocksToBlueprint.isEnabled()) { - if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { - // we still want to finish our drag down gesture when locking the screen - handled |= mDragDownHelper.onTouchEvent(ev) || handled; - } - if (!handled && mShadeViewController.handleExternalTouch(ev)) { - return true; - } - } else { - if (mDragDownHelper.isDragDownEnabled() - || mDragDownHelper.isDraggingDown()) { - // we still want to finish our drag down gesture when locking the screen - return mDragDownHelper.onTouchEvent(ev) || handled; - } else { - return handled; - } + if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { + // we still want to finish our drag down gesture when locking the screen + handled |= mDragDownHelper.onTouchEvent(ev) || handled; + } + if (!handled && mShadeViewController.handleExternalTouch(ev)) { + return true; } return handled; } @@ -673,14 +644,12 @@ public class NotificationShadeWindowViewController implements Dumpable { } private boolean didNotificationPanelInterceptEvent(MotionEvent ev) { - if (MigrateClocksToBlueprint.isEnabled()) { - // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need - // to also ask NotificationPanelViewController directly, in order to process swipe up - // events originating from notifications - if (mShadeViewController.handleExternalInterceptTouch(ev)) { - mShadeLogger.d("NSWVC: NPVC intercepted"); - return true; - } + // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need to + // also ask NotificationPanelViewController directly, in order to process swipe up events + // originating from notifications + if (mShadeViewController.handleExternalInterceptTouch(ev)) { + mShadeLogger.d("NSWVC: NPVC intercepted"); + return true; } return false; @@ -707,9 +676,7 @@ public class NotificationShadeWindowViewController implements Dumpable { if (!SceneContainerFlag.isEnabled()) { mAmbientState.setSwipingUp(false); } - if (MigrateClocksToBlueprint.isEnabled()) { - mDragDownHelper.stopDragging(); - } + mDragDownHelper.stopDragging(); } private void setBrightnessMirrorShowingForDepth(boolean showing) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 207439e1f374..58111576574e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -21,7 +21,6 @@ import android.view.ViewGroup import android.view.WindowInsets import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START @@ -32,7 +31,6 @@ import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.fragments.FragmentService -import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.plugins.qs.QS @@ -275,7 +273,6 @@ constructor( constraintSet.clone(mView) setKeyguardStatusViewConstraints(constraintSet) setQsConstraints(constraintSet) - setNotificationsConstraints(constraintSet) setLargeScreenShadeHeaderConstraints(constraintSet) mView.applyConstraints(constraintSet) } @@ -288,21 +285,6 @@ constructor( } } - private fun setNotificationsConstraints(constraintSet: ConstraintSet) { - if (MigrateClocksToBlueprint.isEnabled) { - return - } - val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID - val nsslId = R.id.notification_stack_scroller - constraintSet.apply { - connect(nsslId, START, startConstraintId, START) - setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal) - setMargin(nsslId, END, panelMarginHorizontal) - setMargin(nsslId, TOP, topMargin) - setMargin(nsslId, BOTTOM, notificationsBottomMargin) - } - } - private fun setQsConstraints(constraintSet: ConstraintSet) { val endConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java index 13330553b2de..000a666bac0d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java @@ -21,7 +21,6 @@ import static androidx.constraintlayout.core.widgets.Optimizer.OPTIMIZATION_GRAP 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.MotionEvent; @@ -33,13 +32,10 @@ import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.plugins.qs.QS; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AboveShelfObserver; -import java.util.ArrayList; -import java.util.Comparator; import java.util.function.Consumer; /** @@ -50,11 +46,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout private View mQsFrame; private View mStackScroller; - private View mKeyguardStatusBar; - private final ArrayList<View> mDrawingOrderedChildren = new ArrayList<>(); - private final ArrayList<View> mLayoutDrawingOrder = new ArrayList<>(); - private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild); private Consumer<WindowInsets> mInsetsChangedListener = insets -> {}; private Consumer<QS> mQSFragmentAttachedListener = qs -> {}; private QS mQs; @@ -80,7 +72,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout protected void onFinishInflate() { super.onFinishInflate(); mQsFrame = findViewById(R.id.qs_frame); - mKeyguardStatusBar = findViewById(R.id.keyguard_header); } void setStackScroller(View stackScroller) { @@ -160,46 +151,11 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } @Override - protected void dispatchDraw(Canvas canvas) { - mDrawingOrderedChildren.clear(); - mLayoutDrawingOrder.clear(); - if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) { - mDrawingOrderedChildren.add(mKeyguardStatusBar); - mLayoutDrawingOrder.add(mKeyguardStatusBar); - } - if (mQsFrame.getVisibility() == View.VISIBLE) { - mDrawingOrderedChildren.add(mQsFrame); - mLayoutDrawingOrder.add(mQsFrame); - } - if (mStackScroller.getVisibility() == View.VISIBLE) { - mDrawingOrderedChildren.add(mStackScroller); - mLayoutDrawingOrder.add(mStackScroller); - } - - // Let's now find the order that the view has when drawing regularly by sorting - mLayoutDrawingOrder.sort(mIndexComparator); - super.dispatchDraw(canvas); - } - - @Override public boolean dispatchTouchEvent(MotionEvent ev) { return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev, super.dispatchTouchEvent(ev)); } - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - if (MigrateClocksToBlueprint.isEnabled()) { - return super.drawChild(canvas, child, drawingTime); - } - int layoutIndex = mLayoutDrawingOrder.indexOf(child); - if (layoutIndex >= 0) { - return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime); - } else { - return super.drawChild(canvas, child, drawingTime); - } - } - public void applyConstraints(ConstraintSet constraintSet) { constraintSet.applyTo(this); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index 0df2299eb8dd..4fb43fdcfdae 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -68,7 +68,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.media.controls.domain.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; @@ -1828,16 +1827,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum "onQsIntercept: down action, QS partially expanded/collapsed"); return true; } - // TODO (b/265193930): remove dependency on NPVC - if (mPanelViewControllerLazy.get().isKeyguardShowing() - && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { - // Dragging down on the lockscreen statusbar should prohibit other interactions - // immediately, otherwise we'll wait on the touchslop. This is to allow - // dragging down to expanded quick settings directly on the lockscreen. - if (!MigrateClocksToBlueprint.isEnabled()) { - mPanelView.getParent().requestDisallowInterceptTouchEvent(true); - } - } if (mExpansionAnimator != null) { mInitialHeightOnTouch = mExpansionHeight; mShadeLog.logMotionEvent(event, @@ -1879,9 +1868,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept( mInitialTouchX, mInitialTouchY, h)) { - if (!MigrateClocksToBlueprint.isEnabled()) { - mPanelView.getParent().requestDisallowInterceptTouchEvent(true); - } mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); setTracking(true); traceQsJank(true, false); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt index d31868ca0217..61b9f0819f56 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt @@ -35,6 +35,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractorImpl import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl import com.android.systemui.shade.display.ShadeDisplayPolicyModule @@ -216,6 +218,25 @@ object ShadeDisplayAwareModule { } @Provides + @SysUISingleton + fun provideShadeDialogContextInteractor( + impl: ShadeDialogContextInteractorImpl + ): ShadeDialogContextInteractor = impl + + @Provides + @IntoMap + @ClassKey(ShadeDialogContextInteractor::class) + fun provideShadeDialogContextInteractorCoreStartable( + impl: Provider<ShadeDialogContextInteractorImpl> + ): CoreStartable { + return if (ShadeWindowGoesAround.isEnabled) { + impl.get() + } else { + CoreStartable.NOP + } + } + + @Provides @IntoMap @ClassKey(ShadePrimaryDisplayCommand::class) fun provideShadePrimaryDisplayCommand( diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt new file mode 100644 index 000000000000..45161331c0d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.content.res.Configuration +import android.os.Trace +import com.android.app.tracing.TraceUtils.traceAsync + +/** + * Centralized logging for shade-related events to a dedicated Perfetto track. + * + * Used by shade components to log events to a track named [TAG]. This consolidates shade-specific + * events into a single track for easier analysis in Perfetto, rather than scattering them across + * various threads' logs. + */ +object ShadeTraceLogger { + private const val TAG = "ShadeTraceLogger" + + @JvmStatic + fun logOnMovedToDisplay(displayId: Int, config: Configuration) { + if (!Trace.isEnabled()) return + Trace.instantForTrack( + Trace.TRACE_TAG_APP, + TAG, + "onMovedToDisplay(displayId=$displayId, dpi=" + config.densityDpi + ")", + ) + } + + @JvmStatic + fun logOnConfigChanged(config: Configuration) { + if (!Trace.isEnabled()) return + Trace.instantForTrack( + Trace.TRACE_TAG_APP, + TAG, + "onConfigurationChanged(dpi=" + config.densityDpi + ")", + ) + } + + fun logMoveShadeWindowTo(displayId: Int) { + if (!Trace.isEnabled()) return + Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "moveShadeWindowTo(displayId=$displayId)") + } + + fun traceReparenting(r: () -> Unit) { + traceAsync(TAG, { "reparenting" }) { r() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 8937ce33cd38..e19112047d2a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -50,7 +50,6 @@ import com.android.systemui.statusbar.NotificationInsetsController import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer -import com.android.systemui.statusbar.phone.KeyguardBottomAreaView import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.TapAgainView @@ -145,20 +144,6 @@ abstract class ShadeViewProviderModule { return notificationShadeWindowView.requireViewById(R.id.notification_panel) } - /** - * Constructs a new, unattached [KeyguardBottomAreaView]. - * - * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it - */ - @Provides - fun providesKeyguardBottomAreaView( - npv: NotificationPanelView, - @ShadeDisplayAware layoutInflater: LayoutInflater, - ): KeyguardBottomAreaView { - return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false) - as KeyguardBottomAreaView - } - @Provides @SysUISingleton fun providesLightRevealScrim( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt index 768952d1ee77..455370c726a2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.domain.interactor +package com.android.systemui.shade.domain.interactor -import com.android.systemui.kosmos.Kosmos -import org.mockito.kotlin.mock +import android.content.Context -val Kosmos.notificationAlertsInteractor by Kosmos.Fixture { mock<NotificationAlertsInteractor>() } +/** Fake context repository that always returns the same context. */ +class FakeShadeDialogContextInteractor(override val context: Context) : + ShadeDialogContextInteractor diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt new file mode 100644 index 000000000000..201dc0339a0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.domain.interactor + +import android.content.Context +import android.util.Log +import android.view.Display +import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL +import com.android.app.tracing.coroutines.launchTraced +import com.android.app.tracing.traceSection +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.shade.data.repository.ShadeDisplaysRepository +import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround +import javax.inject.Inject +import javax.inject.Provider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filter + +/** Provides the correct context to show dialogs on the shade window, whenever it moves. */ +interface ShadeDialogContextInteractor { + /** Context usable to create dialogs on the notification shade display. */ + val context: Context +} + +@SysUISingleton +class ShadeDialogContextInteractorImpl +@Inject +constructor( + @Main private val defaultContext: Context, + private val displayWindowPropertyRepository: Provider<DisplayWindowPropertiesRepository>, + private val shadeDisplaysRepository: ShadeDisplaysRepository, + @Background private val bgScope: CoroutineScope, +) : CoreStartable, ShadeDialogContextInteractor { + + override fun start() { + if (ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()) return + bgScope.launchTraced(TAG) { + shadeDisplaysRepository.displayId + // No need for default display pre-warming. + .filter { it != Display.DEFAULT_DISPLAY } + .collectLatest { displayId -> + // Prewarms the context in the background every time the display changes. + // In this way, there will be no main thread delays when a dialog is shown. + getContextOrDefault(displayId) + } + } + } + + override val context: Context + get() { + if (!ShadeWindowGoesAround.isEnabled) { + return defaultContext + } + val displayId = shadeDisplaysRepository.displayId.value + return getContextOrDefault(displayId) + } + + private fun getContextOrDefault(displayId: Int): Context { + return try { + traceSection({ "Getting dialog context for displayId=$displayId" }) { + displayWindowPropertyRepository.get().get(displayId, DIALOG_WINDOW_TYPE).context + } + } catch (e: Exception) { + // This can happen if the display was disconnected in the meantime. + Log.e( + TAG, + "Couldn't get dialog context for displayId=$displayId. Returning default one", + e, + ) + defaultContext + } + } + + private companion object { + const val TAG = "ShadeDialogContextRepo" + const val DIALOG_WINDOW_TYPE = TYPE_STATUS_BAR_SUB_PANEL + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt index 08c03e28d596..8d536accaf76 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt @@ -27,6 +27,8 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo +import com.android.systemui.shade.ShadeTraceLogger.traceReparenting import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround import com.android.systemui.util.kotlin.getOrNull @@ -68,6 +70,7 @@ constructor( /** Tries to move the shade. If anything wrong happens, fails gracefully without crashing. */ private suspend fun moveShadeWindowTo(destinationId: Int) { Log.d(TAG, "Trying to move shade window to display with id $destinationId") + logMoveShadeWindowTo(destinationId) // Why using the shade context here instead of the view's Display? // The context's display is updated before the view one, so it is a better indicator of // which display the shade is supposed to be at. The View display is updated after the first @@ -83,7 +86,9 @@ constructor( return } try { - withContext(mainThreadContext) { reparentToDisplayId(id = destinationId) } + withContext(mainThreadContext) { + traceReparenting { reparentToDisplayId(id = destinationId) } + } } catch (e: IllegalStateException) { Log.e( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt index 50b5607f1955..2d7476c0433c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade.domain.interactor -import com.android.keyguard.LockIconViewController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.shared.model.KeyguardState @@ -38,7 +37,6 @@ constructor( @Background private val backgroundScope: CoroutineScope, private val shadeInteractor: ShadeInteractor, private val sceneInteractor: SceneInteractor, - private val lockIconViewController: LockIconViewController, shadeRepository: ShadeRepository, ) : ShadeLockscreenInteractor { @@ -61,7 +59,7 @@ constructor( } override fun dozeTimeTick() { - lockIconViewController.dozeTimeTick() + // TODO("b/383591086") Implement replacement or delete } @Deprecated("Not supported by scenes") diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt index ea4e065be84b..3a5245d9b9a5 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt @@ -65,8 +65,8 @@ abstract class SmartspaceModule { @Binds @Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) abstract fun bindSmartspacePrecondition( - lockscreenPrecondition: LockscreenPrecondition? - ): SmartspacePrecondition? + lockscreenPrecondition: LockscreenPrecondition + ): SmartspacePrecondition @BindsOptionalOf @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index b2ca33a4aecf..a7ad46296e08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -44,13 +44,11 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.logging.UiEventLogger; -import com.android.keyguard.KeyguardClockSwitch; import com.android.systemui.DejankUtils; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor; import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -136,7 +134,6 @@ public class StatusBarStateControllerImpl implements private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE]; // These views are used by InteractionJankMonitor to get callback from HWUI. private View mView; - private KeyguardClockSwitch mClockSwitchView; /** * If any of the system bars is hidden. @@ -426,7 +423,6 @@ public class StatusBarStateControllerImpl implements if ((mView == null || !mView.isAttachedToWindow()) && (view != null && view.isAttachedToWindow())) { mView = view; - mClockSwitchView = view.findViewById(R.id.keyguard_clock_container); } mDozeAmountTarget = dozeAmount; if (animated) { @@ -511,16 +507,7 @@ public class StatusBarStateControllerImpl implements /** Returns the id of the currently rendering clock */ public String getClockId() { - if (MigrateClocksToBlueprint.isEnabled()) { - return mKeyguardClockInteractorLazy.get().getRenderedClockId(); - } - - if (mClockSwitchView == null) { - Log.e(TAG, "Clock container was missing"); - return KeyguardClockSwitch.MISSING_CLOCK_ID; - } - - return mClockSwitchView.getClockId(); + return mKeyguardClockInteractorLazy.get().getRenderedClockId(); } private void beginInteractionJankMonitor() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt index 229cef910c6e..b3dbf299e7cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor -import android.media.projection.StopReason import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer @@ -66,9 +65,7 @@ constructor( /** Stops the currently active MediaRouter cast. */ fun stopCasting() { - activeCastDevice.value?.let { - mediaRouterRepository.stopCasting(it, StopReason.STOP_PRIVACY_CHIP) - } + activeCastDevice.value?.let { mediaRouterRepository.stopCasting(it) } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt index bbecde830a9f..dff6f567f6c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt @@ -137,7 +137,7 @@ constructor( } } - return NotificationChipModel(key, statusBarChipIconView, whenTime, promotedContent) + return NotificationChipModel(key, statusBarChipIconView, promotedContent) } @AssistedFactory diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt index 9f0638baec83..c6759da304bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt @@ -23,7 +23,5 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote data class NotificationChipModel( val key: String, val statusBarChipIconView: StatusBarIconView?, - // TODO(b/364653005): Use [PromotedNotificationContentModel.time] instead of a custom field. - val whenTime: Long, val promotedContent: PromotedNotificationContentModel, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index 2d16f3b51ed1..66af275bc702 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.notification.headsup.PinnedStatus +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -82,21 +83,51 @@ constructor( ) } } - return if (headsUpState == PinnedStatus.PinnedByUser) { + + if (headsUpState == PinnedStatus.PinnedByUser) { // If the user tapped the chip to show the HUN, we want to just show the icon because // the HUN will show the rest of the information. - OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) - } else { - OngoingActivityChipModel.Shown.ShortTimeDelta( + return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + } + + if (this.promotedContent.shortCriticalText != null) { + return OngoingActivityChipModel.Shown.Text( icon, colors, - time = this.whenTime, + this.promotedContent.shortCriticalText, onClickListener, ) } - // TODO(b/364653005): Use Notification.showWhen to determine if we should show the time. - // TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`. - // TODO(b/364653005): If the app that posted the notification is in the foreground, don't - // show that app's chip. + + if (this.promotedContent.time == null) { + return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + } + when (this.promotedContent.time.mode) { + PromotedNotificationContentModel.When.Mode.BasicTime -> { + return OngoingActivityChipModel.Shown.ShortTimeDelta( + icon, + colors, + time = this.promotedContent.time.time, + onClickListener, + ) + } + PromotedNotificationContentModel.When.Mode.CountUp -> { + return OngoingActivityChipModel.Shown.Timer( + icon, + colors, + startTimeMs = this.promotedContent.time.time, + onClickListener, + ) + } + PromotedNotificationContentModel.When.Mode.CountDown -> { + // TODO(b/364653005): Support CountDown. + return OngoingActivityChipModel.Shown.Timer( + icon, + colors, + startTimeMs = this.promotedContent.time.time, + onClickListener, + ) + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt index 0b5e669b5fca..f5952f4804fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.chips.screenrecord.domain.interactor -import android.media.projection.StopReason import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -141,7 +140,7 @@ constructor( /** Stops the recording. */ fun stopRecording() { - scope.launch { screenRecordRepository.stopRecording(StopReason.STOP_PRIVACY_CHIP) } + scope.launch { screenRecordRepository.stopRecording() } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt index cf69d401df60..0c4c1a71ccc7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt @@ -310,9 +310,13 @@ object OngoingActivityChipBinder { private fun View.setBackgroundPaddingForEmbeddedPaddingIcon() { val sidePadding = - context.resources.getDimensionPixelSize( - R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon - ) + if (StatusBarNotifChips.isEnabled) { + 0 + } else { + context.resources.getDimensionPixelSize( + R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon + ) + } setPaddingRelative(sidePadding, paddingTop, sidePadding, paddingBottom) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index 2dce4e38b803..18217d786cd4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -116,7 +116,8 @@ sealed class OngoingActivityChipModel { override val colors: ColorsModel, // TODO(b/361346412): Enforce a max length requirement? val text: String, - ) : Shown(icon, colors, onClickListener = null) { + override val onClickListener: View.OnClickListener? = null, + ) : Shown(icon, colors, onClickListener) { override val logName = "Shown.Text" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 8a1371f1c415..aa010cf63d5b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -62,6 +62,7 @@ import com.android.systemui.statusbar.notification.data.NotificationDataLayerMod import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule; import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor; import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.icon.ConversationIconManager; import com.android.systemui.statusbar.notification.icon.IconManager; import com.android.systemui.statusbar.notification.init.NotificationsController; @@ -78,8 +79,7 @@ import com.android.systemui.statusbar.notification.logging.NotificationPanelLogg import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl; import com.android.systemui.statusbar.notification.logging.dagger.NotificationsLogModule; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger; -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider; +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl; import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel; import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory; import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl; @@ -92,7 +92,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModesCleanupStartable; import dagger.Binds; @@ -105,8 +104,6 @@ import kotlin.coroutines.CoroutineContext; import kotlinx.coroutines.CoroutineScope; -import java.util.Optional; - import javax.inject.Provider; /** @@ -315,21 +312,17 @@ public interface NotificationsModule { @ClassKey(ZenModesCleanupStartable.class) CoreStartable bindsZenModesCleanup(ZenModesCleanupStartable zenModesCleanup); - /** - * Provides {@link - * com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor} if - * one of the relevant feature flags is enabled. - */ + /** Provides the default implementation of {@link PromotedNotificationContentExtractor} if at + * least one of the relevant feature flags is enabled, or an implementation that always returns + * null if none are enabled. */ @Provides @SysUISingleton - static Optional<PromotedNotificationContentExtractor> - providePromotedNotificationContentExtractor( - PromotedNotificationsProvider provider, Context context, - PromotedNotificationLogger logger) { + static PromotedNotificationContentExtractor providesPromotedNotificationContentExtractor( + Provider<PromotedNotificationContentExtractorImpl> implProvider) { if (PromotedNotificationContentModel.featureFlagEnabled()) { - return Optional.of(new PromotedNotificationContentExtractor(provider, context, logger)); + return implProvider.get(); } else { - return Optional.empty(); + return (entry, recoveredBuilder) -> null; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java index 6756077e5444..d02e17cab534 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java @@ -1299,7 +1299,6 @@ public class HeadsUpManagerImpl } private NotificationEntry requireEntry() { - /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode(); return Objects.requireNonNull(mEntry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt index e122ca888f45..4e9e3336b86f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt @@ -34,15 +34,22 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When import javax.inject.Inject +interface PromotedNotificationContentExtractor { + fun extractContent( + entry: NotificationEntry, + recoveredBuilder: Notification.Builder, + ): PromotedNotificationContentModel? +} + @SysUISingleton -class PromotedNotificationContentExtractor +class PromotedNotificationContentExtractorImpl @Inject constructor( private val promotedNotificationsProvider: PromotedNotificationsProvider, @ShadeDisplayAware private val context: Context, private val logger: PromotedNotificationLogger, -) { - fun extractContent( +) : PromotedNotificationContentExtractor { + override fun extractContent( entry: NotificationEntry, recoveredBuilder: Notification.Builder, ): PromotedNotificationContentModel? { @@ -71,6 +78,7 @@ constructor( contentBuilder.appName = notification.loadHeaderAppName(context) contentBuilder.subText = notification.subText() contentBuilder.time = notification.extractWhen() + contentBuilder.shortCriticalText = notification.shortCriticalText() contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs contentBuilder.profileBadgeResId = null // TODO contentBuilder.title = notification.title() @@ -97,6 +105,13 @@ private fun Notification.text(): CharSequence? = extras?.getCharSequence(EXTRA_T private fun Notification.subText(): String? = extras?.getString(EXTRA_SUB_TEXT) +private fun Notification.shortCriticalText(): String? { + if (!android.app.Flags.apiRichOngoing()) { + return null + } + return this.shortCriticalText +} + private fun Notification.chronometerCountDown(): Boolean = extras?.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, /* defaultValue= */ false) ?: false @@ -107,7 +122,7 @@ private fun Notification.extractWhen(): When? { val countDown = chronometerCountDown() return when { - showsTime -> When(time, When.Mode.Absolute) + showsTime -> When(time, When.Mode.BasicTime) showsChronometer -> When(time, if (countDown) When.Mode.CountDown else When.Mode.CountUp) else -> null } @@ -161,5 +176,5 @@ private fun CallStyle.extractContent(contentBuilder: PromotedNotificationContent private fun ProgressStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) { // TODO: Create NotificationProgressModel.toSkeleton, or something similar. - contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0x00000000) + contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0xff000000.toInt()) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt index 0af40437828e..fe2dabe1ba8a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -34,6 +34,11 @@ data class PromotedNotificationContentModel( val skeletonSmallIcon: Icon?, // TODO(b/377568176): Make into an IconModel. val appName: CharSequence?, val subText: CharSequence?, + val shortCriticalText: String?, + /** + * The timestamp associated with the notification. Null if the timestamp should not be + * displayed. + */ val time: When?, val lastAudiblyAlertedMs: Long, @DrawableRes val profileBadgeResId: Int?, @@ -57,6 +62,7 @@ data class PromotedNotificationContentModel( var appName: CharSequence? = null var subText: CharSequence? = null var time: When? = null + var shortCriticalText: String? = null var lastAudiblyAlertedMs: Long = 0L @DrawableRes var profileBadgeResId: Int? = null var title: CharSequence? = null @@ -80,6 +86,7 @@ data class PromotedNotificationContentModel( skeletonSmallIcon = skeletonSmallIcon, appName = appName, subText = subText, + shortCriticalText = shortCriticalText, time = time, lastAudiblyAlertedMs = lastAudiblyAlertedMs, profileBadgeResId = profileBadgeResId, @@ -100,8 +107,11 @@ data class PromotedNotificationContentModel( data class When(val time: Long, val mode: Mode) { /** The mode used to display a notification's `when` value. */ enum class Mode { - Absolute, + /** No custom mode requested by the notification. */ + BasicTime, + /** Show the notification's time as a chronometer that counts down to [time]. */ CountDown, + /** Show the notification's time as a chronometer that counts up from [time]. */ CountUp, } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 6e05e8e8b80e..6eeb80d45211 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -921,7 +921,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder logger.logAsyncTaskProgress(entry, "finishing"); if (PromotedNotificationContentModel.featureFlagEnabled()) { - entry.setPromotedNotificationContentModel(result.mExtractedPromotedNotificationContent); + entry.setPromotedNotificationContentModel(result.mPromotedContent); } boolean setRepliesAndActions = true; @@ -1292,10 +1292,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (PromotedNotificationContentModel.featureFlagEnabled()) { mLogger.logAsyncTaskProgress(mEntry, "extracting promoted notification content"); - result.mExtractedPromotedNotificationContent = mPromotedNotificationContentExtractor - .extractContent(mEntry, recoveredBuilder); + final PromotedNotificationContentModel promotedContent = + mPromotedNotificationContentExtractor.extractContent(mEntry, + recoveredBuilder); mLogger.logAsyncTaskProgress(mEntry, "extracted promoted notification content: " - + result.mExtractedPromotedNotificationContent); + + promotedContent); + + result.mPromotedContent = promotedContent; } mLogger.logAsyncTaskProgress(mEntry, @@ -1399,7 +1402,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder @VisibleForTesting static class InflationProgress { - PromotedNotificationContentModel mExtractedPromotedNotificationContent; + PromotedNotificationContentModel mPromotedContent; private RemoteViews newContentView; private RemoteViews newHeadsUpView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index c7d80e9d03ce..7dcb2de57e56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row import android.annotation.SuppressLint -import android.app.Flags import android.app.Notification import android.content.Context import android.content.ContextWrapper @@ -591,7 +590,7 @@ constructor( @VisibleForTesting val packageContext: Context, val remoteViews: NewRemoteViews, val contentModel: NotificationContentModel, - val extractedPromotedNotificationContentModel: PromotedNotificationContentModel?, + val promotedContent: PromotedNotificationContentModel?, ) { var inflatedContentView: View? = null @@ -683,16 +682,15 @@ constructor( promotedNotificationContentExtractor: PromotedNotificationContentExtractor, logger: NotificationRowContentBinderLogger, ): InflationProgress { - val promoted = + val promotedContent = if (PromotedNotificationContentModel.featureFlagEnabled()) { logger.logAsyncTaskProgress(entry, "extracting promoted notification content") - val extracted = - promotedNotificationContentExtractor.extractContent(entry, builder) - logger.logAsyncTaskProgress( - entry, - "extracted promoted notification content: {extracted}", - ) - extracted + promotedNotificationContentExtractor.extractContent(entry, builder).also { + logger.logAsyncTaskProgress( + entry, + "extracted promoted notification content: $it", + ) + } } else { null } @@ -759,7 +757,7 @@ constructor( packageContext = packageContext, remoteViews = remoteViews, contentModel = contentModel, - extractedPromotedNotificationContentModel = promoted, + promotedContent = promotedContent, ) } @@ -1420,8 +1418,7 @@ constructor( entry.setContentModel(result.contentModel) if (PromotedNotificationContentModel.featureFlagEnabled()) { - entry.promotedNotificationContentModel = - result.extractedPromotedNotificationContentModel + entry.promotedNotificationContentModel = result.promotedContent } result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 7c9d850eaf07..38a70359e816 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -3729,14 +3729,6 @@ public class NotificationStackScrollLayout // Only when scene container is enabled, mark that we are being dragged so that we start // dispatching the rest of the gesture to scene container. - void startOverscrollAfterExpanding() { - if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; - getExpandHelper().finishExpanding(); - setIsBeingDragged(true); - } - - // Only when scene container is enabled, mark that we are being dragged so that we start - // dispatching the rest of the gesture to scene container. void startDraggingOnHun() { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; setIsBeingDragged(true); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 245b1d29fb79..a33a9ed2df75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -2203,11 +2203,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { expandingNotification = mView.isExpandingNotification(); if (mView.getExpandedInThisMotion() && !expandingNotification && wasExpandingBefore && !mView.getDisallowScrollingInThisMotion()) { - // We need to dispatch the overscroll differently when Scene Container is on, - // since NSSL no longer controls its own scroll. + // Finish expansion here, as this gesture will be marked to be sent to + // scene container if (SceneContainerFlag.isEnabled() && !isCancelOrUp) { - mView.startOverscrollAfterExpanding(); - return true; + expandHelper.finishExpanding(); } else { mView.dispatchDownEventToScroller(ev); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index f8f29ff59154..b81c71ebe19b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -563,7 +563,7 @@ constructor( lockscreenToGoneTransitionViewModel.notificationAlpha(viewState), lockscreenToOccludedTransitionViewModel.lockscreenAlpha, lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha, - alternateBouncerToPrimaryBouncerTransitionViewModel.lockscreenAlpha, + alternateBouncerToPrimaryBouncerTransitionViewModel.notificationAlpha, occludedToAodTransitionViewModel.lockscreenAlpha, occludedToGoneTransitionViewModel.notificationAlpha(viewState), occludedToLockscreenTransitionViewModel.lockscreenAlpha, 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 7bea4800f7fc..6dc25aa5144f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -21,9 +21,10 @@ import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO; +import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; -import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; -import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; import static androidx.lifecycle.Lifecycle.State.RESUMED; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; @@ -207,6 +208,7 @@ import com.android.systemui.statusbar.data.repository.StatusBarModeRepositorySto import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -221,7 +223,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController.Configurati import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; @@ -2514,12 +2515,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { * should update only the status bar components. */ private void setBouncerShowingForStatusBarComponents(boolean bouncerShowing) { - int importance = bouncerShowing - ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : IMPORTANT_FOR_ACCESSIBILITY_AUTO; if (!StatusBarConnectedDisplays.isEnabled() && mPhoneStatusBarViewController != null) { + int importance = bouncerShowing + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_AUTO; mPhoneStatusBarViewController.setImportantForAccessibility(importance); } + int importance = bouncerShowing + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_NO; mShadeSurface.setImportantForAccessibility(importance); mShadeSurface.setBouncerShowing(bouncerShowing); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt index 858cac111525..9c7af181284e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt @@ -65,6 +65,13 @@ constructor(@Assisted private val context: Context) : listeners.filterForEach({ this.listeners.contains(it) }) { it.onThemeChanged() } } + override fun dispatchOnMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration) { + val listeners = synchronized(this.listeners) { ArrayList(this.listeners) } + listeners.filterForEach({ this.listeners.contains(it) }) { + it.onMovedToDisplay(newDisplayId, newConfiguration) + } + } + override fun onConfigurationChanged(newConfig: Configuration) { // Avoid concurrent modification exception val listeners = synchronized(this.listeners) { ArrayList(this.listeners) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt index 3fd46fc484a9..537e3e1893b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt @@ -28,4 +28,13 @@ import android.content.res.Configuration interface ConfigurationForwarder { /** Should be called when a new configuration is received. */ fun onConfigurationChanged(newConfiguration: Configuration) + + /** + * Should be called when the view associated to this configuration forwarded moved to another + * display, usually as a consequence of [View.onMovedToDisplay]. + * + * For the default configuration forwarder (associated with the global configuration) this is + * never expected to be called. + */ + fun dispatchOnMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt index eadb7f5a1684..2368824311f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt @@ -22,14 +22,7 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.StringRes -import com.android.keyguard.LockIconViewController -import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder -import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind -import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R -import com.android.systemui.statusbar.VibratorHelper /** * Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI @@ -51,124 +44,7 @@ constructor( defStyleAttr, defStyleRes, ) { - - @Deprecated("Deprecated as part of b/278057014") - interface MessageDisplayer { - fun display(@StringRes stringResourceId: Int) - } - - private var ambientIndicationArea: View? = null - private var keyguardIndicationArea: View? = null - private var binding: KeyguardBottomAreaViewBinder.Binding? = null - private var lockIconViewController: LockIconViewController? = null - private var isLockscreenLandscapeEnabled: Boolean = false - - /** Initializes the view. */ - @Deprecated("Deprecated as part of b/278057014") - fun init( - viewModel: KeyguardBottomAreaViewModel, - falsingManager: FalsingManager? = null, - lockIconViewController: LockIconViewController? = null, - messageDisplayer: MessageDisplayer? = null, - vibratorHelper: VibratorHelper? = null, - activityStarter: ActivityStarter? = null, - ) { - binding?.destroy() - binding = - bind( - this, - viewModel, - falsingManager, - vibratorHelper, - activityStarter, - ) { - messageDisplayer?.display(it) - } - this.lockIconViewController = lockIconViewController - } - - /** - * Initializes this instance of [KeyguardBottomAreaView] based on the given instance of another - * [KeyguardBottomAreaView] - */ - @Deprecated("Deprecated as part of b/278057014") - fun initFrom(oldBottomArea: KeyguardBottomAreaView) { - // if it exists, continue to use the original ambient indication container - // instead of the newly inflated one - ambientIndicationArea?.let { nonNullAmbientIndicationArea -> - // remove old ambient indication from its parent - val originalAmbientIndicationView = - oldBottomArea.requireViewById<View>(R.id.ambient_indication_container) - (originalAmbientIndicationView.parent as ViewGroup).removeView( - originalAmbientIndicationView - ) - - // remove current ambient indication from its parent (discard) - val ambientIndicationParent = nonNullAmbientIndicationArea.parent as ViewGroup - val ambientIndicationIndex = - ambientIndicationParent.indexOfChild(nonNullAmbientIndicationArea) - ambientIndicationParent.removeView(nonNullAmbientIndicationArea) - - // add the old ambient indication to this view - ambientIndicationParent.addView(originalAmbientIndicationView, ambientIndicationIndex) - ambientIndicationArea = originalAmbientIndicationView - } - } - - fun setIsLockscreenLandscapeEnabled(isLockscreenLandscapeEnabled: Boolean) { - this.isLockscreenLandscapeEnabled = isLockscreenLandscapeEnabled - } - - override fun onFinishInflate() { - super.onFinishInflate() - ambientIndicationArea = findViewById(R.id.ambient_indication_container) - keyguardIndicationArea = findViewById(R.id.keyguard_indication_area) - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - binding?.onConfigurationChanged() - - if (isLockscreenLandscapeEnabled) { - updateIndicationAreaBottomMargin() - } - } - - private fun updateIndicationAreaBottomMargin() { - keyguardIndicationArea?.let { - val params = it.layoutParams as FrameLayout.LayoutParams - params.bottomMargin = - resources.getDimensionPixelSize(R.dimen.keyguard_indication_margin_bottom) - it.layoutParams = params - } - } - override fun hasOverlappingRendering(): Boolean { return false } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - super.onLayout(changed, left, top, right, bottom) - findViewById<View>(R.id.ambient_indication_container)?.let { - val (ambientLeft, ambientTop) = it.locationOnScreen - if (binding?.shouldConstrainToTopOfLockIcon() == true) { - // make top of ambient indication view the bottom of the lock icon - it.layout( - ambientLeft, - lockIconViewController?.getBottom()?.toInt() ?: 0, - right - ambientLeft, - ambientTop + it.measuredHeight - ) - } else { - // make bottom of ambient indication view the top of the lock icon - val lockLocationTop = lockIconViewController?.getTop() ?: 0 - it.layout( - ambientLeft, - lockLocationTop.toInt() - it.measuredHeight, - right - ambientLeft, - lockLocationTop.toInt() - ) - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt deleted file mode 100644 index 4aece3d5cd6a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt +++ /dev/null @@ -1,82 +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.phone - -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags -import com.android.systemui.Flags.smartspaceRelocateToBottom -import android.view.View -import android.view.ViewGroup -import android.widget.LinearLayout -import com.android.systemui.res.R -import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController -import com.android.systemui.util.ViewController -import javax.inject.Inject - -class KeyguardBottomAreaViewController - @Inject constructor( - view: KeyguardBottomAreaView, - private val smartspaceController: LockscreenSmartspaceController, - featureFlags: FeatureFlagsClassic -) : ViewController<KeyguardBottomAreaView> (view) { - - private var smartspaceView: View? = null - - init { - view.setIsLockscreenLandscapeEnabled( - featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) - } - - override fun onViewAttached() { - if (!smartspaceRelocateToBottom() || !smartspaceController.isEnabled) { - return - } - - val ambientIndicationArea = mView.findViewById<View>(R.id.ambient_indication_container) - ambientIndicationArea?.visibility = View.GONE - - addSmartspaceView() - } - - override fun onViewDetached() { - } - - fun getView(): KeyguardBottomAreaView { - // TODO: remove this method. - return mView - } - - private fun addSmartspaceView() { - if (!smartspaceRelocateToBottom()) { - return - } - - val smartspaceContainer = mView.findViewById<View>(R.id.smartspace_container) - smartspaceContainer!!.visibility = View.VISIBLE - - smartspaceView = smartspaceController.buildAndConnectView(smartspaceContainer as ViewGroup) - val lp = LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - (smartspaceContainer as ViewGroup).addView(smartspaceView, 0, lp) - val startPadding = context.resources.getDimensionPixelSize( - R.dimen.below_clock_padding_start) - val endPadding = context.resources.getDimensionPixelSize( - R.dimen.below_clock_padding_end) - smartspaceView?.setPaddingRelative(startPadding, 0, endPadding, 0) -// mKeyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 0fac6448909c..f37bc6b2d4fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -37,12 +37,10 @@ import androidx.annotation.NonNull; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.InitController; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeViewController; @@ -61,7 +59,6 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; @@ -72,6 +69,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.util.Set; @@ -104,7 +102,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu private final IStatusBarService mBarService; private final DynamicPrivacyController mDynamicPrivacyController; private final NotificationListContainer mNotifListContainer; - private final DeviceUnlockedInteractor mDeviceUnlockedInteractor; private final QuickSettingsController mQsController; protected boolean mVrMode; @@ -136,8 +133,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu VisualInterruptionDecisionProvider visualInterruptionDecisionProvider, NotificationRemoteInputManager remoteInputManager, NotificationRemoteInputManager.Callback remoteInputManagerCallback, - NotificationListContainer notificationListContainer, - DeviceUnlockedInteractor deviceUnlockedInteractor) { + NotificationListContainer notificationListContainer) { mActivityStarter = activityStarter; mKeyguardStateController = keyguardStateController; mNotificationPanel = panel; @@ -164,7 +160,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mNotifListContainer = notificationListContainer; - mDeviceUnlockedInteractor = deviceUnlockedInteractor; IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService( Context.VR_SERVICE)); @@ -253,25 +248,14 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { mShadeTransitionController.goToLockedShade(clickedEntry.getRow()); } else if (clickedEntry.isSensitive().getValue() - && isInLockedDownShade()) { + && mDynamicPrivacyController.isInLockedDownShade()) { mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); - // launch the bouncer mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */ , null /* cancelRunnable */, false /* afterKeyguardGone */); } } } - /** @return true if the Shade is shown over the Lockscreen, and the device is locked */ - private boolean isInLockedDownShade() { - if (SceneContainerFlag.isEnabled()) { - return mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED - && !mDeviceUnlockedInteractor.getDeviceUnlockStatus().getValue().isUnlocked(); - } else { - return mDynamicPrivacyController.isInLockedDownShade(); - } - } - @Override public boolean isDeviceInVrMode() { return mVrMode; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt index a36ef56e57a8..f11ebc00e242 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt @@ -220,7 +220,7 @@ constructor( if (satelliteManager != null) { // Outer scope launch allows us to delay until MIN_UPTIME - scope.launch { + scope.launch(context = bgDispatcher) { // First, check that satellite is supported on this device satelliteSupport.value = checkSatelliteSupportAfterMinUptime(satelliteManager) logBuffer.i( @@ -229,7 +229,9 @@ constructor( ) // Second, register a listener to let us know if there are changes to support - scope.launch { listenForChangesToSatelliteSupport(satelliteManager) } + scope.launch(context = bgDispatcher) { + listenForChangesToSatelliteSupport(satelliteManager) + } } } else { logBuffer.i { "Satellite manager is null" } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java index ece5a3facdf4..a3dcc3b6f851 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.policy; -import android.media.projection.StopReason; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.CastController.Callback; @@ -27,7 +26,7 @@ public interface CastController extends CallbackController<Callback>, Dumpable { void setCurrentUserId(int currentUserId); List<CastDevice> getCastDevices(); void startCasting(CastDevice device); - void stopCasting(CastDevice device, @StopReason int stopReason); + void stopCasting(CastDevice device); /** * @return whether we have a connected device. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java index ab208506f203..52f80fbf50fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java @@ -185,13 +185,13 @@ public class CastControllerImpl implements CastController { } @Override - public void stopCasting(CastDevice device, @StopReason int stopReason) { + public void stopCasting(CastDevice device) { final boolean isProjection = device.getTag() instanceof MediaProjectionInfo; mLogger.logStopCasting(isProjection); if (isProjection) { final MediaProjectionInfo projection = (MediaProjectionInfo) device.getTag(); if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) { - mProjectionManager.stopActiveProjection(stopReason); + mProjectionManager.stopActiveProjection(StopReason.STOP_QS_TILE); } else { mLogger.logStopCastingNoProjection(projection); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java index 1bb4e8c66ef1..c77f6c1b8552 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java @@ -45,5 +45,6 @@ public interface ConfigurationController extends CallbackController<Configuratio default void onLocaleListChanged() {} default void onLayoutDirectionChanged(boolean isLayoutRtl) {} default void onOrientationChanged(int orientation) {} + default void onMovedToDisplay(int newDisplayId, Configuration newConfiguration) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 78954dea27ba..8b60ee56d5f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -192,7 +192,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mUiEventLogger.log( LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP); - mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(), + mUserSwitchDialogController.showDialog( Expandable.fromView(mUserAvatarViewWithBackground)); }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt index a1d5cbea62f9..9ff0d18f0e2b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt @@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dialog.ui.composable.AlertDialogContent import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.ComponentSystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialogFactory @@ -67,6 +68,7 @@ constructor( private val viewModel: Provider<ModesDialogViewModel>, private val dialogEventLogger: ModesDialogEventLogger, @Main private val mainCoroutineContext: CoroutineContext, + private val shadeDisplayContextRepository: ShadeDialogContextInteractor, ) : SystemUIDialog.Delegate { // NOTE: This should only be accessed/written from the main thread. @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null @@ -78,7 +80,10 @@ constructor( currentDialog?.dismiss() } - currentDialog = sysuiDialogFactory.create { ModesDialogContent(it) } + currentDialog = + sysuiDialogFactory.create(context = shadeDisplayContextRepository.context) { + ModesDialogContent(it) + } currentDialog ?.lifecycle ?.addObserver( @@ -106,9 +111,8 @@ constructor( modifier = Modifier.semantics { testTagsAsResourceId = true - paneTitle = dialog.context.getString( - R.string.accessibility_desc_quick_settings - ) + paneTitle = + dialog.context.getString(R.string.accessibility_desc_quick_settings) }, title = { Text( diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt index 35ea0ea3e048..64cda1f21cf6 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt @@ -41,7 +41,13 @@ class BackGestureRecognizer(private val gestureDistanceThresholdPx: Int) : Gestu } override fun accept(event: MotionEvent) { - if (!isThreeFingerTouchpadSwipe(event)) return + if (!isMultifingerTouchpadSwipe(event)) return + if (!isThreeFingerTouchpadSwipe(event)) { + if (event.actionMasked == MotionEvent.ACTION_UP) { + gestureStateChangedCallback(GestureState.Error) + } + return + } val gestureState = distanceTracker.processEvent(event) updateGestureState( gestureStateChangedCallback, diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt index 68a2ef9528eb..884f08eae22d 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureRecognizer.kt @@ -35,6 +35,11 @@ private fun isNFingerTouchpadSwipe(event: MotionEvent, fingerCount: Int): Boolea event.getAxisValue(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT) == fingerCount.toFloat() } +fun isMultifingerTouchpadSwipe(event: MotionEvent): Boolean { + return event.classification == MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE || + event.classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE +} + fun isTwoFingerSwipe(event: MotionEvent): Boolean { return event.classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt index 9801626dac8f..7767a46f7318 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt @@ -40,7 +40,13 @@ class HomeGestureRecognizer( } override fun accept(event: MotionEvent) { - if (!isThreeFingerTouchpadSwipe(event)) return + if (!isMultifingerTouchpadSwipe(event)) return + if (!isThreeFingerTouchpadSwipe(event)) { + if (event.actionMasked == MotionEvent.ACTION_UP) { + gestureStateChangedCallback(GestureState.Error) + } + return + } val gestureState = distanceTracker.processEvent(event) velocityTracker.accept(event) updateGestureState( diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt index 5ff583a19ee9..74746de0562c 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt @@ -43,7 +43,13 @@ class RecentAppsGestureRecognizer( } override fun accept(event: MotionEvent) { - if (!isThreeFingerTouchpadSwipe(event)) return + if (!isMultifingerTouchpadSwipe(event)) return + if (!isThreeFingerTouchpadSwipe(event)) { + if (event.actionMasked == MotionEvent.ACTION_UP) { + gestureStateChangedCallback(GestureState.Error) + } + return + } val gestureState = distanceTracker.processEvent(event) velocityTracker.accept(event) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt index 21e2917cf01b..dd275bd11d1e 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt @@ -40,9 +40,8 @@ class TouchpadGestureHandler( return if (isFromTouchpad && !buttonClick) { if (isTwoFingerSwipe(event)) { easterEggGestureMonitor.processTouchpadEvent(event) - } else { - gestureRecognizer.accept(event) } + gestureRecognizer.accept(event) true } else { false diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt index 102fcc0c59f2..e4b2dc25e411 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt @@ -18,7 +18,6 @@ package com.android.systemui.user.ui.dialog import android.app.Dialog -import android.content.Context import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.UiEventLogger import com.android.settingslib.users.UserCreatingDialog @@ -32,6 +31,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.tiles.UserDetailView +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.user.UserSwitchFullscreenDialog import com.android.systemui.user.domain.interactor.UserSwitcherInteractor import com.android.systemui.user.domain.model.ShowDialogRequestModel @@ -48,7 +48,6 @@ import com.android.app.tracing.coroutines.launchTraced as launch class UserSwitcherDialogCoordinator @Inject constructor( - @Application private val context: Lazy<Context>, @Application private val applicationScope: Lazy<CoroutineScope>, private val falsingManager: Lazy<FalsingManager>, private val broadcastSender: Lazy<BroadcastSender>, @@ -59,6 +58,7 @@ constructor( private val activityStarter: Lazy<ActivityStarter>, private val falsingCollector: Lazy<FalsingCollector>, private val userSwitcherViewModel: Lazy<UserSwitcherViewModel>, + private val shadeDialogContextInteractor: Lazy<ShadeDialogContextInteractor>, ) : CoreStartable { private var currentDialog: Dialog? = null @@ -71,12 +71,13 @@ constructor( private fun startHandlingDialogShowRequests() { applicationScope.get().launch { interactor.get().dialogShowRequests.filterNotNull().collect { request -> + val context = shadeDialogContextInteractor.get().context val (dialog, dialogCuj) = when (request) { is ShowDialogRequestModel.ShowAddUserDialog -> Pair( AddUserDialog( - context = context.get(), + context = context, userHandle = request.userHandle, isKeyguardShowing = request.isKeyguardShowing, showEphemeralMessage = request.showEphemeralMessage, @@ -92,7 +93,7 @@ constructor( is ShowDialogRequestModel.ShowUserCreationDialog -> Pair( UserCreatingDialog( - context.get(), + context, request.isGuest, ), null, @@ -100,7 +101,7 @@ constructor( is ShowDialogRequestModel.ShowExitGuestDialog -> Pair( ExitGuestDialog( - context = context.get(), + context = context, guestUserId = request.guestUserId, isGuestEphemeral = request.isGuestEphemeral, targetUserId = request.targetUserId, @@ -117,7 +118,7 @@ constructor( is ShowDialogRequestModel.ShowUserSwitcherDialog -> Pair( UserSwitchDialog( - context = context.get(), + context = context, adapter = userDetailAdapterProvider.get(), uiEventLogger = eventLogger.get(), falsingManager = falsingManager.get(), @@ -132,7 +133,7 @@ constructor( is ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog -> Pair( UserSwitchFullscreenDialog( - context = context.get(), + context = context, falsingCollector = falsingCollector.get(), userSwitcherViewModel = userSwitcherViewModel.get(), ), diff --git a/packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt b/packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt index ca0876ca32cc..27ada236fa19 100644 --- a/packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt @@ -22,5 +22,5 @@ import dagger.Module @Module interface EventLogModule { - @SysUISingleton @Binds fun bindEventLog(eventLogImpl: EventLogImpl?): EventLog? + @SysUISingleton @Binds fun bindEventLog(eventLogImpl: EventLogImpl): EventLog } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index a41725f754df..4abbbacd800b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -25,7 +25,6 @@ import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.Flags @@ -299,20 +298,6 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun keyguardCallback_visibilityChanged_clockDozeCalled() = - runBlocking(IMMEDIATE) { - val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() - verify(keyguardUpdateMonitor).registerCallback(capture(captor)) - - captor.value.onKeyguardVisibilityChanged(true) - verify(animations, never()).doze(0f) - - captor.value.onKeyguardVisibilityChanged(false) - verify(animations, times(2)).doze(0f) - } - - @Test fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) { val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() @@ -344,19 +329,6 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun keyguardCallback_verifyKeyguardChanged() = - runBlocking(IMMEDIATE) { - val job = underTest.listenForDozeAmount(this) - repository.setDozeAmount(0.4f) - - yield() - - verify(animations, times(2)).doze(0.4f) - - job.cancel() - } - - @Test fun listenForDozeAmountTransition_updatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt index 4aaa82e4a16d..37eb148a5ea7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt @@ -14,6 +14,8 @@ import android.testing.TestableLooper.RunWithLooper import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget +import android.view.RemoteAnimationTarget.MODE_CLOSING +import android.view.RemoteAnimationTarget.MODE_OPENING import android.view.SurfaceControl import android.view.ViewGroup import android.view.WindowManager.TRANSIT_NONE @@ -36,10 +38,6 @@ import junit.framework.Assert.assertTrue import junit.framework.AssertionFailedError import kotlin.concurrent.thread import kotlin.test.assertEquals -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout import org.junit.After import org.junit.Assert.assertThrows import org.junit.Before @@ -49,6 +47,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -215,22 +214,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun registersLongLivedTransition() { - activityTransitionAnimator.register( - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie_1") - override val component = ComponentName("com.test.package", "Test1") - } - ) + var factory = controllerFactory() + activityTransitionAnimator.register(factory.cookie, factory) assertEquals(2, testShellTransitions.remotes.size) - activityTransitionAnimator.register( - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie_2") - override val component = ComponentName("com.test.package", "Test2") - } - ) + factory = controllerFactory() + activityTransitionAnimator.register(factory.cookie, factory) assertEquals(4, testShellTransitions.remotes.size) } @@ -241,20 +230,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun registersLongLivedTransitionOverridingPreviousRegistration() { val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie") - activityTransitionAnimator.register( - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = cookie - override val component = ComponentName("com.test.package", "Test1") - } - ) + var factory = controllerFactory(cookie) + activityTransitionAnimator.register(cookie, factory) val transitions = testShellTransitions.remotes.values.toList() - activityTransitionAnimator.register( - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = cookie - override val component = ComponentName("com.test.package", "Test2") - } - ) + factory = controllerFactory(cookie) + activityTransitionAnimator.register(cookie, factory) assertEquals(2, testShellTransitions.remotes.size) for (transition in transitions) { assertThat(testShellTransitions.remotes.values).doesNotContain(transition) @@ -264,38 +245,19 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED) @Test fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() { - val controller = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie") - override val component = ComponentName("com.test.package", "Test") - } + val factory = controllerFactory(component = null) assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.register(controller) + activityTransitionAnimator.register(factory.cookie, factory) } } @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED) @Test fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() { - // No TransitionCookie - val controllerWithoutCookie = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = null - } - assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.register(controllerWithoutCookie) - } - // No ComponentName - val controllerWithoutComponent = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie") - override val component = null - } + var factory = controllerFactory(component = null) assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.register(controllerWithoutComponent) + activityTransitionAnimator.register(factory.cookie, factory) } // No TransitionRegister @@ -307,14 +269,9 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { testTransitionAnimator, disableWmTimeout = true, ) - val validController = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = - ActivityTransitionAnimator.TransitionCookie("test_cookie") - override val component = ComponentName("com.test.package", "Test") - } + factory = controllerFactory() assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.register(validController) + activityTransitionAnimator.register(factory.cookie, factory) } } @@ -324,18 +281,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun unregistersLongLivedTransition() { - val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3) for (index in 0 until 3) { - cookies[index] = ActivityTransitionAnimator.TransitionCookie("test_cookie_$index") - - val controller = - object : DelegateTransitionAnimatorController(controller) { - override val transitionCookie = cookies[index] - override val component = ComponentName("foo.bar", "Foobar") - } - activityTransitionAnimator.register(controller) + cookies[index] = mock(ActivityTransitionAnimator.TransitionCookie::class.java) + val factory = controllerFactory(cookies[index]!!) + activityTransitionAnimator.register(factory.cookie, factory) } activityTransitionAnimator.unregister(cookies[0]!!) @@ -350,7 +301,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun doesNotStartIfAnimationIsCancelled() { - val runner = activityTransitionAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) runner.onAnimationCancelled() runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback) @@ -364,7 +315,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun cancelsIfNoOpeningWindowIsFound() { - val runner = activityTransitionAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback) waitForIdleSync() @@ -377,7 +328,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun startsAnimationIfWindowIsOpening() { - val runner = activityTransitionAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) runner.onAnimationStart( TRANSIT_NONE, arrayOf(fakeWindow()), @@ -404,7 +355,8 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { @Test fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() { assertThrows(IllegalStateException::class.java) { - activityTransitionAnimator.createRunner(controller, longLived = true) + val factory = controllerFactory() + activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true) } } @@ -414,7 +366,8 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun runnerCreatesDelegateLazily_whenPostingTimeouts() { - val runner = activityTransitionAnimator.createRunner(controller, longLived = true) + val factory = controllerFactory() + val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true) assertNull(runner.delegate) runner.postTimeouts() assertNotNull(runner.delegate) @@ -426,29 +379,29 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun runnerCreatesDelegateLazily_onAnimationStart() { - val runner = activityTransitionAnimator.createRunner(controller, longLived = true) + val factory = controllerFactory() + val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true) assertNull(runner.delegate) - // The delegate is cleaned up after execution (which happens in another thread), so what we - // do instead is check if it becomes non-null at any point with a 1 second timeout. This - // will tell us that takeOverWithAnimation() triggered the lazy initialization. var delegateInitialized = false - runBlocking { - val initChecker = launch { - withTimeout(1.seconds) { - while (runner.delegate == null) continue + activityTransitionAnimator.addListener( + object : ActivityTransitionAnimator.Listener { + override fun onTransitionAnimationStart() { + // This is called iff the delegate was initialized, so it's a good proxy for + // checking the initialization. delegateInitialized = true } } - runner.onAnimationStart( - TRANSIT_NONE, - arrayOf(fakeWindow()), - emptyArray(), - emptyArray(), - iCallback, - ) - initChecker.join() - } + ) + runner.onAnimationStart( + TRANSIT_NONE, + arrayOf(fakeWindow()), + emptyArray(), + emptyArray(), + iCallback, + ) + + waitForIdleSync() assertTrue(delegateInitialized) } @@ -458,28 +411,28 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun runnerCreatesDelegateLazily_onAnimationTakeover() { - val runner = activityTransitionAnimator.createRunner(controller, longLived = true) + val factory = controllerFactory() + val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = false) assertNull(runner.delegate) - // The delegate is cleaned up after execution (which happens in another thread), so what we - // do instead is check if it becomes non-null at any point with a 1 second timeout. This - // will tell us that takeOverWithAnimation() triggered the lazy initialization. var delegateInitialized = false - runBlocking { - val initChecker = launch { - withTimeout(1.seconds) { - while (runner.delegate == null) continue + activityTransitionAnimator.addListener( + object : ActivityTransitionAnimator.Listener { + override fun onTransitionAnimationStart() { + // This is called iff the delegate was initialized, so it's a good proxy for + // checking the initialization. delegateInitialized = true } } - runner.takeOverAnimation( - arrayOf(fakeWindow()), - arrayOf(WindowAnimationState()), - SurfaceControl.Transaction(), - iCallback, - ) - initChecker.join() - } + ) + runner.takeOverAnimation( + arrayOf(fakeWindow(MODE_CLOSING)), + arrayOf(WindowAnimationState()), + SurfaceControl.Transaction(), + iCallback, + ) + + waitForIdleSync() assertTrue(delegateInitialized) } @@ -489,7 +442,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun animationTakeoverThrows_whenTheFlagsAreDisabled() { - val runner = activityTransitionAnimator.createRunner(controller, longLived = false) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) assertThrows(IllegalStateException::class.java) { runner.takeOverAnimation( arrayOf(fakeWindow()), @@ -506,14 +459,28 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { ) @Test fun disposeRunner_delegateDereferenced() { - val runner = activityTransitionAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createEphemeralRunner(controller) assertNotNull(runner.delegate) runner.dispose() waitForIdleSync() assertNull(runner.delegate) } - private fun fakeWindow(): RemoteAnimationTarget { + private fun controllerFactory( + cookie: ActivityTransitionAnimator.TransitionCookie = + mock(ActivityTransitionAnimator.TransitionCookie::class.java), + component: ComponentName? = mock(ComponentName::class.java), + ): ActivityTransitionAnimator.ControllerFactory { + return object : ActivityTransitionAnimator.ControllerFactory(cookie, component) { + override fun createController(forLaunch: Boolean) = + object : DelegateTransitionAnimatorController(controller) { + override val isLaunching: Boolean + get() = forLaunch + } + } + } + + private fun fakeWindow(mode: Int = MODE_OPENING): RemoteAnimationTarget { val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */) val taskInfo = ActivityManager.RunningTaskInfo() taskInfo.topActivity = ComponentName("com.android.systemui", "FakeActivity") @@ -521,7 +488,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { return RemoteAnimationTarget( 0, - RemoteAnimationTarget.MODE_OPENING, + mode, SurfaceControl(), false, Rect(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt index fb0fd23fab24..6bfd08025833 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt @@ -34,8 +34,10 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.model.SysUiState import com.android.systemui.res.R +import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -82,7 +84,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { private val uiProperties = BluetoothTileDialogViewModel.UiProperties.build( isBluetoothEnabled = ENABLED, - isAutoOnToggleFeatureAvailable = ENABLED + isAutoOnToggleFeatureAvailable = ENABLED, ) @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory @Mock private lateinit var dialogManager: SystemUIDialogManager @@ -98,6 +100,8 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { private lateinit var mBluetoothTileDialogDelegate: BluetoothTileDialogDelegate private lateinit var deviceItem: DeviceItem + private val kosmos = testKosmos() + @Before fun setUp() { scheduler = TestCoroutineScheduler() @@ -116,10 +120,16 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { fakeSystemClock, uiEventLogger, logger, - sysuiDialogFactory + sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) - whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))).thenAnswer { + whenever( + sysuiDialogFactory.create( + any(SystemUIDialog.Delegate::class.java), + any() + ) + ).thenAnswer { SystemUIDialog( mContext, 0, @@ -128,7 +138,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { sysuiState, fakeBroadcastDispatcher, dialogTransitionAnimator, - it.getArgument(0) + it.getArgument(0), ) } @@ -140,7 +150,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { deviceName = DEVICE_NAME, connectionSummary = DEVICE_CONNECTION_SUMMARY, iconWithDescription = icon, - background = null + background = null, ) `when`(cachedBluetoothDevice.isBusy).thenReturn(false) } @@ -169,7 +179,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, listOf(deviceItem), showSeeAll = false, - showPairNewDevice = false + showPairNewDevice = false, ) val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list) @@ -217,6 +227,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) .Adapter(bluetoothTileDialogCallback) .DeviceItemViewHolder(view) @@ -238,7 +249,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, listOf(deviceItem), showSeeAll = false, - showPairNewDevice = true + showPairNewDevice = true, ) val seeAllButton = dialog.requireViewById<View>(R.id.see_all_button) @@ -272,6 +283,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) .createDialog() dialog.show() @@ -295,6 +307,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) .createDialog() dialog.show() @@ -318,6 +331,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, + kosmos.shadeDialogContextInteractor, ) .createDialog() dialog.show() @@ -339,7 +353,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, visibility = VISIBLE, label = null, - isActive = true + isActive = true, ) val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button) @@ -361,7 +375,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, visibility = VISIBLE, label = null, - isActive = false + isActive = false, ) val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button) @@ -383,7 +397,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dialog, visibility = GONE, label = null, - isActive = false + isActive = false, ) val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java index c2c94a88603a..1cabf202463e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java @@ -77,6 +77,8 @@ public class CommunalTouchHandlerTest extends SysuiTestCase { INITIATION_WIDTH, mKosmos.getCommunalInteractor(), mKosmos.getConfigurationInteractor(), + mKosmos.getSceneInteractor(), + Optional.of(mKosmos.getMockWindowRootViewProvider()), mLifecycle ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 24bca70fd41f..fb70846049da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -41,6 +41,7 @@ import android.os.Handler; import android.os.UserManager; import android.provider.Settings; import android.testing.TestableLooper; +import android.view.Display; import android.view.GestureDetector; import android.view.IWindowManager; import android.view.KeyEvent; @@ -63,6 +64,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.display.data.repository.FakeDisplayWindowPropertiesRepository; import com.android.systemui.globalactions.domain.interactor.GlobalActionsInteractor; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.ActivityStarter; @@ -159,6 +161,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { getContext().getResources().getConfiguration()); when(mStatusBarWindowControllerStore.getDefaultDisplay()) .thenReturn(mStatusBarWindowController); + when(mStatusBarWindowControllerStore.forDisplay(anyInt())) + .thenReturn(mStatusBarWindowController); mGlobalSettings = new FakeGlobalSettings(); mSecureSettings = new FakeSettings(); mInteractor = mKosmos.getGlobalActionsInteractor(); @@ -198,7 +202,9 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mDialogTransitionAnimator, mSelectedUserInteractor, mLogoutInteractor, - mInteractor); + mInteractor, + () -> new FakeDisplayWindowPropertiesRepository(mContext) + ); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors(); @@ -609,13 +615,15 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { // When entering power menu from lockscreen, with smart lock enabled when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true); - mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */); + mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */, + Display.DEFAULT_DISPLAY); // Then smart lock will be disabled verify(mLockPatternUtils).requireCredentialEntry(eq(expectedUser)); // hide dialog again - mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */); + mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */, + Display.DEFAULT_DISPLAY); } @Test @@ -729,13 +737,13 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); // Show dialog with keyguard showing - mGlobalActionsDialogLite.showOrHideDialog(true, true, null); + mGlobalActionsDialogLite.showOrHideDialog(true, true, null, Display.DEFAULT_DISPLAY); assertOneItemOfType(mGlobalActionsDialogLite.mItems, GlobalActionsDialogLite.SystemUpdateAction.class); // Hide dialog - mGlobalActionsDialogLite.showOrHideDialog(true, true, null); + mGlobalActionsDialogLite.showOrHideDialog(true, true, null, Display.DEFAULT_DISPLAY); } @Test @@ -754,13 +762,13 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); // Show dialog with keyguard showing - mGlobalActionsDialogLite.showOrHideDialog(false, false, null); + mGlobalActionsDialogLite.showOrHideDialog(false, false, null, Display.DEFAULT_DISPLAY); assertNoItemsOfType(mGlobalActionsDialogLite.mItems, GlobalActionsDialogLite.SystemUpdateAction.class); // Hide dialog - mGlobalActionsDialogLite.showOrHideDialog(false, false, null); + mGlobalActionsDialogLite.showOrHideDialog(false, false, null, Display.DEFAULT_DISPLAY); } private UserInfo mockCurrentUser(int flags) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt index 1184a76d54ff..eb19a9c2528d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt @@ -81,7 +81,7 @@ import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @FlakyTest( bugId = 292574995, - detail = "on certain architectures all permutations with startActivity=true is causing failures" + detail = "on certain architectures all permutations with startActivity=true is causing failures", ) @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @@ -93,11 +93,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { private val DRAWABLE = mock<Icon> { whenever(this.contentDescription) - .thenReturn( - ContentDescription.Resource( - res = CONTENT_DESCRIPTION_RESOURCE_ID, - ) - ) + .thenReturn(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) } private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337 @@ -273,13 +269,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { context = context, userFileManager = mock<UserFileManager>().apply { - whenever( - getSharedPreferences( - anyString(), - anyInt(), - anyInt(), - ) - ) + whenever(getSharedPreferences(anyString(), anyInt(), anyInt())) .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, @@ -316,9 +306,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = - KeyguardInteractorFactory.create( - featureFlags = featureFlags, - ) + KeyguardInteractorFactory.create(featureFlags = featureFlags) .keyguardInteractor, shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, @@ -350,9 +338,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { homeControls.setState( lockScreenState = - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = DRAWABLE, - ) + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = DRAWABLE) ) homeControls.onTriggeredResult = if (startActivity) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index cea51a89a378..222a7fe05778 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.flags.FakeFeatureFlags @@ -66,8 +65,6 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - featureFlags = FakeFeatureFlagsClassic().apply { set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) } underTest = @@ -88,16 +85,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { } @Test - fun addViewsConditionally_migrateFlagOn() { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - val constraintLayout = ConstraintLayout(context, null) - underTest.addViews(constraintLayout) - assertThat(constraintLayout.childCount).isGreaterThan(0) - } - - @Test - fun addViewsConditionally_migrateAndRefactorFlagsOn() { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) + fun addViewsConditionally() { val constraintLayout = ConstraintLayout(context, null) underTest.addViews(constraintLayout) assertThat(constraintLayout.childCount).isGreaterThan(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt deleted file mode 100644 index 7e85dd5d3236..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ /dev/null @@ -1,771 +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.keyguard.ui.viewmodel - -import android.app.admin.DevicePolicyManager -import android.content.Intent -import android.os.UserHandle -import android.platform.test.flag.junit.FlagsParameterization -import androidx.test.filters.SmallTest -import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.internal.widget.LockPatternUtils -import com.android.keyguard.logging.KeyguardQuickAffordancesLogger -import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.DialogTransitionAnimator -import com.android.systemui.animation.Expandable -import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor -import com.android.systemui.dock.DockManagerFake -import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.flags.andSceneContainer -import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys -import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig -import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager -import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTouchHandlingInteractor -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor -import com.android.systemui.keyguard.shared.quickaffordance.ActivationState -import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition -import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger -import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.res.R -import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.settings.UserFileManager -import com.android.systemui.settings.UserTracker -import com.android.systemui.shade.domain.interactor.shadeInteractor -import com.android.systemui.shade.pulsingGestureListener -import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.testKosmos -import com.android.systemui.util.FakeSharedPreferences -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.settings.fakeSettings -import com.google.common.truth.Truth.assertThat -import kotlin.math.max -import kotlin.math.min -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.MockitoAnnotations -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters - -@SmallTest -@RunWith(ParameterizedAndroidJunit4::class) -class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { - - private val kosmos = testKosmos() - private val testDispatcher = kosmos.testDispatcher - private val testScope = kosmos.testScope - private val settings = kosmos.fakeSettings - - @Mock private lateinit var expandable: Expandable - @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper - @Mock private lateinit var lockPatternUtils: LockPatternUtils - @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var userTracker: UserTracker - @Mock private lateinit var activityStarter: ActivityStarter - @Mock private lateinit var launchAnimator: DialogTransitionAnimator - @Mock private lateinit var devicePolicyManager: DevicePolicyManager - @Mock private lateinit var logger: KeyguardQuickAffordancesLogger - @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger - @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper - - private lateinit var underTest: KeyguardBottomAreaViewModel - - private lateinit var repository: FakeKeyguardRepository - private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig - private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig - private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig - private lateinit var dockManager: DockManagerFake - private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository - - init { - mSetFlagsRule.setFlagsParameterization(flags) - } - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true) - overrideResource( - R.array.config_keyguardQuickAffordanceDefaults, - arrayOf( - KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + - ":" + - BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS, - KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + - ":" + - BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET - ) - ) - - whenever(burnInHelperWrapper.burnInOffset(anyInt(), any())) - .thenReturn(RETURNED_BURN_IN_OFFSET) - - homeControlsQuickAffordanceConfig = - FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS) - quickAccessWalletAffordanceConfig = - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET - ) - qrCodeScannerAffordanceConfig = - FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER) - dockManager = DockManagerFake() - biometricSettingsRepository = FakeBiometricSettingsRepository() - val featureFlags = - FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) } - - val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) - val keyguardInteractor = withDeps.keyguardInteractor - repository = withDeps.repository - - whenever(userTracker.userHandle).thenReturn(mock()) - whenever(lockPatternUtils.getStrongAuthForUser(anyInt())) - .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED) - val localUserSelectionManager = - KeyguardQuickAffordanceLocalUserSelectionManager( - context = context, - userFileManager = - mock<UserFileManager>().apply { - whenever( - getSharedPreferences( - anyString(), - anyInt(), - anyInt(), - ) - ) - .thenReturn(FakeSharedPreferences()) - }, - userTracker = userTracker, - broadcastDispatcher = fakeBroadcastDispatcher, - ) - val remoteUserSelectionManager = - KeyguardQuickAffordanceRemoteUserSelectionManager( - scope = testScope.backgroundScope, - userTracker = userTracker, - clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker), - userHandle = UserHandle.SYSTEM, - ) - val quickAffordanceRepository = - KeyguardQuickAffordanceRepository( - appContext = context, - scope = testScope.backgroundScope, - localUserSelectionManager = localUserSelectionManager, - remoteUserSelectionManager = remoteUserSelectionManager, - userTracker = userTracker, - legacySettingSyncer = - KeyguardQuickAffordanceLegacySettingSyncer( - scope = testScope.backgroundScope, - backgroundDispatcher = testDispatcher, - secureSettings = settings, - selectionsManager = localUserSelectionManager, - ), - configs = - setOf( - homeControlsQuickAffordanceConfig, - quickAccessWalletAffordanceConfig, - qrCodeScannerAffordanceConfig, - ), - dumpManager = mock(), - userHandle = UserHandle.SYSTEM, - ) - val keyguardTouchHandlingInteractor = - KeyguardTouchHandlingInteractor( - context = mContext, - scope = testScope.backgroundScope, - transitionInteractor = kosmos.keyguardTransitionInteractor, - repository = repository, - logger = UiEventLoggerFake(), - featureFlags = featureFlags, - broadcastDispatcher = broadcastDispatcher, - accessibilityManager = accessibilityManager, - pulsingGestureListener = kosmos.pulsingGestureListener, - faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor, - ) - underTest = - KeyguardBottomAreaViewModel( - keyguardInteractor = keyguardInteractor, - quickAffordanceInteractor = - KeyguardQuickAffordanceInteractor( - keyguardInteractor = keyguardInteractor, - shadeInteractor = kosmos.shadeInteractor, - lockPatternUtils = lockPatternUtils, - keyguardStateController = keyguardStateController, - userTracker = userTracker, - activityStarter = activityStarter, - featureFlags = featureFlags, - repository = { quickAffordanceRepository }, - launchAnimator = launchAnimator, - logger = logger, - metricsLogger = metricsLogger, - devicePolicyManager = devicePolicyManager, - dockManager = dockManager, - biometricSettingsRepository = biometricSettingsRepository, - backgroundDispatcher = testDispatcher, - appContext = mContext, - sceneInteractor = { kosmos.sceneInteractor }, - ), - bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), - burnInHelperWrapper = burnInHelperWrapper, - keyguardTouchHandlingViewModel = - KeyguardTouchHandlingViewModel( - interactor = keyguardTouchHandlingInteractor, - ), - settingsMenuViewModel = - KeyguardSettingsMenuViewModel( - interactor = keyguardTouchHandlingInteractor, - ), - ) - } - - @Test - fun startButton_present_visibleModel_startsActivityOnClick() = - testScope.runTest { - repository.setKeyguardShowing(true) - val latest = collectLastValue(underTest.startButton) - - val testConfig = - TestConfig( - isVisible = true, - isClickable = true, - isActivated = true, - icon = mock(), - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = testConfig, - ) - - assertQuickAffordanceViewModel( - viewModel = latest(), - testConfig = testConfig, - configKey = configKey, - ) - } - - @Test - fun startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeatures() = - testScope.runTest { - whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) - .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) - repository.setKeyguardShowing(true) - val latest by collectLastValue(underTest.startButton) - - val testConfig = - TestConfig( - isVisible = true, - isClickable = true, - isActivated = true, - icon = mock(), - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = testConfig, - ) - - assertQuickAffordanceViewModel( - viewModel = latest, - testConfig = - TestConfig( - isVisible = false, - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ), - configKey = configKey, - ) - } - - @Test - fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() = - testScope.runTest { - underTest.enablePreviewMode( - initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - shouldHighlightSelectedAffordance = true, - ) - repository.setKeyguardShowing(false) - val latest = collectLastValue(underTest.startButton) - - val icon: Icon = mock() - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = - TestConfig( - isVisible = true, - isClickable = true, - isActivated = true, - icon = icon, - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ), - ) - - assertQuickAffordanceViewModel( - viewModel = latest(), - testConfig = - TestConfig( - isVisible = true, - isClickable = false, - isActivated = false, - icon = icon, - canShowWhileLocked = false, - intent = Intent("action"), - isSelected = true, - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ), - configKey = configKey, - ) - assertThat(latest()?.isSelected).isTrue() - } - - @Test - fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() = - testScope.runTest { - underTest.enablePreviewMode( - initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - shouldHighlightSelectedAffordance = true, - ) - repository.setKeyguardShowing(false) - val startButton = collectLastValue(underTest.startButton) - val endButton = collectLastValue(underTest.endButton) - - val icon: Icon = mock() - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = - TestConfig( - isVisible = true, - isClickable = true, - isActivated = true, - icon = icon, - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ), - ) - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_END, - testConfig = - TestConfig( - isVisible = true, - isClickable = true, - isActivated = true, - icon = icon, - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(), - ), - ) - - assertQuickAffordanceViewModel( - viewModel = endButton(), - testConfig = - TestConfig( - isVisible = true, - isClickable = false, - isActivated = false, - icon = icon, - canShowWhileLocked = false, - intent = Intent("action"), - isDimmed = true, - slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(), - ), - configKey = configKey, - ) - } - - @Test - fun endButton_present_visibleModel_doNothingOnClick() = - testScope.runTest { - repository.setKeyguardShowing(true) - val latest = collectLastValue(underTest.endButton) - - val config = - TestConfig( - isVisible = true, - isClickable = true, - icon = mock(), - canShowWhileLocked = false, - intent = - null, // This will cause it to tell the system that the click was handled. - slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(), - ) - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_END, - testConfig = config, - ) - - assertQuickAffordanceViewModel( - viewModel = latest(), - testConfig = config, - configKey = configKey, - ) - } - - @Test - fun startButton_notPresent_modelIsHidden() = - testScope.runTest { - val latest = collectLastValue(underTest.startButton) - - val config = - TestConfig( - isVisible = false, - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = config, - ) - - assertQuickAffordanceViewModel( - viewModel = latest(), - testConfig = config, - configKey = configKey, - ) - } - - @Test - fun animateButtonReveal() = - testScope.runTest { - repository.setKeyguardShowing(true) - val testConfig = - TestConfig( - isVisible = true, - isClickable = true, - icon = mock(), - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = testConfig, - ) - - val value = collectLastValue(underTest.startButton.map { it.animateReveal }) - - assertThat(value()).isFalse() - repository.setAnimateDozingTransitions(true) - assertThat(value()).isTrue() - repository.setAnimateDozingTransitions(false) - assertThat(value()).isFalse() - } - - @Test - fun isOverlayContainerVisible() = - testScope.runTest { - val value = collectLastValue(underTest.isOverlayContainerVisible) - - assertThat(value()).isTrue() - repository.setIsDozing(true) - assertThat(value()).isFalse() - repository.setIsDozing(false) - assertThat(value()).isTrue() - } - - @Test - fun alpha() = - testScope.runTest { - val value = collectLastValue(underTest.alpha) - - assertThat(value()).isEqualTo(1f) - repository.setBottomAreaAlpha(0.1f) - assertThat(value()).isEqualTo(0.1f) - repository.setBottomAreaAlpha(0.5f) - assertThat(value()).isEqualTo(0.5f) - repository.setBottomAreaAlpha(0.2f) - assertThat(value()).isEqualTo(0.2f) - repository.setBottomAreaAlpha(0f) - assertThat(value()).isEqualTo(0f) - } - - @Test - fun alpha_inPreviewMode_doesNotChange() = - testScope.runTest { - underTest.enablePreviewMode( - initiallySelectedSlotId = null, - shouldHighlightSelectedAffordance = false, - ) - val value = collectLastValue(underTest.alpha) - - assertThat(value()).isEqualTo(1f) - repository.setBottomAreaAlpha(0.1f) - assertThat(value()).isEqualTo(1f) - repository.setBottomAreaAlpha(0.5f) - assertThat(value()).isEqualTo(1f) - repository.setBottomAreaAlpha(0.2f) - assertThat(value()).isEqualTo(1f) - repository.setBottomAreaAlpha(0f) - assertThat(value()).isEqualTo(1f) - } - - @Test - fun isClickable_trueWhenAlphaAtThreshold() = - testScope.runTest { - repository.setKeyguardShowing(true) - repository.setBottomAreaAlpha( - KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - ) - - val testConfig = - TestConfig( - isVisible = true, - isClickable = true, - icon = mock(), - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = testConfig, - ) - - val latest = collectLastValue(underTest.startButton) - - assertQuickAffordanceViewModel( - viewModel = latest(), - testConfig = testConfig, - configKey = configKey, - ) - } - - @Test - fun isClickable_trueWhenAlphaAboveThreshold() = - testScope.runTest { - repository.setKeyguardShowing(true) - val latest = collectLastValue(underTest.startButton) - repository.setBottomAreaAlpha( - min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f), - ) - - val testConfig = - TestConfig( - isVisible = true, - isClickable = true, - icon = mock(), - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = testConfig, - ) - - assertQuickAffordanceViewModel( - viewModel = latest(), - testConfig = testConfig, - configKey = configKey, - ) - } - - @Test - fun isClickable_falseWhenAlphaBelowThreshold() = - testScope.runTest { - repository.setKeyguardShowing(true) - val latest = collectLastValue(underTest.startButton) - repository.setBottomAreaAlpha( - max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f), - ) - - val testConfig = - TestConfig( - isVisible = true, - isClickable = false, - icon = mock(), - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = testConfig, - ) - - assertQuickAffordanceViewModel( - viewModel = latest(), - testConfig = testConfig, - configKey = configKey, - ) - } - - @Test - fun isClickable_falseWhenAlphaAtZero() = - testScope.runTest { - repository.setKeyguardShowing(true) - val latest = collectLastValue(underTest.startButton) - repository.setBottomAreaAlpha(0f) - - val testConfig = - TestConfig( - isVisible = true, - isClickable = false, - icon = mock(), - canShowWhileLocked = false, - intent = Intent("action"), - slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), - ) - val configKey = - setUpQuickAffordanceModel( - position = KeyguardQuickAffordancePosition.BOTTOM_START, - testConfig = testConfig, - ) - - assertQuickAffordanceViewModel( - viewModel = latest(), - testConfig = testConfig, - configKey = configKey, - ) - } - - private suspend fun setUpQuickAffordanceModel( - position: KeyguardQuickAffordancePosition, - testConfig: TestConfig, - ): String { - val config = - when (position) { - KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig - KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig - } - - val lockScreenState = - if (testConfig.isVisible) { - if (testConfig.intent != null) { - config.onTriggeredResult = - KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity( - intent = testConfig.intent, - canShowWhileLocked = testConfig.canShowWhileLocked, - ) - } - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = testConfig.icon ?: error("Icon is unexpectedly null!"), - activationState = - when (testConfig.isActivated) { - true -> ActivationState.Active - false -> ActivationState.Inactive - } - ) - } else { - KeyguardQuickAffordanceConfig.LockScreenState.Hidden - } - config.setState(lockScreenState) - - return "${position.toSlotId()}::${config.key}" - } - - private fun assertQuickAffordanceViewModel( - viewModel: KeyguardQuickAffordanceViewModel?, - testConfig: TestConfig, - configKey: String, - ) { - checkNotNull(viewModel) - assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible) - assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable) - assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated) - assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected) - assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed) - assertThat(viewModel.slotId).isEqualTo(testConfig.slotId) - if (testConfig.isVisible) { - assertThat(viewModel.icon).isEqualTo(testConfig.icon) - viewModel.onClicked.invoke( - KeyguardQuickAffordanceViewModel.OnClickedParameters( - configKey = configKey, - expandable = expandable, - slotId = viewModel.slotId, - ) - ) - if (testConfig.intent != null) { - assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1) - } else { - verifyNoMoreInteractions(activityStarter) - } - } else { - assertThat(viewModel.isVisible).isFalse() - } - } - - private data class TestConfig( - val isVisible: Boolean, - val isClickable: Boolean = false, - val isActivated: Boolean = false, - val icon: Icon? = null, - val canShowWhileLocked: Boolean = false, - val intent: Intent? = null, - val isSelected: Boolean = false, - val isDimmed: Boolean = false, - val slotId: String = "" - ) { - init { - check(!isVisible || icon != null) { "Must supply non-null icon if visible!" } - } - } - - companion object { - private const val DEFAULT_BURN_IN_OFFSET = 5 - private const val RETURNED_BURN_IN_OFFSET = 3 - - @JvmStatic - @Parameters(name = "{0}") - fun getParams(): List<FlagsParameterization> { - return FlagsParameterization.allCombinationsOf().andSceneContainer() - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index cb2c8fc2c418..3364528f0b54 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -25,7 +25,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.keyguard.logging.KeyguardQuickAffordancesLogger -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable @@ -191,8 +190,6 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { dockManager = DockManagerFake() biometricSettingsRepository = FakeBiometricSettingsRepository() - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) - val featureFlags = FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index d8d6f2e9fbb0..330b887b70a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -4,6 +4,8 @@ import android.bluetooth.BluetoothDevice import android.os.Handler import android.os.Looper import android.os.UserManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.testing.TestableLooper @@ -20,10 +22,12 @@ import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.flags.QSComposeFragment +import com.android.systemui.qs.flags.QsDetailedView import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl @@ -35,6 +39,7 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlin.test.assertTrue import kotlinx.coroutines.Job import org.junit.After import org.junit.Before @@ -199,6 +204,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @DisableFlags(QsDetailedView.FLAG_NAME) fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() { mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) @@ -212,6 +218,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @DisableFlags(QsDetailedView.FLAG_NAME) fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() { mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) @@ -223,6 +230,7 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @DisableFlags(QsDetailedView.FLAG_NAME) fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() { mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) @@ -234,6 +242,35 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @EnableFlags(QsDetailedView.FLAG_NAME) + fun handleClick_hasSatelliteFeatureAndQsDetailedViewIsEnabledAndClickIsProcessing_doNothing() { + mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) + .thenReturn(false) + `when`(clickJob.isCompleted).thenReturn(false) + tile.mClickJob = clickJob + var currentModel: TileDetailsViewModel? = null + + tile.getDetailsViewModel { model: TileDetailsViewModel? -> currentModel = model } + + // Click is not allowed. + assertThat(currentModel).isEqualTo(null) + } + + @Test + @EnableFlags(QsDetailedView.FLAG_NAME) + fun handleClick_noSatelliteFeatureAndQsDetailedViewIsEnabled_returnDetailsViewModel() { + mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) + .thenReturn(false) + var currentModel: TileDetailsViewModel? = null + + tile.getDetailsViewModel { model: TileDetailsViewModel? -> currentModel = model } + + assertTrue(currentModel != null) + } + + @Test fun testMetadataListener_whenDisconnected_isUnregistered() { val state = QSTile.BooleanState() val cachedDevice = mock<CachedBluetoothDevice>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java index 300c9b843d1c..8560b67dee33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java @@ -35,6 +35,7 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -149,7 +150,8 @@ public class InternetDialogDelegateTest extends SysuiTestCase { mHandler, mBgExecutor, mKeyguard, - mSystemUIDialogFactory); + mSystemUIDialogFactory, + new FakeShadeDialogContextInteractor(mContext)); mInternetDialogDelegate.createDialog(); mInternetDialogDelegate.onCreate(mSystemUIDialog, null); mInternetDialogDelegate.mAdapter = mInternetAdapter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index a17f100904be..afff4858499a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -36,7 +36,6 @@ import android.app.Dialog; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.media.projection.StopReason; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -155,7 +154,7 @@ public class RecordingControllerTest extends SysuiTestCase { PendingIntent stopIntent = Mockito.mock(PendingIntent.class); mController.startCountdown(0, 0, startIntent, stopIntent); - mController.stopRecording(StopReason.STOP_UNKNOWN); + mController.stopRecording(); assertFalse(mController.isStarting()); assertFalse(mController.isRecording()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt index a0ecb802dd61..f695c13a9e62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt @@ -76,6 +76,8 @@ class UserTrackerImplTest : SysuiTestCase() { @Mock private lateinit var iActivityManager: IActivityManager + @Mock private lateinit var beforeUserSwitchingReply: IRemoteCallback + @Mock private lateinit var userSwitchingReply: IRemoteCallback @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager @@ -199,9 +201,10 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) - captor.value.onBeforeUserSwitching(newID) + captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply) captor.value.onUserSwitching(newID, userSwitchingReply) runCurrent() + verify(beforeUserSwitchingReply).sendResult(any()) verify(userSwitchingReply).sendResult(any()) verify(userManager).getProfiles(newID) @@ -341,10 +344,11 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) - captor.value.onBeforeUserSwitching(newID) + captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply) captor.value.onUserSwitching(newID, userSwitchingReply) runCurrent() + verify(beforeUserSwitchingReply).sendResult(any()) verify(userSwitchingReply).sendResult(any()) assertThat(callback.calledOnUserChanging).isEqualTo(1) assertThat(callback.lastUser).isEqualTo(newID) @@ -395,7 +399,7 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) - captor.value.onBeforeUserSwitching(newID) + captor.value.onBeforeUserSwitching(newID, any()) captor.value.onUserSwitchComplete(newID) runCurrent() @@ -453,8 +457,10 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply) captor.value.onUserSwitching(newID, userSwitchingReply) runCurrent() + verify(beforeUserSwitchingReply).sendResult(any()) verify(userSwitchingReply).sendResult(any()) captor.value.onUserSwitchComplete(newID) @@ -488,6 +494,7 @@ class UserTrackerImplTest : SysuiTestCase() { } private class TestCallback : UserTracker.Callback { + var calledOnBeforeUserChanging = 0 var calledOnUserChanging = 0 var calledOnUserChanged = 0 var calledOnProfilesChanged = 0 @@ -495,6 +502,11 @@ class UserTrackerImplTest : SysuiTestCase() { var lastUserContext: Context? = null var lastUserProfiles = emptyList<UserInfo>() + override fun onBeforeUserSwitching(newUser: Int) { + calledOnBeforeUserChanging++ + lastUser = newUser + } + override fun onUserChanging(newUser: Int, userContext: Context) { calledOnUserChanging++ lastUser = newUser 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 041d1a611b55..4b11e2c24722 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.shade import android.content.Context -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.FlagsParameterization @@ -32,7 +31,6 @@ import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityContainerController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.Flags -import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor @@ -406,18 +404,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @DisableSceneContainer - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_called() { - underTest.setStatusBarViewController(phoneStatusBarViewController) - - interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) - - verify(centralSurfaces, times(0)).userActivity() - } - - @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun handleDispatchTouchEvent_nsslMigrationOn_userActivity() { underTest.setStatusBarViewController(phoneStatusBarViewController) @@ -438,7 +424,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -451,7 +436,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -464,7 +448,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -609,7 +592,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun cancelCurrentTouch_callsDragDownHelper() { underTest.cancelCurrentTouch() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index 13bc82fa2c70..a04ca038021e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade -import android.platform.test.annotations.DisableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -27,7 +26,6 @@ import androidx.annotation.IdRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService @@ -65,10 +63,7 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -/** - * Uses Flags.KEYGUARD_STATUS_VIEW_MIGRATE_NSSL set to false. If all goes well, this set of tests - * will be deleted. - */ +/** NotificationsQSContainerController tests */ @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest @@ -122,7 +117,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { delayableExecutor, notificationStackScrollLayoutController, ResourcesSplitShadeStateController(), - largeScreenHeaderHelperLazy = { largeScreenHeaderHelper } + largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }, ) overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN) @@ -209,23 +204,23 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = true, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = 0, // taskbar should disappear when shade is expanded expectedNotificationsMargin = NOTIFICATIONS_MARGIN, - expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) given( taskbarVisible = true, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = STABLE_INSET_BOTTOM, expectedNotificationsMargin = NOTIFICATIONS_MARGIN, - expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) } @@ -237,22 +232,22 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = 0, - expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) given( taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN, - expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) } @@ -263,22 +258,22 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withCutout() + insets = windowInsets().withCutout(), ) then( expectedContainerPadding = CUTOUT_HEIGHT, - expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) given( taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withCutout().withStableBottom() + insets = windowInsets().withCutout().withStableBottom(), ) then( expectedContainerPadding = 0, expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN, - expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET + expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET, ) } @@ -289,18 +284,18 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = true, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM) given( taskbarVisible = true, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = STABLE_INSET_BOTTOM, - expectedQsPadding = STABLE_INSET_BOTTOM + expectedQsPadding = STABLE_INSET_BOTTOM, ) } @@ -314,19 +309,19 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withCutout().withStableBottom() + insets = windowInsets().withCutout().withStableBottom(), ) then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM) given( taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then( expectedContainerPadding = 0, expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN, - expectedQsPadding = STABLE_INSET_BOTTOM + expectedQsPadding = STABLE_INSET_BOTTOM, ) } @@ -339,7 +334,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then(expectedContainerPadding = 0, expectedNotificationsMargin = 0) @@ -355,7 +350,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { given( taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, - insets = windowInsets().withStableBottom() + insets = windowInsets().withStableBottom(), ) then(expectedContainerPadding = 0) @@ -376,43 +371,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSplitShadeLayout_isAlignedToGuideline() { - enableSplitShade() - underTest.updateResources() - assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart) - .isEqualTo(R.id.qs_edge_guideline) - } - - @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSinglePaneLayout_childrenHaveEqualMargins() { - disableSplitShade() - underTest.updateResources() - val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin - val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin - val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin - val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin - assertThat( - qsStartMargin == qsEndMargin && - notifStartMargin == notifEndMargin && - qsStartMargin == notifStartMargin - ) - .isTrue() - } - - @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() { - enableSplitShade() - underTest.updateResources() - assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin) - .isEqualTo(0) - } - - @Test fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() { enableSplitShade() underTest.updateResources() @@ -421,37 +379,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeightHelper() { - setLargeScreen() - val largeScreenHeaderResourceHeight = 100 - val largeScreenHeaderHelperHeight = 200 - whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) - .thenReturn(largeScreenHeaderHelperHeight) - overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight) - - // ensure the estimated height (would be 30 here) wouldn't impact this test case - overrideResource(R.dimen.large_screen_shade_header_min_height, 10) - overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10) - - underTest.updateResources() - - assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin) - .isEqualTo(largeScreenHeaderHelperHeight) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin) - .isEqualTo(largeScreenHeaderHelperHeight) - } - - @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() { - setSmallScreen() - underTest.updateResources() - assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0) - } - - @Test fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() { disableSplitShade() underTest.updateResources() @@ -464,17 +391,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun testSinglePaneShadeLayout_isAlignedToParent() { - disableSplitShade() - underTest.updateResources() - assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) - .isEqualTo(ConstraintSet.PARENT_ID) - assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart) - .isEqualTo(ConstraintSet.PARENT_ID) - } - - @Test fun testAllChildrenOfNotificationContainer_haveIds() { // set dimen to 0 to avoid triggering updating bottom spacing overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0) @@ -493,7 +409,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { delayableExecutor, notificationStackScrollLayoutController, ResourcesSplitShadeStateController(), - largeScreenHeaderHelperLazy = { largeScreenHeaderHelper } + largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }, ) controller.updateConstraints() @@ -509,7 +425,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets(), - applyImmediately = false + applyImmediately = false, ) fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2) windowInsetsCallback.accept(windowInsets().withStableBottom()) @@ -576,7 +492,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { taskbarVisible: Boolean, navigationMode: Int, insets: WindowInsets, - applyImmediately: Boolean = true + applyImmediately: Boolean = true, ) { Mockito.clearInvocations(view) taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false) @@ -591,7 +507,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { fun then( expectedContainerPadding: Int, expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN, - expectedQsPadding: Int = 0 + expectedQsPadding: Int = 0, ) { verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding)) verify(view).setNotificationsMarginBottom(expectedNotificationsMargin) @@ -623,7 +539,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { val layoutParams = ConstraintLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, ) // required as cloning ConstraintSet fails if view doesn't have layout params view.layoutParams = layoutParams diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index a91fb45c9c80..e1a891662889 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -1544,14 +1544,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { assertFalse(mStackScroller.mHeadsUpAnimatingAway); } - @Test - @EnableSceneContainer - public void finishExpanding_sceneContainerEnabled() { - mStackScroller.startOverscrollAfterExpanding(); - verify(mStackScroller.getExpandHelper()).finishExpanding(); - assertTrue(mStackScroller.getIsBeingDragged()); - } - private MotionEvent captureTouchSentToSceneFramework() { ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class); verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture()); diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt index 76fc61185b49..25d1c377ecbd 100644 --- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt +++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt @@ -433,6 +433,8 @@ class FakeStatusBarService : IStatusBarService.Stub() { override fun showRearDisplayDialog(currentBaseState: Int) {} + override fun unbundleNotification(key: String) {} + companion object { const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY const val SECONDARY_DISPLAY_ID = 2 diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt index 4d74254cf9f8..487049740079 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.common.ui.data.repository import android.content.res.Configuration +import android.view.Display import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module @@ -25,6 +26,7 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow @@ -46,6 +48,10 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor override val configurationValues: Flow<Configuration> = _configurationChangeValues.asSharedFlow() + private val _onMovedToDisplay = MutableStateFlow<Int>(Display.DEFAULT_DISPLAY) + override val onMovedToDisplay: StateFlow<Int> + get() = _onMovedToDisplay + private val _scaleForResolution = MutableStateFlow(1f) override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow() @@ -64,6 +70,10 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor onAnyConfigurationChange() } + fun onMovedToDisplay(newDisplayId: Int) { + _onMovedToDisplay.value = newDisplayId + } + fun setScaleForResolution(scale: Float) { _scaleForResolution.value = scale } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index ad92b318b0b9..bfc424848900 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -86,12 +86,8 @@ suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) { } suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean) { - setCommunalV2ConfigEnabled(true) - if (enabled) { - fakeUserRepository.asMainUser() - } else { - fakeUserRepository.asDefaultUser() - } + setCommunalV2ConfigEnabled(enabled) + setCommunalEnabled(enabled) } suspend fun Kosmos.setCommunalAvailable(available: Boolean) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt index b407b1ba227a..e3cfb80489e3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt @@ -17,8 +17,10 @@ package com.android.systemui.communal.ui.viewmodel import android.service.dream.dreamManager +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.policy.batteryController val Kosmos.communalToDreamButtonViewModel by @@ -26,6 +28,8 @@ val Kosmos.communalToDreamButtonViewModel by CommunalToDreamButtonViewModel( backgroundContext = testDispatcher, batteryController = batteryController, + settingsInteractor = communalSettingsInteractor, + activityStarter = activityStarter, dreamManager = dreamManager, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt index 534ded57eb85..9012393badd6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt @@ -58,4 +58,26 @@ class FakeDisplayWindowPropertiesRepository(private val context: Context) : fun insert(instance: DisplayWindowProperties) { properties.put(instance.displayId, instance.windowType, instance) } + + /** inserts an entry, mocking everything except the context. */ + fun insertForContext(displayId: Int, windowType: Int, context: Context) { + properties.put( + displayId, + windowType, + DisplayWindowProperties( + displayId = displayId, + windowType = windowType, + context = context, + windowManager = mock(), + layoutInflater = mock(), + ), + ) + } + + /** Whether the repository contains an entry already. */ + fun contains(displayId: Int, windowType: Int): Boolean = + properties.contains(displayId, windowType) + + /** Removes all the entries. */ + fun clear() = properties.clear() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt index 41402badf141..4513cc086513 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt @@ -17,7 +17,6 @@ package com.android.systemui.flags import android.platform.test.annotations.EnableFlags -import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN @@ -29,7 +28,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER * that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites. */ @EnableFlags( - FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 4cb8a416124f..2641070a1a59 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -22,12 +22,14 @@ import android.content.res.mainResources import android.hardware.input.fakeInputManager import android.view.windowManager import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.keyboard.shortcut.data.repository.AppLaunchDataRepository import com.android.systemui.keyboard.shortcut.data.repository.CustomInputGesturesRepository import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.data.repository.InputGestureDataAdapter import com.android.systemui.keyboard.shortcut.data.repository.InputGestureMaps import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperInputDeviceRepository import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource @@ -47,6 +49,7 @@ import com.android.systemui.keyguard.data.repository.fakeCommandQueue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.backgroundCoroutineContext +import com.android.systemui.kosmos.backgroundScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.model.sysUiState @@ -99,35 +102,54 @@ val Kosmos.defaultShortcutCategoriesRepository by Kosmos.Fixture { DefaultShortcutCategoriesRepository( applicationCoroutineScope, - testDispatcher, shortcutHelperSystemShortcutsSource, shortcutHelperMultiTaskingShortcutsSource, shortcutHelperAppCategoriesShortcutsSource, shortcutHelperInputShortcutsSource, shortcutHelperCurrentAppShortcutsSource, - fakeInputManager.inputManager, - shortcutHelperStateRepository, + shortcutHelperInputDeviceRepository, shortcutCategoriesUtils, ) } val Kosmos.inputGestureMaps by Kosmos.Fixture { InputGestureMaps(applicationContext) } -val Kosmos.inputGestureDataAdapter by Kosmos.Fixture { InputGestureDataAdapter(userTracker, inputGestureMaps, applicationContext)} +val Kosmos.inputGestureDataAdapter by + Kosmos.Fixture { InputGestureDataAdapter(userTracker, inputGestureMaps, applicationContext) } val Kosmos.customInputGesturesRepository by Kosmos.Fixture { CustomInputGesturesRepository(userTracker, testDispatcher) } +val Kosmos.shortcutHelperInputDeviceRepository by + Kosmos.Fixture { + ShortcutHelperInputDeviceRepository( + shortcutHelperStateRepository, + backgroundScope, + backgroundCoroutineContext, + fakeInputManager.inputManager, + ) + } + +val Kosmos.appLaunchDataRepository by + Kosmos.Fixture { + AppLaunchDataRepository( + fakeInputManager.inputManager, + backgroundScope, + shortcutCategoriesUtils, + shortcutHelperInputDeviceRepository, + ) + } + val Kosmos.customShortcutCategoriesRepository by Kosmos.Fixture { CustomShortcutCategoriesRepository( - shortcutHelperStateRepository, + shortcutHelperInputDeviceRepository, applicationCoroutineScope, - testDispatcher, shortcutCategoriesUtils, inputGestureDataAdapter, customInputGesturesRepository, fakeInputManager.inputManager, + appLaunchDataRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 693ec7954d70..1288d3151051 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -56,9 +56,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { override val animateBottomAreaDozingTransitions: StateFlow<Boolean> = _animateBottomAreaDozingTransitions - private val _bottomAreaAlpha = MutableStateFlow(1f) - override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha - private val _isKeyguardShowing = MutableStateFlow(false) override val isKeyguardShowing: StateFlow<Boolean> = _isKeyguardShowing @@ -159,11 +156,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _animateBottomAreaDozingTransitions.tryEmit(animate) } - @Deprecated("Deprecated as part of b/278057014") - override fun setBottomAreaAlpha(alpha: Float) { - _bottomAreaAlpha.value = alpha - } - fun setKeyguardShowing(isShowing: Boolean) { _isKeyguardShowing.value = isShowing } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt index 4aa132c1d3af..8c9163dd3b21 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt @@ -43,5 +43,6 @@ val Kosmos.keyguardWakeDirectlyToGoneInteractor by selectedUserInteractor, keyguardEnabledInteractor, keyguardServiceLockNowInteractor, + keyguardInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt index 2d1f836d455d..b03624bcc294 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt @@ -26,5 +26,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel by Fixture { AlternateBouncerToPrimaryBouncerTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, + shadeDependentFlows = shadeDependentFlows, ) } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerViewModelKosmos.kt index c5012b01dd3e..5e6d605e9bfb 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerViewModelKosmos.kt @@ -14,22 +14,14 @@ * limitations under the License. */ -package com.android.keyguard +package com.android.systemui.keyguard.ui.viewmodel -import android.view.MotionEvent -import android.view.View +import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi -/** Controls the [LockIconView]. */ -interface LockIconViewController { - fun setLockIconView(lockIconView: View) - - fun getTop(): Float - - fun getBottom(): Float - - fun dozeTimeTick() - - fun setAlpha(alpha: Float) - - fun willHandleTouchWhileDozing(event: MotionEvent): Boolean +@OptIn(ExperimentalCoroutinesApi::class) +val Kosmos.aodToPrimaryBouncerTransitionViewModel by Fixture { + AodToPrimaryBouncerTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 3ab686da1a6c..e47310727905 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -51,6 +51,8 @@ val Kosmos.keyguardRootViewModel by Fixture { alternateBouncerToLockscreenTransitionViewModel, alternateBouncerToOccludedTransitionViewModel = alternateBouncerToOccludedTransitionViewModel, + alternateBouncerToPrimaryBouncerTransitionViewModel = + alternateBouncerToPrimaryBouncerTransitionViewModel, aodToGoneTransitionViewModel = aodToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelKosmos.kt index a3955f7634eb..09233af7bae6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.android.systemui.keyguard.domain.interactor +package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -val Kosmos.keyguardBottomAreaInteractor by Fixture { - KeyguardBottomAreaInteractor( - repository = keyguardRepository, +val Kosmos.primaryBouncerToGlanceableHubTransitionViewModel by Fixture { + PrimaryBouncerToGlanceableHubTransitionViewModel( + animationFlow = keyguardTransitionAnimationFlow ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt index 370afc3b660b..76478cb43361 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt @@ -26,5 +26,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.primaryBouncerToLockscreenTransitionViewModel by Fixture { PrimaryBouncerToLockscreenTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, + shadeDependentFlows = shadeDependentFlows, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt index d941fb049835..43835607c77d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.mockito.kotlin.verify var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() } @@ -82,6 +83,32 @@ fun <T> TestScope.currentValue(stateFlow: StateFlow<T>): T { } /** Retrieve the current value of this [StateFlow] safely. See `currentValue(TestScope)`. */ +fun <T> Kosmos.currentValue(fn: () -> T) = testScope.currentValue(fn) + +/** + * Retrieve the result of [fn] after running all pending tasks. Do not use to retrieve the value of + * a flow directly; for that, use either `currentValue(StateFlow)` or [collectLastValue] + */ +@OptIn(ExperimentalCoroutinesApi::class) +fun <T> TestScope.currentValue(fn: () -> T): T { + runCurrent() + return fn() +} + +/** Retrieve the result of [fn] after running all pending tasks. See `TestScope.currentValue(fn)` */ fun <T> Kosmos.currentValue(stateFlow: StateFlow<T>): T { return testScope.currentValue(stateFlow) } + +/** Safely verify that a mock has been called after the test scope has caught up */ +@OptIn(ExperimentalCoroutinesApi::class) +fun <T> TestScope.verifyCurrent(mock: T): T { + runCurrent() + return verify(mock) +} + +/** + * Safely verify that a mock has been called after the test scope has caught up. See + * `TestScope.verifyCurrent` + */ +fun <T> Kosmos.verifyCurrent(mock: T) = testScope.verifyCurrent(mock) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 41cfceaa5e38..39f1ad42797b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -63,6 +63,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.scrimStartable import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.shared.model.sceneDataSource +import com.android.systemui.scene.ui.view.mockWindowRootViewProvider import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -191,4 +192,5 @@ class KosmosJavaAdapter() { } val disableFlagsInteractor by lazy { kosmos.disableFlagsInteractor } val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository } + val mockWindowRootViewProvider by lazy { kosmos.mockWindowRootViewProvider } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt index d5637cbe36b5..8aa7a03710cb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt @@ -16,7 +16,6 @@ package com.android.systemui.mediarouter.data.repository -import android.media.projection.StopReason import com.android.systemui.statusbar.policy.CastDevice import kotlinx.coroutines.flow.MutableStateFlow @@ -26,7 +25,7 @@ class FakeMediaRouterRepository : MediaRouterRepository { var lastStoppedDevice: CastDevice? = null private set - override fun stopCasting(device: CastDevice, @StopReason stopReason: Int) { + override fun stopCasting(device: CastDevice) { lastStoppedDevice = device } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt index 49bbbef6ccc0..0ec8d49ec29b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt @@ -17,6 +17,6 @@ package com.android.systemui.plugins import com.android.systemui.kosmos.Kosmos -import org.mockito.kotlin.mock +import com.android.systemui.util.mockito.mock var Kosmos.activityStarter by Kosmos.Fixture { mock<ActivityStarter>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt index 06822a6c2339..4714969af508 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt @@ -19,7 +19,6 @@ package com.android.systemui.qs import com.android.internal.logging.InstanceId import com.android.systemui.animation.Expandable import com.android.systemui.plugins.qs.QSTile -import com.android.systemui.plugins.qs.TileDetailsViewModel class FakeQSTile(var user: Int, var available: Boolean = true) : QSTile { private var tileSpec: String? = null diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt index d72630dc2f03..01e357e1d0c8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs import android.app.admin.devicePolicyManager import android.content.applicationContext +import android.content.mockedContext import android.os.fakeExecutorHandler import android.os.looper import com.android.internal.logging.metricsLogger @@ -54,9 +55,7 @@ var Kosmos.qsTileFactory by Fixture<QSFactory> { FakeQSFactory(::tileCreator) } val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() } val Kosmos.footerActionsController by Fixture { - FooterActionsController( - fgsManagerController = fgsManagerController, - ) + FooterActionsController(fgsManagerController = fgsManagerController) } val Kosmos.qsSecurityFooterUtils by Fixture { @@ -86,6 +85,7 @@ val Kosmos.footerActionsInteractor by Fixture { userSwitcherRepository = userSwitcherRepository, broadcastDispatcher = broadcastDispatcher, bgDispatcher = testDispatcher, + context = mockedContext, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt index cde5d4ec5931..9edeb0cb1e4e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt @@ -69,6 +69,7 @@ class FooterActionsTestUtils( private val scheduler: TestCoroutineScheduler, ) { private val mockActivityStarter: ActivityStarter = mock<ActivityStarter>() + /** Enable or disable the user switcher in the settings. */ fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean) { settings.putBool(Settings.Global.USER_SWITCHER_ENABLED, enabled) @@ -110,6 +111,7 @@ class FooterActionsTestUtils( userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(), broadcastDispatcher: BroadcastDispatcher = mock(), bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler), + context: Context = mock(), ): FooterActionsInteractor { return FooterActionsInteractorImpl( activityStarter, @@ -124,6 +126,7 @@ class FooterActionsTestUtils( userSwitcherRepository, broadcastDispatcher, bgDispatcher, + context, ) } @@ -132,15 +135,12 @@ class FooterActionsTestUtils( securityController: SecurityController = FakeSecurityController(), bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler), ): SecurityRepository { - return SecurityRepositoryImpl( - securityController, - bgDispatcher, - ) + return SecurityRepositoryImpl(securityController, bgDispatcher) } /** Create a [SecurityRepository] to be used in tests. */ fun foregroundServicesRepository( - fgsManagerController: FakeFgsManagerController = FakeFgsManagerController(), + fgsManagerController: FakeFgsManagerController = FakeFgsManagerController() ): ForegroundServicesRepository { return ForegroundServicesRepositoryImpl(fgsManagerController) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt index f52572a9e42d..f5eebb46c2ec 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt @@ -19,13 +19,13 @@ package com.android.systemui.scene.shared.model import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey +import com.android.systemui.kosmos.currentValue import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.test.TestScope -class FakeSceneDataSource( - initialSceneKey: SceneKey, -) : SceneDataSource { +class FakeSceneDataSource(initialSceneKey: SceneKey, val testScope: TestScope) : SceneDataSource { private val _currentScene = MutableStateFlow(initialSceneKey) override val currentScene: StateFlow<SceneKey> = _currentScene.asStateFlow() @@ -33,18 +33,20 @@ class FakeSceneDataSource( private val _currentOverlays = MutableStateFlow<Set<OverlayKey>>(emptySet()) override val currentOverlays: StateFlow<Set<OverlayKey>> = _currentOverlays.asStateFlow() - var isPaused = false - private set + private var _isPaused = false + val isPaused + get() = testScope.currentValue { _isPaused } - var pendingScene: SceneKey? = null - private set + private var _pendingScene: SceneKey? = null + val pendingScene + get() = testScope.currentValue { _pendingScene } var pendingOverlays: Set<OverlayKey>? = null private set override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) { - if (isPaused) { - pendingScene = toScene + if (_isPaused) { + _pendingScene = toScene } else { _currentScene.value = toScene } @@ -55,7 +57,7 @@ class FakeSceneDataSource( } override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) { - if (isPaused) { + if (_isPaused) { pendingOverlays = (pendingOverlays ?: currentOverlays.value) + overlay } else { _currentOverlays.value += overlay @@ -63,7 +65,7 @@ class FakeSceneDataSource( } override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) { - if (isPaused) { + if (_isPaused) { pendingOverlays = (pendingOverlays ?: currentOverlays.value) - overlay } else { _currentOverlays.value -= overlay @@ -82,9 +84,9 @@ class FakeSceneDataSource( * last one will be remembered. */ fun pause() { - check(!isPaused) { "Can't pause what's already paused!" } + check(!_isPaused) { "Can't pause what's already paused!" } - isPaused = true + _isPaused = true } /** @@ -100,15 +102,12 @@ class FakeSceneDataSource( * * If [expectedScene] is provided, will assert that it's indeed the latest called. */ - fun unpause( - force: Boolean = false, - expectedScene: SceneKey? = null, - ) { - check(force || isPaused) { "Can't unpause what's already not paused!" } - - isPaused = false - pendingScene?.let { _currentScene.value = it } - pendingScene = null + fun unpause(force: Boolean = false, expectedScene: SceneKey? = null) { + check(force || _isPaused) { "Can't unpause what's already not paused!" } + + _isPaused = false + _pendingScene?.let { _currentScene.value = it } + _pendingScene = null pendingOverlays?.let { _currentOverlays.value = it } pendingOverlays = null diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt index f5196866ae6f..7eebfc305682 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt @@ -19,13 +19,12 @@ package com.android.systemui.scene.shared.model import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope import com.android.systemui.scene.initialSceneKey import com.android.systemui.scene.sceneContainerConfig val Kosmos.fakeSceneDataSource by Fixture { - FakeSceneDataSource( - initialSceneKey = initialSceneKey, - ) + FakeSceneDataSource(initialSceneKey = initialSceneKey, testScope = testScope) } val Kosmos.sceneDataSourceDelegator by Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt index 5c91dc80b3d4..e6ba9a581836 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt @@ -17,6 +17,9 @@ package com.android.systemui.scene.ui.view import com.android.systemui.kosmos.Kosmos +import javax.inject.Provider import org.mockito.kotlin.mock val Kosmos.mockShadeRootView by Kosmos.Fixture { mock<WindowRootView>() } +val Kosmos.mockWindowRootViewProvider by + Kosmos.Fixture { Provider<WindowRootView> { mock<WindowRootView>() } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt index 4c9e1740b3b5..30b4763118a7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt @@ -16,7 +16,6 @@ package com.android.systemui.screenrecord.data.repository -import android.media.projection.StopReason import com.android.systemui.screenrecord.data.model.ScreenRecordModel import kotlinx.coroutines.flow.MutableStateFlow @@ -26,7 +25,7 @@ class FakeScreenRecordRepository : ScreenRecordRepository { var stopRecordingInvoked = false - override suspend fun stopRecording(@StopReason stopReason: Int) { + override suspend fun stopRecording() { stopRecordingInvoked = true } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt index 4dcd2208b152..3ed730271bc3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt @@ -16,6 +16,14 @@ package com.android.systemui.shade.data.repository +import android.content.applicationContext import com.android.systemui.kosmos.Kosmos +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock val Kosmos.shadeAnimationRepository by Kosmos.Fixture { ShadeAnimationRepository() } +val Kosmos.shadeDialogContextInteractor by + Kosmos.Fixture { + mock<ShadeDialogContextInteractor> { on { context } doReturn applicationContext } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt index 00b788fa4a41..c0d2621fae23 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt @@ -30,7 +30,6 @@ val Kosmos.shadeLockscreenInteractor by backgroundScope = applicationCoroutineScope, shadeInteractor = shadeInteractorImpl, sceneInteractor = sceneInteractor, - lockIconViewController = mock(), shadeRepository = shadeRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt index 8865573c4030..e5a75d59468d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,8 @@ package com.android.systemui.statusbar import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import org.mockito.kotlin.mock - -var Kosmos.lockscreenShadeKeyguardTransitionController by Fixture { - mock<LockscreenShadeKeyguardTransitionController>() -} +import com.android.systemui.util.mockito.mock var Kosmos.lockscreenShadeKeyguardTransitionControllerFactory by Fixture { - LockscreenShadeKeyguardTransitionController.Factory { - lockscreenShadeKeyguardTransitionController - } + mock<LockscreenShadeKeyguardTransitionController.Factory>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt index fc52e454a1c6..27679804d11f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt @@ -18,12 +18,8 @@ package com.android.systemui.statusbar import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import org.mockito.kotlin.mock - -var Kosmos.lockscreenShadeQsTransitionController by Fixture { - mock<LockscreenShadeQsTransitionController>() -} +import com.android.systemui.util.mockito.mock var Kosmos.lockscreenShadeQsTransitionControllerFactory by Fixture { - LockscreenShadeQsTransitionController.Factory { lockscreenShadeQsTransitionController } + mock<LockscreenShadeQsTransitionController.Factory>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt index 5523bd68f692..43e39c05f6e9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt @@ -18,12 +18,8 @@ package com.android.systemui.statusbar import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import org.mockito.kotlin.mock - -var Kosmos.singleShadeLockScreenOverScroller by Fixture { - mock<SingleShadeLockScreenOverScroller>() -} +import com.android.systemui.util.mockito.mock var Kosmos.singleShadeLockScreenOverScrollerFactory by Fixture { - SingleShadeLockScreenOverScroller.Factory { _ -> singleShadeLockScreenOverScroller } + mock<SingleShadeLockScreenOverScroller.Factory>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt index e491dffb0ed5..017371a6cba8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt @@ -18,10 +18,8 @@ package com.android.systemui.statusbar import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import org.mockito.kotlin.mock - -var Kosmos.splitShadeLockScreenOverScroller by Fixture { mock<SplitShadeLockScreenOverScroller>() } +import com.android.systemui.util.mockito.mock var Kosmos.splitShadeLockScreenOverScrollerFactory by Fixture { - SplitShadeLockScreenOverScroller.Factory { _, _ -> splitShadeLockScreenOverScroller } + mock<SplitShadeLockScreenOverScroller.Factory>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt new file mode 100644 index 000000000000..680e0de22794 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.promoted + +import android.app.Notification +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import org.junit.Assert + +class FakePromotedNotificationContentExtractor : PromotedNotificationContentExtractor { + @JvmField + val contentForEntry = mutableMapOf<NotificationEntry, PromotedNotificationContentModel?>() + @JvmField val extractCalls = mutableListOf<Pair<NotificationEntry, Notification.Builder>>() + + override fun extractContent( + entry: NotificationEntry, + recoveredBuilder: Notification.Builder, + ): PromotedNotificationContentModel? { + extractCalls.add(entry to recoveredBuilder) + + if (contentForEntry.isEmpty()) { + // If *no* entries are set, just return null for everything. + return null + } else { + // If entries *are* set, fail on unexpected ones. + Assert.assertTrue(contentForEntry.containsKey(entry)) + return contentForEntry.get(entry) + } + } + + fun resetForEntry(entry: NotificationEntry, content: PromotedNotificationContentModel?) { + contentForEntry.clear() + contentForEntry.put(entry, content) + extractCalls.clear() + } + + fun verifyZeroExtractCalls() { + Assert.assertTrue(extractCalls.isEmpty()) + } + + fun verifyOneExtractCall() { + Assert.assertEquals(1, extractCalls.size) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt index 88caf6e2ba30..ea7b41d43871 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt @@ -17,11 +17,20 @@ package com.android.systemui.statusbar.notification.promoted import com.android.systemui.statusbar.notification.collection.NotificationEntry +import org.junit.Assert class FakePromotedNotificationsProvider : PromotedNotificationsProvider { val promotedEntries = mutableSetOf<NotificationEntry>() + val shouldPromoteForEntry = mutableMapOf<NotificationEntry, Boolean>() override fun shouldPromote(entry: NotificationEntry): Boolean { - return promotedEntries.contains(entry) + if (shouldPromoteForEntry.isEmpty()) { + // If *no* entries are set, just return false for everything. + return false + } else { + // If entries *are* set, fail on unexpected ones. + Assert.assertTrue(shouldPromoteForEntry.containsKey(entry)) + return shouldPromoteForEntry[entry] ?: false + } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt index 5e9f12b4b1cc..52c17c82fb12 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt @@ -21,7 +21,7 @@ import com.android.systemui.kosmos.Kosmos var Kosmos.promotedNotificationContentExtractor by Kosmos.Fixture { - PromotedNotificationContentExtractor( + PromotedNotificationContentExtractorImpl( promotedNotificationsProvider, applicationContext, promotedNotificationLogger, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt index 7126933154df..e739e82aa8a8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -65,7 +65,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.icon.IconBuilder import com.android.systemui.statusbar.notification.icon.IconManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProviderImpl import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.CoordinateOnClickListener @@ -222,16 +222,11 @@ class ExpandableNotificationRowBuilder( Mockito.mock(LauncherApps::class.java, STUB_ONLY), Mockito.mock(ConversationNotificationManager::class.java, STUB_ONLY), ) - - val promotedNotificationsProvider = PromotedNotificationsProviderImpl() - val promotedNotificationLog = logcatLogBuffer("PromotedNotifLog") - val promotedNotificationLogger = PromotedNotificationLogger(promotedNotificationLog) - val promotedNotificationContentExtractor = - PromotedNotificationContentExtractor( - promotedNotificationsProvider, + PromotedNotificationContentExtractorImpl( + PromotedNotificationsProviderImpl(), context, - promotedNotificationLogger, + PromotedNotificationLogger(logcatLogBuffer("PromotedNotifLog")), ) mContentBinder = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt index da6b2ae46d2d..2df0c7a5386e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.policy -import android.media.projection.StopReason import java.io.PrintWriter class FakeCastController : CastController { @@ -46,7 +45,7 @@ class FakeCastController : CastController { override fun startCasting(device: CastDevice?) {} - override fun stopCasting(device: CastDevice?, @StopReason stopReason: Int) { + override fun stopCasting(device: CastDevice?) { lastStoppedDevice = device } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt index 32191277c94a..13673d16bf3c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt @@ -27,6 +27,10 @@ class FakeConfigurationController @Inject constructor() : listeners.forEach { it.onConfigChanged(newConfiguration) } } + override fun dispatchOnMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration) { + listeners.forEach { it.onMovedToDisplay(newDisplayId, newConfiguration) } + } + override fun notifyThemeChanged() { listeners.forEach { it.onThemeChanged() } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt index 26642d4f7534..f19ac1e5a58d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.policy import com.android.systemui.kosmos.Kosmos -import org.mockito.kotlin.mock +import org.mockito.Mockito.mock var Kosmos.keyguardStateController: KeyguardStateController by - Kosmos.Fixture { mock<KeyguardStateController>() } + Kosmos.Fixture { mock(KeyguardStateController::class.java) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt index 932e768676cb..6c98d19db5d7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.animation.dialogTransitionAnimator import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.mainCoroutineContext import com.android.systemui.plugins.activityStarter +import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.phone.systemUIDialogFactory import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel import com.android.systemui.util.mockito.mock @@ -35,5 +36,6 @@ var Kosmos.modesDialogDelegate: ModesDialogDelegate by { modesDialogViewModel }, modesDialogEventLogger, mainCoroutineContext, + shadeDialogContextInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java index 111c40d49efc..9cf25e8df727 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java @@ -16,6 +16,8 @@ package com.android.systemui.utils.leaks; import android.content.res.Configuration; +import androidx.annotation.NonNull; + import com.android.systemui.statusbar.policy.ConfigurationController; public class FakeConfigurationController @@ -43,4 +45,10 @@ public class FakeConfigurationController public String getNightModeName() { return "undefined"; } + + @Override + public void dispatchOnMovedToDisplay(int newDisplayId, + @NonNull Configuration newConfiguration) { + + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java index 857dc8584be9..2249bc0b667f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java @@ -16,7 +16,6 @@ package com.android.systemui.utils.leaks; -import android.media.projection.StopReason; import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.CastController; @@ -52,7 +51,7 @@ public class LeakCheckerCastController extends BaseLeakChecker<Callback> impleme } @Override - public void stopCasting(CastDevice device, @StopReason int stopReason) { + public void stopCasting(CastDevice device) { } diff --git a/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java index 3638429f33fb..a760b12ac8a1 100644 --- a/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java +++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java @@ -161,7 +161,14 @@ public final class VcnTransportInfo implements TransportInfo, Parcelable { return 0; } - /** @hide */ + /** + * Create a copy of a {@link VcnTransportInfo} with some fields redacted based on the + * permissions held by the receiving app. + * + * @param redactions bitmask of redactions that needs to be performed on this instance. + * @return the copy of this instance with the necessary redactions. + * @hide + */ @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @Override diff --git a/services/Android.bp b/services/Android.bp index 3d512f782232..efd35ce8f1a3 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -244,13 +244,21 @@ soong_config_module_type { name: "system_java_library", module_type: "java_library", config_namespace: "system_services", - bool_variables: ["without_vibrator"], + variables: ["without_hal"], properties: ["vintf_fragment_modules"], } +soong_config_string_variable { + name: "without_hal", + values: [ + "vibrator", + "devicestate", + ], +} + vintf_fragment { - name: "manifest_services.xml", - src: "manifest_services.xml", + name: "manifest_services_android.frameworks.location.xml", + src: "manifest_services_android.frameworks.location.xml", } vintf_fragment { @@ -258,6 +266,11 @@ vintf_fragment { src: "manifest_services_android.frameworks.vibrator.xml", } +vintf_fragment { + name: "manifest_services_android.frameworks.devicestate.xml", + src: "manifest_services_android.frameworks.devicestate.xml", +} + system_java_library { name: "services", defaults: [ @@ -328,14 +341,24 @@ system_java_library { ], soong_config_variables: { - without_vibrator: { - vintf_fragment_modules: [ - "manifest_services.xml", - ], + without_hal: { + vibrator: { + vintf_fragment_modules: [ + "manifest_services_android.frameworks.location.xml", + "manifest_services_android.frameworks.devicestate.xml", + ], + }, + devicestate: { + vintf_fragment_modules: [ + "manifest_services_android.frameworks.location.xml", + "manifest_services_android.frameworks.vibrator.xml", + ], + }, conditions_default: { vintf_fragment_modules: [ - "manifest_services.xml", + "manifest_services_android.frameworks.location.xml", "manifest_services_android.frameworks.vibrator.xml", + "manifest_services_android.frameworks.devicestate.xml", ], }, }, diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index e1b6c9c5aa42..513357508ab3 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -245,22 +245,45 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo final boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE && !event.isCancelled(); + + // TODO(b/355499907): Receive and handle held key gestures, which can be used + // for continuous scaling and panning. In addition, handle multiple pan gestures + // at the same time (e.g. user may try to pan diagonally) reasonably, including + // decreasing diagonal movement by sqrt(2) to make it appear the same speed + // as non-diagonal movement. + + if (!complete) { + return false; + } + final int gestureType = event.getKeyGestureType(); final int displayId = isDisplayIdValid(event.getDisplayId()) ? event.getDisplayId() : Display.DEFAULT_DISPLAY; switch (gestureType) { case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN: - if (complete) { mAms.getMagnificationController().scaleMagnificationByStep( displayId, MagnificationController.ZOOM_DIRECTION_IN); - } return true; case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT: - if (complete) { mAms.getMagnificationController().scaleMagnificationByStep( displayId, MagnificationController.ZOOM_DIRECTION_OUT); - } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT: + mAms.getMagnificationController().panMagnificationByStep( + displayId, MagnificationController.PAN_DIRECTION_LEFT); + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT: + mAms.getMagnificationController().panMagnificationByStep( + displayId, MagnificationController.PAN_DIRECTION_RIGHT); + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP: + mAms.getMagnificationController().panMagnificationByStep( + displayId, MagnificationController.PAN_DIRECTION_UP); + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN: + mAms.getMagnificationController().panMagnificationByStep( + displayId, MagnificationController.PAN_DIRECTION_DOWN); return true; } return false; @@ -270,7 +293,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo public boolean isKeyGestureSupported(int gestureType) { return switch (gestureType) { case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN, - KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT -> true; + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN -> true; default -> false; }; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 5c1ad74fac93..37d045bf6422 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1057,17 +1057,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE); final int restoredFromSdk = intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, 0); + final int userId = + android.view.accessibility.Flags.restoreA11ySecureSettingsOnHsumDevice() + ? getSendingUserId() : UserHandle.USER_SYSTEM; switch (which) { case Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES -> { synchronized (mLock) { restoreEnabledAccessibilityServicesLocked( - previousValue, newValue, restoredFromSdk); + previousValue, newValue, restoredFromSdk, userId); } } case ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED -> { synchronized (mLock) { restoreLegacyDisplayMagnificationNavBarIfNeededLocked( - newValue, restoredFromSdk); + newValue, restoredFromSdk, userId); } } // Currently in SUW, the user can't see gesture shortcut option as the @@ -1078,7 +1081,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Settings.Secure.ACCESSIBILITY_QS_TARGETS, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> restoreShortcutTargets(newValue, - ShortcutUtils.convertToType(which)); + ShortcutUtils.convertToType(which), userId); } } } @@ -1144,10 +1147,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - // Called only during settings restore; currently supports only the owner user - // TODO: b/22388012 - private void restoreLegacyDisplayMagnificationNavBarIfNeededLocked(String newSetting, - int restoreFromSdkInt) { + // Called only during settings restore; currently supports only the main user + // TODO: http://b/374830726 + private void restoreLegacyDisplayMagnificationNavBarIfNeededLocked( + String newSetting, int restoreFromSdkInt, int userId) { if (restoreFromSdkInt >= Build.VERSION_CODES.R) { return; } @@ -1160,7 +1163,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } - final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); + final AccessibilityUserState userState = getUserStateLocked(userId); final Set<String> targetsFromSetting = new ArraySet<>(); readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userState.mUserId, str -> str, targetsFromSetting); @@ -2225,20 +2228,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub getMagnificationController().onUserRemoved(userId); } - // Called only during settings restore; currently supports only the owner user - // TODO: http://b/22388012 - void restoreEnabledAccessibilityServicesLocked(String oldSetting, String newSetting, - int restoreFromSdkInt) { + // Called only during settings restore; currently supports only the main user + // TODO: http://b/374830726 + void restoreEnabledAccessibilityServicesLocked( + String oldSetting, String newSetting, int restoreFromSdkInt, int userId) { readComponentNamesFromStringLocked(oldSetting, mTempComponentNameSet, false); readComponentNamesFromStringLocked(newSetting, mTempComponentNameSet, true); - AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); + AccessibilityUserState userState = getUserStateLocked(userId); userState.mEnabledServices.clear(); userState.mEnabledServices.addAll(mTempComponentNameSet); persistComponentNamesToSettingLocked( Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userState.mEnabledServices, - UserHandle.USER_SYSTEM); + userState.mUserId); onUserStateChangedLocked(userState); migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null, restoreFromSdkInt); } @@ -2247,21 +2250,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * User could configure accessibility shortcut during the SUW before restoring user data. * Merges the current value and the new value to make sure we don't lost the setting the user's * preferences of accessibility shortcut updated in SUW are not lost. - * Called only during settings restore; currently supports only the owner user. + * * <P> * Throws an exception if used with {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP}. * </P> - * TODO: http://b/22388012 */ - private void restoreShortcutTargets(String newValue, - @UserShortcutType int shortcutType) { + // Called only during settings restore; currently supports only the main user. + // TODO: http://b/374830726 + private void restoreShortcutTargets( + String newValue, @UserShortcutType int shortcutType, int userId) { assertNoTapShortcut(shortcutType); if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) { return; } synchronized (mLock) { - final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); + final AccessibilityUserState userState = getUserStateLocked(userId); final Set<String> mergedTargets = (shortcutType == HARDWARE) ? new ArraySet<>(ShortcutUtils.getShortcutTargetsFromSettings( mContext, shortcutType, userState.mUserId)) @@ -2295,7 +2299,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.updateShortcutTargetsLocked(mergedTargets, shortcutType); persistColonDelimitedSetToSettingLocked(ShortcutUtils.convertToKey(shortcutType), - UserHandle.USER_SYSTEM, mergedTargets, str -> str); + userState.mUserId, mergedTargets, str -> str); scheduleNotifyClientsOfServicesStateChangeLocked(userState); onUserStateChangedLocked(userState); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 058b2be5f4b3..2e131b696afc 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -35,14 +35,19 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.display.DisplayManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.util.DisplayMetrics; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.SparseDoubleArray; import android.util.SparseIntArray; import android.util.SparseLongArray; +import android.util.TypedValue; +import android.view.Display; import android.view.accessibility.MagnificationAnimationCallback; import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; @@ -102,6 +107,7 @@ public class MagnificationController implements MagnificationConnectionManager.C /** Whether the platform supports window magnification feature. */ private final boolean mSupportWindowMagnification; private final MagnificationScaleStepProvider mScaleStepProvider; + private final MagnificationPanStepProvider mPanStepProvider; private final Executor mBackgroundExecutor; @@ -132,7 +138,7 @@ public class MagnificationController implements MagnificationConnectionManager.C .UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray = new SparseArray<>(); - // Direction magnifier scale can be altered. + // Direction magnification scale can be altered. public static final int ZOOM_DIRECTION_IN = 0; public static final int ZOOM_DIRECTION_OUT = 1; @@ -140,6 +146,16 @@ public class MagnificationController implements MagnificationConnectionManager.C public @interface ZoomDirection { } + // Directions magnification center can be moved. + public static final int PAN_DIRECTION_LEFT = 0; + public static final int PAN_DIRECTION_RIGHT = 1; + public static final int PAN_DIRECTION_UP = 2; + public static final int PAN_DIRECTION_DOWN = 3; + + @IntDef({PAN_DIRECTION_LEFT, PAN_DIRECTION_RIGHT, PAN_DIRECTION_UP, PAN_DIRECTION_DOWN}) + public @interface PanDirection { + } + /** * A callback to inform the magnification transition result on the given display. */ @@ -188,6 +204,87 @@ public class MagnificationController implements MagnificationConnectionManager.C } } + /** + * An interface to configure how much the magnification center should be affected when panning + * in steps. + */ + public interface MagnificationPanStepProvider { + /** + * Calculate the next value based on the current scale. + * + * @param currentScale The current magnification scale value. + * @param displayId The displayId for the display being magnified. + * @return The next pan step value. + */ + float nextPanStep(float currentScale, int displayId); + } + + public static class DefaultMagnificationPanStepProvider implements + MagnificationPanStepProvider, DisplayManager.DisplayListener { + // We want panning to be 40 dip per keystroke at scale 2, and 1 dip per keystroke at scale + // 20. This can be defined using y = mx + b to get the slope and intercept. + // This works even if the device does not allow magnification up to 20x; we will still get + // a reasonable lineary ramp of panning movement for each scale step. + private static final float DEFAULT_SCALE = 2.0f; + private static final float PAN_STEP_AT_DEFAULT_SCALE_DIP = 40.0f; + private static final float SCALE_FOR_1_DIP_PAN = 20.0f; + + private SparseDoubleArray mPanStepSlopes; + private SparseDoubleArray mPanStepIntercepts; + + private final DisplayManager mDisplayManager; + + DefaultMagnificationPanStepProvider(Context context) { + mDisplayManager = context.getSystemService(DisplayManager.class); + mDisplayManager.registerDisplayListener(this, /*handler=*/null); + mPanStepSlopes = new SparseDoubleArray(); + mPanStepIntercepts = new SparseDoubleArray(); + } + + @Override + public void onDisplayAdded(int displayId) { + updateForDisplay(displayId); + } + + @Override + public void onDisplayChanged(int displayId) { + updateForDisplay(displayId); + } + + @Override + public void onDisplayRemoved(int displayId) { + mPanStepSlopes.delete(displayId); + mPanStepIntercepts.delete(displayId); + } + + @Override + public float nextPanStep(float currentScale, int displayId) { + if (mPanStepSlopes.indexOfKey(displayId) < 0) { + updateForDisplay(displayId); + } + return Math.max((float) (mPanStepSlopes.get(displayId) * currentScale + + mPanStepIntercepts.get(displayId)), 1); + } + + private void updateForDisplay(int displayId) { + Display display = mDisplayManager.getDisplay(displayId); + if (display == null) { + return; + } + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + final float panStepAtDefaultScaleInPx = TypedValue.convertDimensionToPixels( + TypedValue.COMPLEX_UNIT_DIP, PAN_STEP_AT_DEFAULT_SCALE_DIP, metrics); + final float panStepAtMaxScaleInPx = TypedValue.convertDimensionToPixels( + TypedValue.COMPLEX_UNIT_DIP, 1.0f, metrics); + final float panStepSlope = (panStepAtMaxScaleInPx - panStepAtDefaultScaleInPx) + / (SCALE_FOR_1_DIP_PAN - DEFAULT_SCALE); + mPanStepSlopes.put(displayId, panStepSlope); + mPanStepIntercepts.put(displayId, + panStepAtDefaultScaleInPx - panStepSlope * DEFAULT_SCALE); + } + } + public MagnificationController(AccessibilityManagerService ams, Object lock, Context context, MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) { @@ -201,6 +298,7 @@ public class MagnificationController implements MagnificationConnectionManager.C mSupportWindowMagnification = context.getPackageManager().hasSystemFeature( FEATURE_WINDOW_MAGNIFICATION); mScaleStepProvider = new DefaultMagnificationScaleStepProvider(); + mPanStepProvider = new DefaultMagnificationPanStepProvider(mContext); mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context); mAlwaysOnMagnificationFeatureFlag.addOnChangedListener( @@ -935,13 +1033,12 @@ public class MagnificationController implements MagnificationConnectionManager.C } /** - * Scales the magnifier on the given display one step in/out based on the zoomIn param. + * Scales the magnifier on the given display one step in/out based on the direction param. * * @param displayId The logical display id. * @param direction Whether the scale should be zoomed in or out. - * @return {@code true} if the magnification scale was affected. */ - public boolean scaleMagnificationByStep(int displayId, @ZoomDirection int direction) { + public void scaleMagnificationByStep(int displayId, @ZoomDirection int direction) { if (getFullScreenMagnificationController().isActivated(displayId)) { final float magnificationScale = getFullScreenMagnificationController().getScale( displayId); @@ -950,7 +1047,6 @@ public class MagnificationController implements MagnificationConnectionManager.C getFullScreenMagnificationController().setScaleAndCenter(displayId, nextMagnificationScale, Float.NaN, Float.NaN, true, MAGNIFICATION_GESTURE_HANDLER_ID); - return nextMagnificationScale != magnificationScale; } if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) { @@ -959,10 +1055,51 @@ public class MagnificationController implements MagnificationConnectionManager.C final float nextMagnificationScale = mScaleStepProvider.nextScaleStep( magnificationScale, direction); getMagnificationConnectionManager().setScale(displayId, nextMagnificationScale); - return nextMagnificationScale != magnificationScale; } + } - return false; + /** + * Pans the magnifier on the given display one step left/right/up/down based on the direction + * param. + * + * @param displayId The logical display id. + * @param direction Whether the direction should be left/right/up/down. + */ + public void panMagnificationByStep(int displayId, @PanDirection int direction) { + final boolean fullscreenActivated = + getFullScreenMagnificationController().isActivated(displayId); + final boolean windowActivated = + getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId); + if (!fullscreenActivated && !windowActivated) { + return; + } + + final float scale = fullscreenActivated + ? getFullScreenMagnificationController().getScale(displayId) + : getMagnificationConnectionManager().getScale(displayId); + final float step = mPanStepProvider.nextPanStep(scale, displayId); + + float offsetX = 0; + float offsetY = 0; + if (direction == PAN_DIRECTION_LEFT) { + offsetX = -step; + } else if (direction == PAN_DIRECTION_RIGHT) { + offsetX = step; + } else if (direction == PAN_DIRECTION_UP) { + offsetY = -step; + } else if (direction == PAN_DIRECTION_DOWN) { + offsetY = step; + } + + if (fullscreenActivated) { + final float centerX = getFullScreenMagnificationController().getCenterX(displayId); + final float centerY = getFullScreenMagnificationController().getCenterY(displayId); + getFullScreenMagnificationController().setScaleAndCenter(displayId, scale, + centerX + offsetX, centerY + offsetY, true, MAGNIFICATION_GESTURE_HANDLER_ID); + } else { + getMagnificationConnectionManager().moveWindowMagnification(displayId, offsetX, + offsetY); + } } private final class DisableMagnificationCallback implements diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig index 7c5cfa91ab8a..ec6c3b7ebf7a 100644 --- a/services/autofill/bugfixes.aconfig +++ b/services/autofill/bugfixes.aconfig @@ -74,6 +74,16 @@ flag { } flag { + name: "multiple_fill_history" + namespace: "autofill" + description: "Allows tracking per Session FillEventHistory. As a bugfix flag to guard against DeviceConfig flag" + bug: "365630157" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "add_session_id_to_client_state" namespace: "autofill" description: "Include the session id into the FillEventHistory events as part of ClientState" @@ -96,3 +106,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "add_accessibility_title_for_augmented_autofill_dropdown" + namespace: "autofill" + description: "Add accessibility title for augmented autofill dropdown" + bug: "375284244" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 0fa43ac7091b..11710c9d8a9b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -152,6 +152,15 @@ final class AutofillManagerServiceImpl @GuardedBy("mLock") private final SparseArray<Session> mSessions = new SparseArray<>(); + /** + * Cache of FillEventHistory for active Sessions. + * + * <p>New histories are added whenever a Session is created and are kept until Sessions are + * removed through removeSessionLocked() + */ + @GuardedBy("mLock") + private final SparseArray<FillEventHistory> mFillHistories = new SparseArray<>(); + /** The last selection */ @GuardedBy("mLock") private FillEventHistory mEventHistory; @@ -663,6 +672,10 @@ final class AutofillManagerServiceImpl flags, mInputMethodManagerInternal, isPrimaryCredential); mSessions.put(newSession.id, newSession); + if (Flags.multipleFillHistory() && !forAugmentedAutofillOnly) { + mFillHistories.put(newSession.id, new FillEventHistory(sessionId, null)); + } + return newSession; } @@ -756,6 +769,28 @@ final class AutofillManagerServiceImpl TAG, "removeSessionLocked(): removed " + sessionId); } + + FillEventHistory history = null; + + if (Flags.multipleFillHistory() && mFillHistories != null) { + history = mFillHistories.get(sessionId); + mFillHistories.delete(sessionId); + } + + if (mInfo == null || mInfo.getServiceInfo() == null) { + if (sVerbose) { + Slog.v(TAG, "removeSessionLocked(): early return because mInfo is null"); + } + return; + } + + if (mMaster == null) { + if (sVerbose) { + Slog.v(TAG, "removeSessionLocked(): early return because mMaster is null"); + } + return; + } + RemoteFillService remoteService = new RemoteFillService( getContext(), @@ -764,7 +799,8 @@ final class AutofillManagerServiceImpl /* callbacks= */ null, mMaster.isInstantServiceAllowed(), /* credentialAutofillService= */ null); - remoteService.onSessionDestroyed(null); + + remoteService.onSessionDestroyed(history); } } @@ -886,6 +922,10 @@ final class AutofillManagerServiceImpl } } mSessions.clear(); + if (Flags.multipleFillHistory()) { + mFillHistories.clear(); + } + for (int i = 0; i < remoteFillServices.size(); i++) { remoteFillServices.valueAt(i).destroy(); } @@ -944,60 +984,132 @@ final class AutofillManagerServiceImpl return true; } + @GuardedBy("mLock") + void addEventToHistory(String eventName, int sessionId, Event event) { + // For the singleton filleventhistory + if (isValidEventLocked(eventName, sessionId)) { + mEventHistory.addEvent(event); + } + + if (Flags.multipleFillHistory()) { + FillEventHistory history = mFillHistories.get(sessionId); + if (history != null) { + history.addEvent(event); + } else { + if (sVerbose) { + Slog.v(TAG, eventName + + " not logged because FillEventHistory is not tracked for: " + + sessionId); + } + } + } + } + /** * Updates the last fill selection when an authentication was selected. */ void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState, - int uiType, @Nullable AutofillId focusedId) { + int uiType, @Nullable AutofillId focusedId, boolean shouldAdd) { synchronized (mLock) { - if (isValidEventLocked("setAuthenticationSelected()", sessionId)) { - mEventHistory.addEvent( - new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null, - null, null, null, null, null, null, - NO_SAVE_UI_REASON_NONE, uiType, focusedId)); + + String methodName = "setAuthenticationSelected()"; + + if (!shouldAdd) { + if (sVerbose) { + Slog.v(TAG, methodName + " not logged because shouldAdd is false"); + } + return; } - } - } - /** - * Updates the last fill selection when an dataset authentication was selected. - */ - void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId, - @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId) { + Event event = + new Event( + Event.TYPE_AUTHENTICATION_SELECTED, + null, + clientState, + null, + null, + null, + null, + null, + null, + null, + null, + NO_SAVE_UI_REASON_NONE, + uiType, + focusedId); + + addEventToHistory(methodName, sessionId, event); + } + } + + /** Updates the last fill selection when a dataset authentication was selected. */ + void logDatasetAuthenticationSelected( + @Nullable String selectedDataset, + int sessionId, + @Nullable Bundle clientState, + int uiType, + @Nullable AutofillId focusedId, + boolean shouldAdd) { synchronized (mLock) { - if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) { - mEventHistory.addEvent( - new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset, - clientState, null, null, null, null, null, null, null, null, - NO_SAVE_UI_REASON_NONE, uiType, focusedId)); + String methodName = "logDatasetAuthenticationSelected()"; + + if (!shouldAdd) { + if (sVerbose) { + Slog.v(TAG, methodName + " not logged because shouldAdd is false"); + } + return; } + + Event event = new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset, + clientState, null, null, null, null, null, null, null, null, + NO_SAVE_UI_REASON_NONE, uiType, focusedId); + addEventToHistory(methodName, sessionId, event); } } /** * Updates the last fill selection when an save Ui is shown. */ - void logSaveShown(int sessionId, @Nullable Bundle clientState) { + void logSaveShown(int sessionId, @Nullable Bundle clientState, boolean shouldAdd) { synchronized (mLock) { - if (isValidEventLocked("logSaveShown()", sessionId)) { - mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null, - null, null, null, null, null, null, null, /* focusedId= */ null)); + String methodName = "logSaveShown()"; + + if (!shouldAdd) { + if (sVerbose) { + Slog.v(TAG, methodName + " not logged because shouldAdd is false"); + } + return; } + + Event event = new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null, + null, null, null, null, null, null, null, /* focusedId= */ null); + + addEventToHistory(methodName, sessionId, event); } } - /** - * Updates the last fill response when a dataset was selected. - */ - void logDatasetSelected(@Nullable String selectedDataset, int sessionId, - @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId) { + /** Updates the last fill response when a dataset was selected. */ + void logDatasetSelected( + @Nullable String selectedDataset, + int sessionId, + @Nullable Bundle clientState, + int uiType, + @Nullable AutofillId focusedId, + boolean shouldAdd) { synchronized (mLock) { - if (isValidEventLocked("logDatasetSelected()", sessionId)) { - mEventHistory.addEvent( - new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null, - null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE, - uiType, focusedId)); + String methodName = "logDatasetSelected()"; + + if (!shouldAdd) { + if (sVerbose) { + Slog.v(TAG, methodName + " not logged because shouldAdd is false"); + } + return; } + + Event event = new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null, + null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE, + uiType, focusedId); + addEventToHistory(methodName, sessionId, event); } } @@ -1005,40 +1117,75 @@ final class AutofillManagerServiceImpl * Updates the last fill response when a dataset is shown. */ void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType, - @Nullable AutofillId focusedId) { + @Nullable AutofillId focusedId, boolean shouldAdd) { synchronized (mLock) { - if (isValidEventLocked("logDatasetShown", sessionId)) { - mEventHistory.addEvent( - new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null, + String methodName = "logDatasetShown()"; + + if (!shouldAdd) { + if (sVerbose) { + Slog.v(TAG, methodName + " not logged because shouldAdd is false"); + } + return; + } + + Event event = new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE, - uiType, focusedId)); + uiType, focusedId); + addEventToHistory(methodName, sessionId, event); + } + } + + void logViewEnteredForHistory( + int sessionId, + @Nullable Bundle clientState, + FillEventHistory history, + @Nullable AutofillId focusedId) { + if (history.getEvents() != null) { + // Do not log this event more than once + for (Event event : history.getEvents()) { + if (event.getType() == Event.TYPE_VIEW_REQUESTED_AUTOFILL) { + if (sVerbose) { + Slog.v(TAG, "logViewEntered: already logged TYPE_VIEW_REQUESTED_AUTOFILL"); + } + return; + } } } + + history.addEvent( + new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null, + null, null, null, null, null, null, null, focusedId)); } /** * Updates the last fill response when a view was entered. */ void logViewEntered(int sessionId, @Nullable Bundle clientState, - @Nullable AutofillId focusedId) { + @Nullable AutofillId focusedId, boolean shouldAdd) { synchronized (mLock) { - if (!isValidEventLocked("logViewEntered", sessionId)) { + String methodName = "logViewEntered()"; + + if (!shouldAdd) { + if (sVerbose) { + Slog.v(TAG, methodName + " not logged because shouldAdd is false"); + } return; } - if (mEventHistory.getEvents() != null) { - // Do not log this event more than once - for (Event event : mEventHistory.getEvents()) { - if (event.getType() == Event.TYPE_VIEW_REQUESTED_AUTOFILL) { - Slog.v(TAG, "logViewEntered: already logged TYPE_VIEW_REQUESTED_AUTOFILL"); - return; - } - } + // This log does not call addEventToHistory() because each distinct FillEventHistory + // can only contain 1 TYPE_VIEW_REQUESTED_AUTOFILL event. Therefore, checking both + // the singleton FillEventHistory and the per Session FillEventHistory is necessary + + if (isValidEventLocked(methodName, sessionId)) { + logViewEnteredForHistory(sessionId, clientState, mEventHistory, focusedId); } - mEventHistory.addEvent( - new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null, - null, null, null, null, null, null, null, focusedId)); + if (Flags.multipleFillHistory()) { + FillEventHistory history = mFillHistories.get(sessionId); + if (history != null) { + logViewEnteredForHistory(sessionId, clientState, history, focusedId); + } + } } } @@ -1096,12 +1243,12 @@ final class AutofillManagerServiceImpl @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, - @NonNull ComponentName appComponentName, boolean compatMode) { + @NonNull ComponentName appComponentName, boolean compatMode, boolean shouldAdd) { logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, /* detectedFieldIdsList= */ null, /* detectedFieldClassificationsList= */ null, appComponentName, compatMode, - Event.NO_SAVE_UI_REASON_NONE); + Event.NO_SAVE_UI_REASON_NONE, shouldAdd); } @GuardedBy("mLock") @@ -1115,9 +1262,19 @@ final class AutofillManagerServiceImpl @Nullable ArrayList<AutofillId> detectedFieldIdsList, @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList, @NonNull ComponentName appComponentName, boolean compatMode, - @NoSaveReason int saveDialogNotShowReason) { - if (isValidEventLocked("logDatasetNotSelected()", sessionId)) { + @NoSaveReason int saveDialogNotShowReason, + boolean shouldAdd) { + + String methodName = "logContextCommittedLocked()"; + + if (!shouldAdd) { if (sVerbose) { + Slog.v(TAG, methodName + " not logged because shouldAdd is false"); + } + return; + } + + if (sVerbose) { Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId + ", selectedDatasets=" + selectedDatasets + ", ignoredDatasetIds=" + ignoredDatasets @@ -1129,44 +1286,58 @@ final class AutofillManagerServiceImpl + ", appComponentName=" + appComponentName.toShortString() + ", compatMode=" + compatMode + ", saveDialogNotShowReason=" + saveDialogNotShowReason); - } - AutofillId[] detectedFieldsIds = null; - FieldClassification[] detectedFieldClassifications = null; - if (detectedFieldIdsList != null) { - detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()]; - detectedFieldIdsList.toArray(detectedFieldsIds); - detectedFieldClassifications = - new FieldClassification[detectedFieldClassificationsList.size()]; - detectedFieldClassificationsList.toArray(detectedFieldClassifications); - - final int numberFields = detectedFieldsIds.length; - int totalSize = 0; - float totalScore = 0; - for (int i = 0; i < numberFields; i++) { - final FieldClassification fc = detectedFieldClassifications[i]; - final List<Match> matches = fc.getMatches(); - final int size = matches.size(); - totalSize += size; - for (int j = 0; j < size; j++) { - totalScore += matches.get(j).getScore(); - } - } + } - final int averageScore = (int) ((totalScore * 100) / totalSize); - mMetricsLogger.write(Helper - .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES, - appComponentName, getServicePackageName(), sessionId, compatMode) - .setCounterValue(numberFields) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, - averageScore)); + AutofillId[] detectedFieldsIds = null; + FieldClassification[] detectedFieldClassifications = null; + if (detectedFieldIdsList != null) { + detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()]; + detectedFieldIdsList.toArray(detectedFieldsIds); + detectedFieldClassifications = + new FieldClassification[detectedFieldClassificationsList.size()]; + detectedFieldClassificationsList.toArray(detectedFieldClassifications); + + final int numberFields = detectedFieldsIds.length; + int totalSize = 0; + float totalScore = 0; + for (int i = 0; i < numberFields; i++) { + final FieldClassification fc = detectedFieldClassifications[i]; + final List<Match> matches = fc.getMatches(); + final int size = matches.size(); + totalSize += size; + for (int j = 0; j < size; j++) { + totalScore += matches.get(j).getScore(); + } } - mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null, - clientState, selectedDatasets, ignoredDatasets, - changedFieldIds, changedDatasetIds, - manuallyFilledFieldIds, manuallyFilledDatasetIds, - detectedFieldsIds, detectedFieldClassifications, saveDialogNotShowReason, - /* focusedId= */ null)); - } + + final int averageScore = (int) ((totalScore * 100) / totalSize); + mMetricsLogger.write( + Helper.newLogMaker( + MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES, + appComponentName, + getServicePackageName(), + sessionId, + compatMode) + .setCounterValue(numberFields) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore)); + } + Event event = + new Event( + Event.TYPE_CONTEXT_COMMITTED, + null, + clientState, + selectedDatasets, + ignoredDatasets, + changedFieldIds, + changedDatasetIds, + manuallyFilledFieldIds, + manuallyFilledDatasetIds, + detectedFieldsIds, + detectedFieldClassifications, + saveDialogNotShowReason, + /* focusedId= */ null); + + addEventToHistory(methodName, sessionId, event); } /** @@ -1174,7 +1345,9 @@ final class AutofillManagerServiceImpl * * @param callingUid The calling uid * @return The history for the autofill or the augmented autofill events depending on the {@code - * callingUid}, or {@code null} if there is none. + * callingUid}, or {@code null} if there is none. + * @deprecated Use {@link + * android.service.autofill.AutofillService#onSessionDestroyed(FillEventHistory)} */ FillEventHistory getFillEventHistory(int callingUid) { synchronized (mLock) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index ba9865d513d7..3ecff3b3ebae 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1962,7 +1962,7 @@ final class Session if (mLogViewEntered) { mLogViewEntered = false; - mService.logViewEntered(id, null, mCurrentViewId); + mService.logViewEntered(id, null, mCurrentViewId, shouldAddEventToHistory()); } } @@ -2866,7 +2866,12 @@ final class Session forceRemoveFromServiceLocked(); return; } - mService.setAuthenticationSelected(id, mClientState, uiType, mCurrentViewId); + mService.setAuthenticationSelected( + id, + mClientState, + uiType, + mCurrentViewId, + shouldAddEventToHistory()); } @@ -2941,7 +2946,12 @@ final class Session if (!mLoggedInlineDatasetShown) { // Chip inflation already logged, do not log again. // This is needed because every chip inflation will call this. - mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId); + mService.logDatasetShown( + this.id, + mClientState, + uiType, + mCurrentViewId, + shouldAddEventToHistory()); Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown); } mLoggedInlineDatasetShown = true; @@ -2949,7 +2959,12 @@ final class Session mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown); // Explicitly sets maybeSetSuggestionPresentedTimestampMs mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs(); - mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId); + mService.logDatasetShown( + this.id, + mClientState, + uiType, + mCurrentViewId, + shouldAddEventToHistory()); Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown); } } @@ -3943,7 +3958,8 @@ final class Session detectedFieldClassifications, mComponentName, mCompatMode, - saveDialogNotShowReason); + saveDialogNotShowReason, + shouldAddEventToHistory()); mSessionCommittedEventLogger.maybeSetCommitReason(commitReason); mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); mSaveEventLogger.maybeSetSaveUiNotShownReason(saveDialogNotShowReason); @@ -4590,7 +4606,7 @@ final class Session } private void logSaveShown() { - mService.logSaveShown(id, mClientState); + mService.logSaveShown(id, mClientState, shouldAddEventToHistory()); } @Nullable @@ -5248,7 +5264,8 @@ final class Session // so this calling logViewEntered will be a nop. // Calling logViewEntered() twice will only log it once // TODO(271181979): this is broken for multiple partitions - mService.logViewEntered(this.id, null, mCurrentViewId); + mService.logViewEntered( + this.id, null, mCurrentViewId, shouldAddEventToHistory()); } // If this is the first time view is entered for inline, the last @@ -6863,8 +6880,13 @@ final class Session // Autofill it directly... if (dataset.getAuthentication() == null) { if (generateEvent) { - mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType, - mCurrentViewId); + mService.logDatasetSelected( + dataset.getId(), + id, + mClientState, + uiType, + mCurrentViewId, + shouldAddEventToHistory()); } if (mCurrentViewId != null) { mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); @@ -6875,7 +6897,7 @@ final class Session // ...or handle authentication. mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType, - mCurrentViewId); + mCurrentViewId, shouldAddEventToHistory()); mPresentationStatsEventLogger.maybeSetAuthenticationType( AUTHENTICATION_TYPE_DATASET_AUTHENTICATION); // does not matter the value of isPrimary because null response won't be overridden. @@ -8018,6 +8040,32 @@ final class Session mService.getMaster().logRequestLocked(historyItem); } + /** + * Don't add secondary providers to FillEventHistory + */ + boolean shouldAddEventToHistory() { + + FillResponse lastResponse = null; + + synchronized (mLock) { + lastResponse = getLastResponseLocked("shouldAddEventToHistory(%s)"); + } + + // There might be events (like TYPE_VIEW_REQUESTED_AUTOFILL) that are + // generated before FillRequest/FillResponse mechanism are started, so + // still need to log it + if (lastResponse == null) { + return true; + } + + if (mRequestId.isSecondaryProvider(lastResponse.getRequestId())) { + // The request was to a secondary provider - don't log these events + return false; + } + + return true; + } + private void wtf(@Nullable Exception e, String fmt, Object... args) { final String message = String.format(fmt, args); synchronized (mLock) { diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 3025e2eaede0..549f8fa77b53 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -217,6 +217,13 @@ public class UserBackupManagerService { + mPowerManagerWakeLock.getTag())); return; } + + if (!mPowerManagerWakeLock.isHeld()) { + Slog.w(TAG, addUserIdToLogMessage(mUserId, + "Wakelock not held: " + mPowerManagerWakeLock.getTag())); + return; + } + mPowerManagerWakeLock.release(); Slog.v( TAG, diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index fd18fa856916..abfb8268bd9a 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -55,6 +55,7 @@ import android.content.Intent; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; +import android.media.AudioManager; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -102,6 +103,7 @@ public class ContextualSearchManagerService extends SystemService { private final PackageManagerInternal mPackageManager; private final WindowManagerInternal mWmInternal; private final DevicePolicyManagerInternal mDpmInternal; + private final AudioManager mAudioManager; private final Object mLock = new Object(); private final AssistDataRequester mAssistDataRequester; @@ -163,6 +165,8 @@ public class ContextualSearchManagerService extends SystemService { mAtmInternal = Objects.requireNonNull( LocalServices.getService(ActivityTaskManagerInternal.class)); mPackageManager = LocalServices.getService(PackageManagerInternal.class); + mAudioManager = context.getSystemService(AudioManager.class); + mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class)); mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); mAssistDataRequester = new AssistDataRequester( @@ -306,6 +310,10 @@ public class ContextualSearchManagerService extends SystemService { SystemClock.uptimeMillis()); launchIntent.putExtra(ContextualSearchManager.EXTRA_ENTRYPOINT, entrypoint); launchIntent.putExtra(ContextualSearchManager.EXTRA_TOKEN, mToken); + if (Flags.includeAudioPlayingStatus()) { + launchIntent.putExtra(ContextualSearchManager.EXTRA_IS_AUDIO_PLAYING, + mAudioManager.isMusicActive()); + } boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed(); final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities(); final List<IBinder> activityTokens = new ArrayList<>(records.size()); diff --git a/services/core/Android.bp b/services/core/Android.bp index 06f9e2bf55b2..dc830642dcc5 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -229,7 +229,6 @@ java_library_static { "power_hint_flags_lib", "biometrics_flags_lib", "am_flags_lib", - "updates_flags_lib", "com_android_server_accessibility_flags_lib", "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "com_android_launcher3_flags_lib", diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index cd929c1883d0..a184e905d0fa 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4799,9 +4799,6 @@ public class ActivityManagerService extends IActivityManager.Stub updateLruProcessLocked(app, false, null); checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked"); - updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); - checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked"); - final long now = SystemClock.uptimeMillis(); synchronized (mAppProfiler.mProfilerLock) { app.mProfile.setLastRequestedGc(now); @@ -4815,6 +4812,15 @@ public class ActivityManagerService extends IActivityManager.Stub } mProcessesOnHold.remove(app); + // See if the top visible activity is waiting to run in this process... + if (com.android.server.am.Flags.expediteActivityLaunchOnColdStart()) { + if (normalMode) { + mAtmInternal.attachApplication(app.getWindowProcessController()); + } + } + updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); + checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked"); + if (!mConstants.mEnableWaitForFinishAttachApplication) { finishAttachApplicationInner(startSeq, callingUid, pid); } @@ -4880,18 +4886,21 @@ public class ActivityManagerService extends IActivityManager.Stub // Mark the finish attach application phase as completed mProcessStateController.setPendingFinishAttach(app, false); - final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); final String processName = app.processName; boolean badApp = false; boolean didSomething = false; - // See if the top visible activity is waiting to run in this process... - if (normalMode) { - try { - didSomething = mAtmInternal.attachApplication(app.getWindowProcessController()); - } catch (Exception e) { - Slog.wtf(TAG, "Exception thrown launching activities in " + app, e); - badApp = true; + if (!com.android.server.am.Flags.expediteActivityLaunchOnColdStart()) { + final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); + + if (normalMode) { + try { + didSomething |= mAtmInternal.attachApplication( + app.getWindowProcessController()); + } catch (Exception e) { + Slog.wtf(TAG, "Exception thrown launching activities in " + app, e); + badApp = true; + } } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index aea24d978bee..600aa1fdaa04 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -427,11 +427,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel); final boolean resetOnUnplugAfterSignificantCharge = context.getResources().getBoolean( com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge); + final int batteryHistoryStorageSize = context.getResources().getInteger( + com.android.internal.R.integer.config_batteryHistoryStorageSize); BatteryStatsImpl.BatteryStatsConfig.Builder batteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder() .setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel) .setResetOnUnplugAfterSignificantCharge( - resetOnUnplugAfterSignificantCharge); + resetOnUnplugAfterSignificantCharge) + .setMaxHistorySizeBytes(batteryHistoryStorageSize); setPowerStatsThrottlePeriods(batteryStatsConfigBuilder, context.getResources().getString( com.android.internal.R.string.config_powerStatsThrottlePeriods)); mBatteryStatsConfig = batteryStatsConfigBuilder.build(); diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java index 123780fb7567..99bdd10ff71b 100644 --- a/services/core/java/com/android/server/am/PhantomProcessList.java +++ b/services/core/java/com/android/server/am/PhantomProcessList.java @@ -112,23 +112,10 @@ public final class PhantomProcessList { private final ActivityManagerService mService; private final Handler mKillHandler; - private static final int CGROUP_V1 = 0; - private static final int CGROUP_V2 = 1; - private static final String[] CGROUP_PATH_PREFIXES = { - "/acct/uid_" /* cgroup v1 */, - "/sys/fs/cgroup/uid_" /* cgroup v2 */ - }; - private static final String CGROUP_PID_PREFIX = "/pid_"; - private static final String CGROUP_PROCS = "/cgroup.procs"; - - @VisibleForTesting - int mCgroupVersion = CGROUP_V1; - PhantomProcessList(final ActivityManagerService service) { mService = service; mKillHandler = service.mProcessList.sKillHandler; mInjector = new Injector(); - probeCgroupVersion(); } @VisibleForTesting @@ -157,9 +144,15 @@ public final class PhantomProcessList { final int appPid = app.getPid(); InputStream input = mCgroupProcsFds.get(appPid); if (input == null) { - final String path = getCgroupFilePath(app.info.uid, appPid); + String path = null; try { + path = getCgroupFilePath(app.info.uid, appPid); input = mInjector.openCgroupProcs(path); + } catch (IllegalArgumentException e) { + if (DEBUG_PROCESSES) { + Slog.w(TAG, "Unable to obtain cgroup.procs path ", e); + } + return; } catch (FileNotFoundException | SecurityException e) { if (DEBUG_PROCESSES) { Slog.w(TAG, "Unable to open " + path, e); @@ -207,18 +200,9 @@ public final class PhantomProcessList { } } - private void probeCgroupVersion() { - for (int i = CGROUP_PATH_PREFIXES.length - 1; i >= 0; i--) { - if ((new File(CGROUP_PATH_PREFIXES[i] + Process.SYSTEM_UID)).exists()) { - mCgroupVersion = i; - break; - } - } - } - @VisibleForTesting String getCgroupFilePath(int uid, int pid) { - return CGROUP_PATH_PREFIXES[mCgroupVersion] + uid + CGROUP_PID_PREFIX + pid + CGROUP_PROCS; + return nativeGetCgroupProcsPath(uid, pid); } static String getProcessName(int pid) { @@ -630,4 +614,7 @@ public final class PhantomProcessList { return PhantomProcessList.getProcessName(pid); } } + + private static native String nativeGetCgroupProcsPath(int uid, int pid) + throws IllegalArgumentException; } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index c31b9ef60bd2..70f2a8e1dd1b 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -160,6 +160,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -176,6 +177,9 @@ import java.util.function.Consumer; class UserController implements Handler.Callback { private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM; + // Amount of time we wait for observers to handle onBeforeUserSwitching, before crashing system. + static final int DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS = 20 * 1000; + // Amount of time we wait for observers to handle a user switch before // giving up on them and dismissing the user switching dialog. static final int DEFAULT_USER_SWITCH_TIMEOUT_MS = 3 * 1000; @@ -1920,8 +1924,14 @@ class UserController implements Handler.Callback { return false; } - mHandler.post(() -> startUserInternalOnHandler(userId, oldUserId, userStartMode, - unlockListener, callingUid, callingPid)); + final Runnable continueStartUserInternal = () -> continueStartUserInternal(userInfo, + oldUserId, userStartMode, unlockListener, callingUid, callingPid); + if (foreground) { + mHandler.post(() -> dispatchOnBeforeUserSwitching(userId, () -> + mHandler.post(continueStartUserInternal))); + } else { + continueStartUserInternal.run(); + } } finally { Binder.restoreCallingIdentity(ident); } @@ -1929,11 +1939,11 @@ class UserController implements Handler.Callback { return true; } - private void startUserInternalOnHandler(int userId, int oldUserId, int userStartMode, + private void continueStartUserInternal(UserInfo userInfo, int oldUserId, int userStartMode, IProgressListener unlockListener, int callingUid, int callingPid) { final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); final boolean foreground = userStartMode == USER_START_MODE_FOREGROUND; - final UserInfo userInfo = getUserInfo(userId); + final int userId = userInfo.id; boolean needStart = false; boolean updateUmState = false; @@ -1995,7 +2005,6 @@ class UserController implements Handler.Callback { // it should be moved outside, but for now it's not as there are many calls to // external components here afterwards updateProfileRelatedCaches(); - dispatchOnBeforeUserSwitching(userId); mInjector.getWindowManager().setCurrentUser(userId); mInjector.reportCurWakefulnessUsageEvent(); // Once the internal notion of the active user has switched, we lock the device @@ -2296,25 +2305,40 @@ class UserController implements Handler.Callback { mUserSwitchObservers.finishBroadcast(); } - private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId) { + private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId, Runnable onComplete) { final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("dispatchOnBeforeUserSwitching-" + newUserId); - final int observerCount = mUserSwitchObservers.beginBroadcast(); - for (int i = 0; i < observerCount; i++) { - final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); - t.traceBegin("onBeforeUserSwitching-" + name); + final AtomicBoolean isSuccessful = new AtomicBoolean(false); + startTimeoutForOnBeforeUserSwitching(isSuccessful); + informUserSwitchObservers((observer, callback) -> { try { - mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId); + observer.onBeforeUserSwitching(newUserId, callback); } catch (RemoteException e) { - // Ignore - } finally { - t.traceEnd(); + // ignore } - } - mUserSwitchObservers.finishBroadcast(); + }, () -> { + isSuccessful.set(true); + onComplete.run(); + }, "onBeforeUserSwitching"); t.traceEnd(); } + private void startTimeoutForOnBeforeUserSwitching(AtomicBoolean isSuccessful) { + mHandler.postDelayed(() -> { + if (isSuccessful.get()) { + return; + } + String unresponsiveObservers; + synchronized (mLock) { + unresponsiveObservers = String.join(", ", mCurWaitingUserSwitchCallbacks); + } + throw new RuntimeException("Timeout on dispatchOnBeforeUserSwitching. " + + "These UserSwitchObservers did not respond in " + + DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS + "ms: " + unresponsiveObservers + "."); + }, DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS); + } + + /** Called on handler thread */ @VisibleForTesting void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) { @@ -2527,70 +2551,76 @@ class UserController implements Handler.Callback { t.traceBegin("dispatchUserSwitch-" + oldUserId + "-to-" + newUserId); EventLog.writeEvent(EventLogTags.UC_DISPATCH_USER_SWITCH, oldUserId, newUserId); + uss.switching = true; + informUserSwitchObservers((observer, callback) -> { + try { + observer.onUserSwitching(newUserId, callback); + } catch (RemoteException e) { + // ignore + } + }, () -> { + synchronized (mLock) { + sendContinueUserSwitchLU(uss, oldUserId, newUserId); + } + }, "onUserSwitching"); + t.traceEnd(); + } + void informUserSwitchObservers(BiConsumer<IUserSwitchObserver, IRemoteCallback> consumer, + final Runnable onComplete, String trace) { final int observerCount = mUserSwitchObservers.beginBroadcast(); - if (observerCount > 0) { - final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>(); + if (observerCount == 0) { + onComplete.run(); + mUserSwitchObservers.finishBroadcast(); + return; + } + final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>(); + synchronized (mLock) { + mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks; + } + final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount); + final long userSwitchTimeoutMs = getUserSwitchTimeoutMs(); + final long dispatchStartedTime = SystemClock.elapsedRealtime(); + for (int i = 0; i < observerCount; i++) { + final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime(); + // Prepend with unique prefix to guarantee that keys are unique + final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); synchronized (mLock) { - uss.switching = true; - mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks; - } - final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount); - final long userSwitchTimeoutMs = getUserSwitchTimeoutMs(); - final long dispatchStartedTime = SystemClock.elapsedRealtime(); - for (int i = 0; i < observerCount; i++) { - final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime(); - try { - // Prepend with unique prefix to guarantee that keys are unique - final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); + curWaitingUserSwitchCallbacks.add(name); + } + final IRemoteCallback callback = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + asyncTraceEnd(trace + "-" + name, 0); synchronized (mLock) { - curWaitingUserSwitchCallbacks.add(name); - } - final IRemoteCallback callback = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) throws RemoteException { - asyncTraceEnd("onUserSwitching-" + name, newUserId); - synchronized (mLock) { - long delayForObserver = SystemClock.elapsedRealtime() - - dispatchStartedTimeForObserver; - if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) { - Slogf.w(TAG, "User switch slowed down by observer " + name - + ": result took " + delayForObserver - + " ms to process."); - } - - long totalDelay = SystemClock.elapsedRealtime() - - dispatchStartedTime; - if (totalDelay > userSwitchTimeoutMs) { - Slogf.e(TAG, "User switch timeout: observer " + name - + "'s result was received " + totalDelay - + " ms after dispatchUserSwitch."); - } - - curWaitingUserSwitchCallbacks.remove(name); - // Continue switching if all callbacks have been notified and - // user switching session is still valid - if (waitingCallbacksCount.decrementAndGet() == 0 - && (curWaitingUserSwitchCallbacks - == mCurWaitingUserSwitchCallbacks)) { - sendContinueUserSwitchLU(uss, oldUserId, newUserId); - } - } + long delayForObserver = SystemClock.elapsedRealtime() + - dispatchStartedTimeForObserver; + if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) { + Slogf.w(TAG, "User switch slowed down by observer " + name + + ": result took " + delayForObserver + + " ms to process. " + trace); } - }; - asyncTraceBegin("onUserSwitching-" + name, newUserId); - mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback); - } catch (RemoteException e) { - // Ignore + long totalDelay = SystemClock.elapsedRealtime() - dispatchStartedTime; + if (totalDelay > userSwitchTimeoutMs) { + Slogf.e(TAG, "User switch timeout: observer " + name + + "'s result was received " + totalDelay + + " ms after dispatchUserSwitch. " + trace); + } + curWaitingUserSwitchCallbacks.remove(name); + // Continue switching if all callbacks have been notified and + // user switching session is still valid + if (waitingCallbacksCount.decrementAndGet() == 0 + && (curWaitingUserSwitchCallbacks + == mCurWaitingUserSwitchCallbacks)) { + onComplete.run(); + } + } } - } - } else { - synchronized (mLock) { - sendContinueUserSwitchLU(uss, oldUserId, newUserId); - } + }; + asyncTraceBegin(trace + "-" + name, 0); + consumer.accept(mUserSwitchObservers.getBroadcastItem(i), callback); } mUserSwitchObservers.finishBroadcast(); - t.traceEnd(); // end dispatchUserSwitch- } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 89e4a8d82676..cfcede8ee40d 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -288,3 +288,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "expedite_activity_launch_on_cold_start" + namespace: "system_performance" + description: "Notify ActivityTaskManager of cold starts early to fix app launch behavior." + bug: "319519089" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1799b7715e5c..0fd47169122b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -11845,7 +11845,7 @@ public class AudioService extends IAudioService.Stub private AudioDeviceAttributes anonymizeAudioDeviceAttributesUnchecked( AudioDeviceAttributes ada) { - if (!AudioSystem.isBluetoothDevice(ada.getInternalType())) { + if (ada == null || !AudioSystem.isBluetoothDevice(ada.getInternalType())) { return ada; } AudioDeviceAttributes res = new AudioDeviceAttributes(ada); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 4c5f65285a9e..ac0892b92646 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1960,6 +1960,10 @@ public class Vpn { public void onUserAdded(int userId) { // If the user is restricted tie them to the parent user's VPN UserInfo user = mUserManager.getUserInfo(userId); + if (user == null) { + Log.e(TAG, "Can not retrieve UserInfo for userId=" + userId); + return; + } if (user.isRestricted() && user.restrictedProfileParentId == mUserId) { synchronized(Vpn.this) { final Set<Range<Integer>> existingRanges = mNetworkCapabilities.getUids(); @@ -1989,6 +1993,14 @@ public class Vpn { public void onUserRemoved(int userId) { // clean up if restricted UserInfo user = mUserManager.getUserInfo(userId); + // TODO: Retrieving UserInfo upon receiving the USER_REMOVED intent is not guaranteed. + // This could prevent the removal of associated ranges. To ensure proper range removal, + // store the user info when adding ranges. This allows using the user ID in the + // USER_REMOVED intent to handle the removal process. + if (user == null) { + Log.e(TAG, "Can not retrieve UserInfo for userId=" + userId); + return; + } if (user.isRestricted() && user.restrictedProfileParentId == mUserId) { synchronized(Vpn.this) { final Set<Range<Integer>> existingRanges = mNetworkCapabilities.getUids(); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index bad5b8b9567a..2985ad330bc6 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2397,7 +2397,7 @@ public final class DisplayManagerService extends SystemService { // We don't bother invalidating the display info caches here because any changes to the // display info will trigger a cache invalidation inside of LogicalDisplay before we hit // this point. - sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); applyDisplayChangedLocked(display); } @@ -2643,7 +2643,8 @@ public final class DisplayManagerService extends SystemService { private void updateCanHostTasksIfNeededLocked(LogicalDisplay display) { if (display.setCanHostTasksLocked(!mMirrorBuiltInDisplay)) { - sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + sendDisplayEventIfEnabledLocked(display, + DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); } } @@ -3474,7 +3475,7 @@ public final class DisplayManagerService extends SystemService { private void sendDisplayEventFrameRateOverrideLocked(int displayId) { Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE, - displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED); mHandler.sendMessage(msg); } @@ -4061,7 +4062,7 @@ public final class DisplayManagerService extends SystemService { handleLogicalDisplayAddedLocked(display); break; - case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED: + case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_BASIC_CHANGED: handleLogicalDisplayChangedLocked(display); break; @@ -4286,8 +4287,9 @@ public final class DisplayManagerService extends SystemService { switch (event) { case DisplayManagerGlobal.EVENT_DISPLAY_ADDED: return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0; - case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED: - return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) != 0; + case DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED: + return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) + != 0; case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED: return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED) @@ -4542,7 +4544,8 @@ public final class DisplayManagerService extends SystemService { public void registerCallback(IDisplayManagerCallback callback) { registerCallbackWithEventMask(callback, DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 730b95cf1eac..2f82b2ac464a 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -521,6 +521,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata, boolean bootCompleted, DisplayManagerFlags flags) { + final Resources resources = context.getResources(); + mFlags = flags; mInjector = injector != null ? injector : new Injector(); mClock = mInjector.getClock(); @@ -540,7 +542,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController( mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(), () -> updatePowerState(), mDisplayId, mSensorManager); - mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController); + mDisplayStateController = new DisplayStateController( + mDisplayPowerProximityStateController, + resources.getBoolean(R.bool.config_skipScreenOffTransition)); mTag = TAG + "[" + mDisplayId + "]"; mThermalBrightnessThrottlingDataId = logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; @@ -574,17 +578,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE), false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); - final Resources resources = context.getResources(); - // DOZE AND DIM SETTINGS mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness( mDisplayDeviceConfig.getDefaultDozeBrightness()); loadBrightnessRampRates(); mSkipScreenOnBrightnessRamp = resources.getBoolean( R.bool.config_skipScreenOnBrightnessRamp); - mDozeScaleFactor = context.getResources().getFraction( - R.fraction.config_screenAutoBrightnessDozeScaleFactor, - 1, 1); + mDozeScaleFactor = resources.getFraction( + R.fraction.config_screenAutoBrightnessDozeScaleFactor, 1, 1); Runnable modeChangeCallback = () -> { sendUpdatePowerState(); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 79592a656409..006921572977 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -81,7 +81,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { public static final int LOGICAL_DISPLAY_EVENT_BASE = 0; public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1 << 0; - public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 1 << 1; + public static final int LOGICAL_DISPLAY_EVENT_BASIC_CHANGED = 1 << 1; public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 1 << 2; public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 1 << 3; public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 1 << 4; @@ -172,9 +172,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { /** * Has an entry for every logical display that the rest of the system has been notified about. - * Any entry in here requires us to send a {@link LOGICAL_DISPLAY_EVENT_REMOVED} event when it - * is deleted or {@link LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. The values are any - * of the {@code UPDATE_STATE_*} constant types. + * The values are any of the {@code UPDATE_STATE_*} constant types. */ private final SparseIntArray mUpdatedLogicalDisplays = new SparseIntArray(); @@ -811,7 +809,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final boolean isCurrentlyEnabled = display.isEnabledLocked(); int logicalDisplayEventMask = mLogicalDisplaysToUpdate .get(displayId, LOGICAL_DISPLAY_EVENT_BASE); - + boolean hasBasicInfoChanged = + !mTempDisplayInfo.equals(newDisplayInfo, /* compareRefreshRate */ false); // The display is no longer valid and needs to be removed. if (!display.isValidLocked()) { // Remove from group @@ -863,19 +862,28 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { int event = isCurrentlyEnabled ? LOGICAL_DISPLAY_EVENT_ADDED : LOGICAL_DISPLAY_EVENT_REMOVED; logicalDisplayEventMask |= event; - } else if (wasDirty || !mTempDisplayInfo.equals(newDisplayInfo)) { + } else if (wasDirty) { // If only the hdr/sdr ratio changed, then send just the event for that case if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) { logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED; } else { - logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED; + logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED + | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED + | LOGICAL_DISPLAY_EVENT_STATE_CHANGED; } + } else if (hasBasicInfoChanged + || mTempDisplayInfo.getRefreshRate() != newDisplayInfo.getRefreshRate()) { + // If only the hdr/sdr ratio changed, then send just the event for that case + if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) { + logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED; + } else { - if (mFlags.isDisplayListenerPerformanceImprovementsEnabled()) { + if (hasBasicInfoChanged) { + logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED; + } logicalDisplayEventMask |= updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo); } - // The display is involved in a display layout transition } else if (updateState == UPDATE_STATE_TRANSITION) { logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION; @@ -891,7 +899,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // things like display cutouts. display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo); if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) { - logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED; + logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED + | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED; } } mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask); @@ -930,7 +939,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { if (mFlags.isConnectedDisplayManagementEnabled()) { sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED); } - sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_BASIC_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_STATE_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); @@ -962,7 +971,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mask |= LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED; } - if (mTempDisplayInfo.state != newDisplayInfo.state) { + if (mFlags.isDisplayListenerPerformanceImprovementsEnabled() + && mTempDisplayInfo.state != newDisplayInfo.state) { mask |= LOGICAL_DISPLAY_EVENT_STATE_CHANGED; } return mask; @@ -1357,8 +1367,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return "added"; case LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION: return "transition"; - case LOGICAL_DISPLAY_EVENT_CHANGED: - return "changed"; case LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED: return "framerate_override"; case LOGICAL_DISPLAY_EVENT_SWAPPED: @@ -1375,6 +1383,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return "state_changed"; case LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED: return "refresh_rate_changed"; + case LOGICAL_DISPLAY_EVENT_BASIC_CHANGED: + return "basic_changed"; } return null; } diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java index 0b46e0fc3268..f3b079912986 100644 --- a/services/core/java/com/android/server/display/state/DisplayStateController.java +++ b/services/core/java/com/android/server/display/state/DisplayStateController.java @@ -31,14 +31,17 @@ import java.io.PrintWriter; * clients about the changes */ public class DisplayStateController { - private DisplayPowerProximityStateController mDisplayPowerProximityStateController; + private final DisplayPowerProximityStateController mDisplayPowerProximityStateController; + private final boolean mShouldSkipScreenOffTransition; private boolean mPerformScreenOffTransition = false; private int mDozeStateOverride = Display.STATE_UNKNOWN; private int mDozeStateOverrideReason = Display.STATE_REASON_UNKNOWN; - public DisplayStateController(DisplayPowerProximityStateController - displayPowerProximityStateController) { + public DisplayStateController( + DisplayPowerProximityStateController displayPowerProximityStateController, + boolean shouldSkipScreenOffTransition) { this.mDisplayPowerProximityStateController = displayPowerProximityStateController; + this.mShouldSkipScreenOffTransition = shouldSkipScreenOffTransition; } /** @@ -65,7 +68,7 @@ public class DisplayStateController { switch (displayPowerRequest.policy) { case DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF: state = Display.STATE_OFF; - mPerformScreenOffTransition = true; + mPerformScreenOffTransition = !mShouldSkipScreenOffTransition; break; case DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE: if (mDozeStateOverride != Display.STATE_UNKNOWN) { @@ -117,7 +120,8 @@ public class DisplayStateController { public void dump(PrintWriter pw) { pw.println("DisplayStateController:"); pw.println("-----------------------"); - pw.println(" mPerformScreenOffTransition:" + mPerformScreenOffTransition); + pw.println(" mShouldSkipScreenOffTransition=" + mShouldSkipScreenOffTransition); + pw.println(" mPerformScreenOffTransition=" + mPerformScreenOffTransition); pw.println(" mDozeStateOverride=" + mDozeStateOverride); IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index 73d563069632..8681ea52ed23 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -221,6 +221,18 @@ final class InputGestureManager { systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN)); + systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT)); + systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT)); + systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_UP, + KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP)); + systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN)); systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION)); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 68001428e567..c653dec1a299 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2723,17 +2723,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } - final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); - if (Flags.imeSwitcherRevamp()) { - // The IME switcher button should be shown when the current IME specified a - // language settings activity. - final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod()); - if (curImi != null && curImi.createImeLanguageSettingsActivityIntent() != null) { - return true; - } - } - - return hasMultipleSubtypesForSwitcher(false /* nonAuxOnly */, settings); + return hasMultipleSubtypesForSwitcher(false /* nonAuxOnly */, userId); } /** @@ -2741,10 +2731,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * across all enabled IMEs for the given user. * * @param nonAuxOnly whether to check only for non auxiliary subtypes. - * @param settings the input method settings under the given user ID. + * @param userId the id of the user for which to check the number of subtypes. */ private static boolean hasMultipleSubtypesForSwitcher(boolean nonAuxOnly, - @NonNull InputMethodSettings settings) { + @UserIdInt int userId) { + final var settings = InputMethodSettingsRepository.get(userId); List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter( InputMethodInfo::shouldShowInInputMethodPicker); final int numImes = imes.size(); @@ -4130,8 +4121,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private void onImeSwitchButtonClickLocked(int displayId, @NonNull UserData userData) { final int userId = userData.mUserId; - final var settings = InputMethodSettingsRepository.get(userId); - if (hasMultipleSubtypesForSwitcher(true /* nonAuxOnly */, settings)) { + if (hasMultipleSubtypesForSwitcher(true /* nonAuxOnly */, userId)) { switchToNextInputMethodLocked(false /* onlyCurrentIme */, userData); } else { showInputMethodPickerFromSystem( diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index 556cc03b3abd..d29fde255ab9 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -16,7 +16,6 @@ package com.android.server.location.contexthub; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED; import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD; import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED; @@ -25,7 +24,6 @@ import android.Manifest; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.PendingIntent; -import android.chre.flags.Flags; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; @@ -655,7 +653,13 @@ public class ContextHubClientBroker extends IContextHubClient.Stub // If in the grace period, don't check permissions state since it'll cause cleanup // messages to be dropped. if (authState == AUTHORIZATION_DENIED - || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) { + || !ContextHubServiceUtil.notePermissions( + mAppOpsManager, + mUid, + mPackage, + mAttributionTag, + messagePermissions, + RECEIVE_MSG_NOTE + nanoAppId)) { Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage + " doesn't have permission"); return ErrorCode.PERMISSION_DENIED; @@ -754,56 +758,6 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * Checks that this client has all of the provided permissions. - * - * @param permissions list of permissions to check - * @return true if the client has all of the permissions granted - */ - boolean hasPermissions(List<String> permissions) { - for (String permission : permissions) { - if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) { - Log.e(TAG, "no permission for " + permission); - return false; - } - } - return true; - } - - /** - * Attributes the provided permissions to the package of this client. - * - * @param permissions list of permissions covering data the client is about to receive - * @param noteMessage message that should be noted alongside permissions attribution to - * facilitate debugging - * @return true if client has ability to use all of the provided permissions - */ - boolean notePermissions(List<String> permissions, String noteMessage) { - for (String permission : permissions) { - int opCode = AppOpsManager.permissionToOpCode(permission); - if (opCode != AppOpsManager.OP_NONE) { - try { - if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage) - != AppOpsManager.MODE_ALLOWED) { - return false; - } - } catch (SecurityException e) { - Log.e( - TAG, - "SecurityException: noteOp for pkg " - + mPackage - + " opcode " - + opCode - + ": " - + e.getMessage()); - return false; - } - } - } - - return true; - } - - /** * @return true if the client is a PendingIntent client that has been cancelled. */ boolean isPendingIntentCancelled() { @@ -868,7 +822,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub synchronized (mMessageChannelNanoappIdMap) { // Check permission granted state synchronously since this method can be invoked from // multiple threads. - boolean hasPermissions = hasPermissions(nanoappPermissions); + boolean hasPermissions = + ContextHubServiceUtil.hasPermissions(mContext, mPid, mUid, nanoappPermissions); curAuthState = mMessageChannelNanoappIdMap.getOrDefault( nanoAppId, AUTHORIZATION_UNKNOWN); diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index 4e1df769100b..2c072d0ed8fe 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -145,7 +145,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub super.closeSession_enforcePermission(); if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered"); try { - mContextHubProxy.closeEndpointSession(sessionId, (byte) reason); + mContextHubProxy.closeEndpointSession( + sessionId, ContextHubServiceUtil.toHalReason(reason)); } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { Log.e(TAG, "Exception while calling HAL closeEndpointSession", e); throw e; diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java index 77ec51af80a7..957307a787b1 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java @@ -16,7 +16,10 @@ package com.android.server.location.contexthub; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import android.Manifest; +import android.app.AppOpsManager; import android.content.Context; import android.hardware.contexthub.EndpointInfo; import android.hardware.contexthub.HubEndpoint; @@ -535,4 +538,97 @@ import java.util.List; return HubEndpoint.REASON_FAILURE; } } + + /** + * Converts a byte integer defined by Reason.aidl to HubEndpoint.Reason values exposed to apps. + * + * @param reason The Reason.aidl value + * @return The converted HubEndpoint.Reason value + */ + /* package */ + static byte toHalReason(@HubEndpoint.Reason int reason) { + switch (reason) { + case HubEndpoint.REASON_FAILURE: + return Reason.UNSPECIFIED; + case HubEndpoint.REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED: + return Reason.OPEN_ENDPOINT_SESSION_REQUEST_REJECTED; + case HubEndpoint.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED: + return Reason.CLOSE_ENDPOINT_SESSION_REQUESTED; + case HubEndpoint.REASON_ENDPOINT_INVALID: + return Reason.ENDPOINT_INVALID; + case HubEndpoint.REASON_ENDPOINT_STOPPED: + return Reason.ENDPOINT_GONE; + case HubEndpoint.REASON_PERMISSION_DENIED: + return Reason.PERMISSION_DENIED; + default: + Log.w(TAG, "toHalReason: invalid reason: " + reason); + return Reason.UNSPECIFIED; + } + } + + /** + * Checks that the module with the provided context/pid/uid has all of the provided permissions. + * + * @param context The context to validate permissions for + * @param pid The PID to validate permissions for + * @param uid The UID to validate permissions for + * @param permissions The collection of permissions to check + * @return true if the module has all of the permissions granted + */ + /* package */ + static boolean hasPermissions( + Context context, int pid, int uid, Collection<String> permissions) { + for (String permission : permissions) { + if (context.checkPermission(permission, pid, uid) != PERMISSION_GRANTED) { + Log.e(TAG, "no permission for " + permission); + return false; + } + } + return true; + } + + /** + * Attributes the provided permissions to the package of this client. + * + * @param appOpsManager The app ops manager to use + * @param uid The UID of the module to note permissions for + * @param packageName The package name of the module to note permissions for + * @param attributionTag The attribution tag of the module to note permissions for + * @param permissions The list of permissions covering data the client is about to receive + * @param noteMessage The message that should be noted alongside permissions attribution to + * facilitate debugging + * @return true if client has ability to use all of the provided permissions + */ + /* package */ + static boolean notePermissions( + AppOpsManager appOpsManager, + int uid, + String packageName, + String attributionTag, + List<String> permissions, + String noteMessage) { + for (String permission : permissions) { + int opCode = AppOpsManager.permissionToOpCode(permission); + if (opCode != AppOpsManager.OP_NONE) { + try { + if (appOpsManager.noteOp(opCode, uid, packageName, attributionTag, noteMessage) + != AppOpsManager.MODE_ALLOWED) { + return false; + } + } catch (SecurityException e) { + Log.e( + TAG, + "SecurityException: noteOp for pkg " + + packageName + + " opcode " + + opCode + + ": " + + e.getMessage()); + return false; + } + } + } + + return true; + } } diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 58c8450d714d..0438a1bac662 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.media.MediaRoute2ProviderService.Reason; import android.media.MediaRouter2; import android.media.MediaRouter2Utils; import android.media.RouteDiscoveryPreference; @@ -123,6 +124,13 @@ abstract class MediaRoute2Provider { } } + /** Calls {@link Callback#onRequestFailed} with the given id and reason. */ + protected void notifyRequestFailed(long requestId, @Reason int reason) { + if (mCallback != null) { + mCallback.onRequestFailed(/* provider= */ this, requestId, reason); + } + } + void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) { setProviderState(providerInfo); notifyProviderState(); @@ -175,7 +183,9 @@ abstract class MediaRoute2Provider { @NonNull RoutingSessionInfo sessionInfo); void onSessionReleased(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo); - void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason); + + void onRequestFailed( + @NonNull MediaRoute2Provider provider, long requestId, @Reason int reason); } /** diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index f09be2c15ee0..80d3c5c5c5ec 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -31,6 +31,7 @@ import android.media.IMediaRoute2ProviderServiceCallback; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.media.MediaRoute2ProviderService.Reason; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; @@ -41,6 +42,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.LongSparseArray; import android.util.Slog; @@ -89,6 +91,12 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { mRequestIdToSessionCreationRequest; @GuardedBy("mLock") + private final Map<String, SystemMediaSessionCallback> mSystemSessionCallbacks; + + @GuardedBy("mLock") + private final LongSparseArray<SystemMediaSessionCallback> mRequestIdToSystemSessionRequest; + + @GuardedBy("mLock") private final Map<String, SessionCreationOrTransferRequest> mSessionOriginalIdToTransferRequest; MediaRoute2ProviderServiceProxy( @@ -102,6 +110,8 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { mContext = Objects.requireNonNull(context, "Context must not be null."); mRequestIdToSessionCreationRequest = new LongSparseArray<>(); mSessionOriginalIdToTransferRequest = new HashMap<>(); + mRequestIdToSystemSessionRequest = new LongSparseArray<>(); + mSystemSessionCallbacks = new ArrayMap<>(); mIsSelfScanOnlyProvider = isSelfScanOnlyProvider; mSupportsSystemMediaRouting = supportsSystemMediaRouting; mUserId = userId; @@ -236,6 +246,48 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { } } + /** + * Requests the creation of a system media routing session. + * + * @param requestId The id of the request. + * @param uid The uid of the package whose media to route, or {@link + * android.os.Process#INVALID_UID} if not applicable (for example, if all the system's media + * must be routed). + * @param packageName The package name to populate {@link + * RoutingSessionInfo#getClientPackageName()}. + * @param routeId The id of the route to be initially {@link + * RoutingSessionInfo#getSelectedRoutes()}. + * @param sessionHints An optional bundle with paramets. + * @param callback A {@link SystemMediaSessionCallback} to notify of session events. + * @see MediaRoute2ProviderService#onCreateSystemRoutingSession + */ + public void requestCreateSystemMediaSession( + long requestId, + int uid, + String packageName, + String routeId, + @Nullable Bundle sessionHints, + @NonNull SystemMediaSessionCallback callback) { + if (!Flags.enableMirroringInMediaRouter2()) { + throw new IllegalStateException( + "Unexpected call to requestCreateSystemMediaSession. Governing flag is" + + " disabled."); + } + if (mConnectionReady) { + boolean binderRequestSucceeded = + mActiveConnection.requestCreateSystemMediaSession( + requestId, uid, packageName, routeId, sessionHints); + if (!binderRequestSucceeded) { + // notify failure. + return; + } + updateBinding(); + synchronized (mLock) { + mRequestIdToSystemSessionRequest.put(requestId, callback); + } + } + } + public boolean hasComponentName(String packageName, String className) { return mComponentName.getPackageName().equals(packageName) && mComponentName.getClassName().equals(className); @@ -292,7 +344,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { mLastDiscoveryPreference != null && mLastDiscoveryPreference.shouldPerformActiveScan() && mSupportsSystemMediaRouting; + boolean bindDueToOngoingSystemMediaRoutingSessions = false; + if (Flags.enableMirroringInMediaRouter2()) { + synchronized (mLock) { + bindDueToOngoingSystemMediaRoutingSessions = !mSystemSessionCallbacks.isEmpty(); + } + } if (!getSessionInfos().isEmpty() + || bindDueToOngoingSystemMediaRoutingSessions || bindDueToManagerScan || bindDueToSystemMediaRoutingSupport) { return true; @@ -438,6 +497,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { String newSessionId = newSession.getId(); synchronized (mLock) { + var systemMediaSessionCallback = mRequestIdToSystemSessionRequest.get(requestId); + if (systemMediaSessionCallback != null) { + mSystemSessionCallbacks.put(newSession.getOriginalId(), systemMediaSessionCallback); + systemMediaSessionCallback.onSessionUpdate(newSession); + return; + } + if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { newSession = createSessionWithPopulatedTransferInitiationDataLocked( @@ -569,6 +635,12 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { boolean found = false; synchronized (mLock) { + var sessionCallback = mSystemSessionCallbacks.get(releasedSession.getOriginalId()); + if (sessionCallback != null) { + sessionCallback.onSessionReleased(); + return; + } + mSessionOriginalIdToTransferRequest.remove(releasedSession.getId()); for (RoutingSessionInfo session : mSessionInfos) { if (TextUtils.equals(session.getId(), releasedSession.getId())) { @@ -673,6 +745,26 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { pendingTransferCount); } + /** + * Callback for events related to system media sessions. + * + * @see MediaRoute2ProviderService#onCreateSystemRoutingSession + */ + public interface SystemMediaSessionCallback { + + /** + * Called when the corresponding session's {@link RoutingSessionInfo}, or upon the creation + * of the given session info. + */ + void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo); + + /** Called when the request with the given id fails for the given reason. */ + void onRequestFailed(long requestId, @Reason int reason); + + /** Called when the corresponding session is released. */ + void onSessionReleased(); + } + // All methods in this class are called on the main thread. private final class ServiceConnectionImpl implements ServiceConnection { @@ -739,6 +831,28 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { } } + /** + * Sends a system media session creation request to the provider service, and returns + * whether the request transaction succeeded. + * + * <p>The transaction might fail, for example, if the recipient process has died. + */ + public boolean requestCreateSystemMediaSession( + long requestId, + int uid, + String packageName, + String routeId, + @Nullable Bundle sessionHints) { + try { + mService.requestCreateSystemMediaSession( + requestId, uid, packageName, routeId, sessionHints); + return true; + } catch (RemoteException ex) { + Slog.e(TAG, "requestCreateSystemMediaSession: Failed to deliver request."); + } + return false; + } + public void releaseSession(long requestId, String sessionId) { try { mService.releaseSession(requestId, sessionId); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 58deffcbd4ba..83ac05d9d4c3 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -846,33 +846,29 @@ class MediaRouter2ServiceImpl { try { synchronized (mLock) { UserRecord userRecord = getOrCreateUserRecordLocked(userId); - List<RoutingSessionInfo> sessionInfos; + SystemMediaRoute2Provider systemProvider = userRecord.mHandler.getSystemProvider(); if (hasSystemRoutingPermissions) { - if (setDeviceRouteSelected && !Flags.enableMirroringInMediaRouter2()) { + if (!Flags.enableMirroringInMediaRouter2() && setDeviceRouteSelected) { // Return a fake system session that shows the device route as selected and // available bluetooth routes as transferable. - return userRecord.mHandler.getSystemProvider() - .generateDeviceRouteSelectedSessionInfo(targetPackageName); + return systemProvider.generateDeviceRouteSelectedSessionInfo( + targetPackageName); } else { - sessionInfos = userRecord.mHandler.getSystemProvider().getSessionInfos(); - if (!sessionInfos.isEmpty()) { - // Return a copy of the current system session with no modification, - // except setting the client package name. - return new RoutingSessionInfo.Builder(sessionInfos.get(0)) - .setClientPackageName(targetPackageName) - .build(); + RoutingSessionInfo session = + systemProvider.getSessionForPackage(targetPackageName); + if (session != null) { + return session; } else { Slog.w(TAG, "System provider does not have any session info."); + return null; } } } else { - return new RoutingSessionInfo.Builder( - userRecord.mHandler.getSystemProvider().getDefaultSessionInfo()) + return new RoutingSessionInfo.Builder(systemProvider.getDefaultSessionInfo()) .setClientPackageName(targetPackageName) .build(); } } - return null; } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index b93846bf9ee7..4aec3678af8b 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -327,6 +327,23 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } /** + * Returns the {@link RoutingSessionInfo} that corresponds to the package with the given name. + */ + public RoutingSessionInfo getSessionForPackage(String targetPackageName) { + synchronized (mLock) { + if (!mSessionInfos.isEmpty()) { + // Return a copy of the current system session with no modification, + // except setting the client package name. + return new RoutingSessionInfo.Builder(mSessionInfos.get(0)) + .setClientPackageName(targetPackageName) + .build(); + } else { + return null; + } + } + } + + /** * Builds a system {@link RoutingSessionInfo} with the selected route set to the currently * selected <b>device</b> route (wired or built-in, but not bluetooth) and transferable routes * set to the currently available (connected) bluetooth routes. @@ -633,10 +650,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { RoutingSessionInfo sessionInfo; synchronized (mLock) { - sessionInfo = mSessionInfos.get(0); - if (sessionInfo == null) { + if (mSessionInfos.isEmpty()) { return; } + sessionInfo = mSessionInfos.get(0); } mCallback.onSessionUpdated(this, sessionInfo); diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java index 7dc30ab66fd2..a27a14b87d53 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java @@ -18,22 +18,31 @@ package com.android.server.media; import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.media.MediaRoute2ProviderService.Reason; +import android.media.MediaRouter2Utils; import android.media.RoutingSessionInfo; +import android.os.Binder; import android.os.Looper; +import android.os.Process; import android.os.UserHandle; -import android.util.ArraySet; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; +import android.util.LongSparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.server.media.MediaRoute2ProviderServiceProxy.SystemMediaSessionCallback; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; @@ -48,11 +57,33 @@ import java.util.stream.Stream; private static final String ROUTE_ID_PREFIX_SYSTEM = "SYSTEM"; private static final String ROUTE_ID_SYSTEM_SEPARATOR = "."; + private final PackageManager mPackageManager; + @GuardedBy("mLock") private MediaRoute2ProviderInfo mLastSystemProviderInfo; @GuardedBy("mLock") - private final Map<String, ProviderProxyRecord> mProxyRecords = new HashMap<>(); + private final Map<String, ProviderProxyRecord> mProxyRecords = new ArrayMap<>(); + + /** + * Maps package names to corresponding sessions maintained by {@link MediaRoute2ProviderService + * provider services}. + */ + @GuardedBy("mLock") + private final Map<String, SystemMediaSessionRecord> mPackageNameToSessionRecord = + new ArrayMap<>(); + + /** + * Maps route {@link MediaRoute2Info#getOriginalId original ids} to the id of the {@link + * MediaRoute2ProviderService provider service} that manages the corresponding route. + */ + @GuardedBy("mLock") + private final Map<String, String> mOriginalRouteIdToProviderId = new ArrayMap<>(); + + /** Maps request ids to pending session creation callbacks. */ + @GuardedBy("mLock") + private final LongSparseArray<PendingSessionCreationCallbackImpl> mPendingSessionCreations = + new LongSparseArray<>(); private static final ComponentName COMPONENT_NAME = new ComponentName( @@ -69,6 +100,128 @@ import java.util.stream.Stream; private SystemMediaRoute2Provider2(Context context, UserHandle user, Looper looper) { super(context, COMPONENT_NAME, user, looper); + mPackageManager = context.getPackageManager(); + } + + @Override + public void transferToRoute( + long requestId, + @NonNull UserHandle clientUserHandle, + @NonNull String clientPackageName, + String sessionOriginalId, + String routeOriginalId, + int transferReason) { + synchronized (mLock) { + var targetProviderProxyId = mOriginalRouteIdToProviderId.get(routeOriginalId); + var targetProviderProxyRecord = mProxyRecords.get(targetProviderProxyId); + // Holds the target route, if it's managed by a provider service. Holds null otherwise. + var serviceTargetRoute = + targetProviderProxyRecord != null + ? targetProviderProxyRecord.getRouteByOriginalId(routeOriginalId) + : null; + var existingSessionRecord = mPackageNameToSessionRecord.get(clientPackageName); + if (existingSessionRecord != null) { + var existingSession = existingSessionRecord.mSourceSessionInfo; + if (targetProviderProxyId != null + && TextUtils.equals( + targetProviderProxyId, existingSession.getProviderId())) { + // The currently selected route and target route both belong to the same + // provider. We tell the provider to handle the transfer. + targetProviderProxyRecord.requestTransfer( + existingSession.getOriginalId(), serviceTargetRoute); + } else { + // The target route is handled by a provider other than the target one. We need + // to release the existing session. + var currentProxyRecord = existingSessionRecord.getProxyRecord(); + if (currentProxyRecord != null) { + currentProxyRecord.releaseSession( + requestId, existingSession.getOriginalId()); + existingSessionRecord.removeSelfFromSessionMap(); + } + } + } + + if (serviceTargetRoute != null) { + boolean isGlobalSession = TextUtils.isEmpty(clientPackageName); + int uid; + if (isGlobalSession) { + uid = Process.INVALID_UID; + } else { + uid = fetchUid(clientPackageName, clientUserHandle); + if (uid == Process.INVALID_UID) { + throw new IllegalArgumentException( + "Cannot resolve transfer for " + + clientPackageName + + " and " + + clientUserHandle); + } + } + var pendingCreationCallback = + new PendingSessionCreationCallbackImpl( + targetProviderProxyId, requestId, clientPackageName); + mPendingSessionCreations.put(requestId, pendingCreationCallback); + targetProviderProxyRecord.requestCreateSystemMediaSession( + requestId, + uid, + clientPackageName, + routeOriginalId, + pendingCreationCallback); + } else { + // The target route is not provided by any of the services. Assume it's a system + // provided route. + super.transferToRoute( + requestId, + clientUserHandle, + clientPackageName, + sessionOriginalId, + routeOriginalId, + transferReason); + } + } + } + + @Nullable + @Override + public RoutingSessionInfo getSessionForPackage(String packageName) { + synchronized (mLock) { + var systemSession = super.getSessionForPackage(packageName); + if (systemSession == null) { + return null; + } + var overridingSession = mPackageNameToSessionRecord.get(packageName); + if (overridingSession != null) { + var builder = + new RoutingSessionInfo.Builder(overridingSession.mTranslatedSessionInfo) + .setProviderId(mUniqueId) + .setSystemSession(true); + for (var systemRoute : mLastSystemProviderInfo.getRoutes()) { + builder.addTransferableRoute(systemRoute.getOriginalId()); + } + return builder.build(); + } else { + return systemSession; + } + } + } + + /** + * Returns the uid that corresponds to the given name and user handle, or {@link + * Process#INVALID_UID} if a uid couldn't be found. + */ + @SuppressLint("MissingPermission") + // We clear the calling identity before calling the package manager, and we are running on the + // system_server. + private int fetchUid(String clientPackageName, UserHandle clientUserHandle) { + final long token = Binder.clearCallingIdentity(); + try { + return mPackageManager.getApplicationInfoAsUser( + clientPackageName, /* flags= */ 0, clientUserHandle) + .uid; + } catch (PackageManager.NameNotFoundException e) { + return Process.INVALID_UID; + } finally { + Binder.restoreCallingIdentity(token); + } } @Override @@ -85,7 +238,7 @@ import java.util.stream.Stream; } else { mProxyRecords.put(serviceProxy.mUniqueId, proxyRecord); } - setProviderState(buildProviderInfo()); + updateProviderInfo(); } updateSessionInfo(); notifyProviderState(); @@ -96,7 +249,7 @@ import java.util.stream.Stream; public void onSystemProviderRoutesChanged(MediaRoute2ProviderInfo providerInfo) { synchronized (mLock) { mLastSystemProviderInfo = providerInfo; - setProviderState(buildProviderInfo()); + updateProviderInfo(); } updateSessionInfo(); notifySessionInfoUpdated(); @@ -116,10 +269,13 @@ import java.util.stream.Stream; var builder = new RoutingSessionInfo.Builder(systemSessionInfo); mProxyRecords.values().stream() .flatMap(ProviderProxyRecord::getRoutesStream) - .map(MediaRoute2Info::getId) + .map(MediaRoute2Info::getOriginalId) .forEach(builder::addTransferableRoute); mSessionInfos.clear(); mSessionInfos.add(builder.build()); + for (var sessionRecords : mPackageNameToSessionRecord.values()) { + mSessionInfos.add(sessionRecords.mTranslatedSessionInfo); + } } } @@ -129,13 +285,47 @@ import java.util.stream.Stream; * provider services}. */ @GuardedBy("mLock") - private MediaRoute2ProviderInfo buildProviderInfo() { + private void updateProviderInfo() { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder(mLastSystemProviderInfo); - mProxyRecords.values().stream() - .flatMap(ProviderProxyRecord::getRoutesStream) - .forEach(builder::addRoute); - return builder.build(); + mOriginalRouteIdToProviderId.clear(); + for (var proxyRecord : mProxyRecords.values()) { + String proxyId = proxyRecord.mProxy.mUniqueId; + proxyRecord + .getRoutesStream() + .forEach( + route -> { + builder.addRoute(route); + mOriginalRouteIdToProviderId.put(route.getOriginalId(), proxyId); + }); + } + setProviderState(builder.build()); + } + + /** + * Equivalent to {@link #asSystemRouteId}, except it takes a unique route id instead of a + * original id. + */ + private static String uniqueIdAsSystemRouteId(String providerId, String uniqueRouteId) { + return asSystemRouteId(providerId, MediaRouter2Utils.getOriginalId(uniqueRouteId)); + } + + /** + * Returns a unique {@link MediaRoute2Info#getOriginalId() original id} for this provider to + * publish system media routes from {@link MediaRoute2ProviderService provider services}. + * + * <p>This provider will publish system media routes as part of the system routing session. + * However, said routes may also support {@link MediaRoute2Info#FLAG_ROUTING_TYPE_REMOTE remote + * routing}, meaning we cannot use the same id, or there would be an id collision. As a result, + * we derive a {@link MediaRoute2Info#getOriginalId original id} that is unique among all + * original route ids used by this provider. + */ + private static String asSystemRouteId(String providerId, String originalRouteId) { + return ROUTE_ID_PREFIX_SYSTEM + + ROUTE_ID_SYSTEM_SEPARATOR + + providerId + + ROUTE_ID_SYSTEM_SEPARATOR + + originalRouteId; } /** @@ -145,14 +335,69 @@ import java.util.stream.Stream; * @param mProxy The corresponding {@link MediaRoute2ProviderServiceProxy}. * @param mSystemMediaRoutes The last snapshot of routes from the service that support system * media routing, as defined by {@link MediaRoute2Info#supportsSystemMediaRouting()}. + * @param mNewOriginalIdToSourceOriginalIdMap Maps the {@link #mSystemMediaRoutes} ids to the + * original ids of corresponding {@link MediaRoute2ProviderService service} route. */ private record ProviderProxyRecord( MediaRoute2ProviderServiceProxy mProxy, - Collection<MediaRoute2Info> mSystemMediaRoutes) { + Map<String, MediaRoute2Info> mSystemMediaRoutes, + Map<String, String> mNewOriginalIdToSourceOriginalIdMap) { /** Returns a stream representation of the {@link #mSystemMediaRoutes}. */ public Stream<MediaRoute2Info> getRoutesStream() { - return mSystemMediaRoutes.stream(); + return mSystemMediaRoutes.values().stream(); + } + + @Nullable + public MediaRoute2Info getRouteByOriginalId(String routeOriginalId) { + return mSystemMediaRoutes.get(routeOriginalId); + } + + /** + * Requests the creation of a system media routing session. + * + * @param requestId The request id. + * @param uid The uid of the package whose media to route, or {@link Process#INVALID_UID} if + * not applicable. + * @param packageName The name of the package whose media to route. + * @param originalRouteId The {@link MediaRoute2Info#getOriginalId() original route id} of + * the route that should be initially selected. + * @param callback A {@link MediaRoute2ProviderServiceProxy.SystemMediaSessionCallback} for + * events. + * @see MediaRoute2ProviderService#onCreateSystemRoutingSession + */ + public void requestCreateSystemMediaSession( + long requestId, + int uid, + String packageName, + String originalRouteId, + SystemMediaSessionCallback callback) { + var targetRouteId = mNewOriginalIdToSourceOriginalIdMap.get(originalRouteId); + if (targetRouteId == null) { + Log.w( + TAG, + "Failed system media session creation due to lack of mapping for id: " + + originalRouteId); + callback.onRequestFailed( + requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE); + } else { + mProxy.requestCreateSystemMediaSession( + requestId, + uid, + packageName, + targetRouteId, + /* sessionHints= */ null, + callback); + } + } + + public void requestTransfer(String sessionId, MediaRoute2Info targetRoute) { + // TODO: Map the target route to the source route original id. + throw new UnsupportedOperationException("TODO Implement"); + } + + public void releaseSession(long requestId, String originalSessionId) { + mProxy.releaseSession(requestId, originalSessionId); } /** @@ -165,22 +410,149 @@ import java.util.stream.Stream; if (providerInfo == null) { return null; } - ArraySet<MediaRoute2Info> routes = new ArraySet<>(); - providerInfo.getRoutes().stream() - .filter(MediaRoute2Info::supportsSystemMediaRouting) - .forEach( - route -> { - String id = - ROUTE_ID_PREFIX_SYSTEM - + route.getProviderId() - + ROUTE_ID_SYSTEM_SEPARATOR - + route.getOriginalId(); - routes.add( - new MediaRoute2Info.Builder(id, route.getName()) - .addFeature(FEATURE_LIVE_AUDIO) - .build()); - }); - return new ProviderProxyRecord(serviceProxy, Collections.unmodifiableSet(routes)); + Map<String, MediaRoute2Info> routesMap = new ArrayMap<>(); + Map<String, String> idMap = new ArrayMap<>(); + for (MediaRoute2Info sourceRoute : providerInfo.getRoutes()) { + if (!sourceRoute.supportsSystemMediaRouting()) { + continue; + } + String id = + asSystemRouteId(providerInfo.getUniqueId(), sourceRoute.getOriginalId()); + var newRoute = + new MediaRoute2Info.Builder(id, sourceRoute.getName()) + .addFeature(FEATURE_LIVE_AUDIO) + .build(); + routesMap.put(id, newRoute); + idMap.put(id, sourceRoute.getOriginalId()); + } + return new ProviderProxyRecord( + serviceProxy, + Collections.unmodifiableMap(routesMap), + Collections.unmodifiableMap(idMap)); + } + } + + private class PendingSessionCreationCallbackImpl implements SystemMediaSessionCallback { + + private final String mProviderId; + private final long mRequestId; + private final String mClientPackageName; + + private PendingSessionCreationCallbackImpl( + String providerId, long requestId, String clientPackageName) { + mProviderId = providerId; + mRequestId = requestId; + mClientPackageName = clientPackageName; + } + + @Override + public void onSessionUpdate(RoutingSessionInfo sessionInfo) { + SystemMediaSessionRecord systemMediaSessionRecord = + new SystemMediaSessionRecord(mProviderId, sessionInfo); + synchronized (mLock) { + mPackageNameToSessionRecord.put(mClientPackageName, systemMediaSessionRecord); + mPendingSessionCreations.remove(mRequestId); + } + } + + @Override + public void onRequestFailed(long requestId, @Reason int reason) { + synchronized (mLock) { + mPendingSessionCreations.remove(mRequestId); + } + notifyRequestFailed(requestId, reason); + } + + @Override + public void onSessionReleased() { + // Unexpected. The session hasn't yet been created. + throw new IllegalStateException(); + } + } + + private class SystemMediaSessionRecord implements SystemMediaSessionCallback { + + private final String mProviderId; + + @GuardedBy("SystemMediaRoute2Provider2.this.mLock") + @NonNull + private RoutingSessionInfo mSourceSessionInfo; + + /** + * The same as {@link #mSourceSessionInfo}, except ids are {@link #asSystemRouteId system + * provider ids}. + */ + @GuardedBy("SystemMediaRoute2Provider2.this.mLock") + @NonNull + private RoutingSessionInfo mTranslatedSessionInfo; + + SystemMediaSessionRecord( + @NonNull String providerId, @NonNull RoutingSessionInfo sessionInfo) { + mProviderId = providerId; + mSourceSessionInfo = sessionInfo; + mTranslatedSessionInfo = asSystemProviderSession(sessionInfo); + } + + @Override + public void onSessionUpdate(RoutingSessionInfo sessionInfo) { + synchronized (mLock) { + mSourceSessionInfo = sessionInfo; + mTranslatedSessionInfo = asSystemProviderSession(sessionInfo); + } + notifySessionInfoUpdated(); + } + + @Override + public void onRequestFailed(long requestId, @Reason int reason) { + notifyRequestFailed(requestId, reason); + } + + @Override + public void onSessionReleased() { + synchronized (mLock) { + removeSelfFromSessionMap(); + } + notifySessionInfoUpdated(); + } + + @GuardedBy("SystemMediaRoute2Provider2.this.mLock") + @Nullable + public ProviderProxyRecord getProxyRecord() { + ProviderProxyRecord provider = mProxyRecords.get(mProviderId); + if (provider == null) { + // Unexpected condition where the proxy is no longer available while there's an + // ongoing session. Could happen due to a crash in the provider process. + removeSelfFromSessionMap(); + } + return provider; + } + + @GuardedBy("SystemMediaRoute2Provider2.this.mLock") + private void removeSelfFromSessionMap() { + mPackageNameToSessionRecord.remove(mSourceSessionInfo.getClientPackageName()); + } + + private RoutingSessionInfo asSystemProviderSession(RoutingSessionInfo session) { + var builder = + new RoutingSessionInfo.Builder(session) + .setProviderId(mUniqueId) + .clearSelectedRoutes() + .clearSelectableRoutes() + .clearDeselectableRoutes() + .clearTransferableRoutes(); + session.getSelectedRoutes().stream() + .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it)) + .forEach(builder::addSelectedRoute); + session.getSelectableRoutes().stream() + .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it)) + .forEach(builder::addSelectableRoute); + session.getDeselectableRoutes().stream() + .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it)) + .forEach(builder::addDeselectableRoute); + session.getTransferableRoutes().stream() + .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it)) + .forEach(builder::addTransferableRoute); + return builder.build(); } } } diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 89902f7f8321..7cbbe2938fd5 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -101,4 +101,10 @@ public interface NotificationDelegate { void onNotificationFeedbackReceived(String key, Bundle feedback); void prepareForPossibleShutdown(); + + /** + * Called when the notification should be unbundled. + * @param key the notification key + */ + void unbundleNotification(String key); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c6d7fc7508da..7375a68c547b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -112,6 +112,7 @@ import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_ import static android.service.notification.Flags.callstyleCallbackApi; import static android.service.notification.Flags.notificationClassification; import static android.service.notification.Flags.notificationForceGrouping; +import static android.service.notification.Flags.notificationRegroupOnClassification; import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle; import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; @@ -1851,6 +1852,42 @@ public class NotificationManagerService extends SystemService { } } + @Override + public void unbundleNotification(String key) { + if (!(notificationClassification() && notificationRegroupOnClassification())) { + return; + } + synchronized (mNotificationLock) { + NotificationRecord r = mNotificationsByKey.get(key); + if (r == null) { + return; + } + + if (DBG) { + Slog.v(TAG, "unbundleNotification: " + r); + } + + boolean hasOriginalSummary = false; + if (r.getSbn().isAppGroup() && r.getNotification().isGroupChild()) { + final String oldGroupKey = GroupHelper.getFullAggregateGroupKey( + r.getSbn().getPackageName(), r.getOriginalGroupKey(), r.getUserId()); + NotificationRecord groupSummary = mSummaryByGroupKey.get(oldGroupKey); + // We only care about app-provided valid groups + hasOriginalSummary = (groupSummary != null + && !GroupHelper.isAggregatedGroup(groupSummary)); + } + + // Only NotificationRecord's mChannel is updated when bundled, the Notification + // mChannelId will always be the original channel. + String origChannelId = r.getNotification().getChannelId(); + NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel( + r.getSbn().getPackageName(), r.getUid(), origChannelId, false); + if (originalChannel != null && !origChannelId.equals(r.getChannel().getId())) { + r.updateNotificationChannel(originalChannel); + mGroupHelper.onNotificationUnbundled(r, hasOriginalSummary); + } + } + } }; NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() { diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 749952e3336f..15377d6b269a 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -559,7 +559,7 @@ public class PreferencesHelper implements RankingConfig { if (r.uid == UNKNOWN_UID) { if (Flags.persistIncompleteRestoreData()) { - r.userId = userId; + r.userIdWhenUidUnknown = userId; } mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r); } else { @@ -756,7 +756,7 @@ public class PreferencesHelper implements RankingConfig { if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) { out.attributeLong(null, ATT_CREATION_TIME, r.creationTime); - out.attributeInt(null, ATT_USERID, r.userId); + out.attributeInt(null, ATT_USERID, r.userIdWhenUidUnknown); } if (!forBackup) { @@ -1959,7 +1959,7 @@ public class PreferencesHelper implements RankingConfig { ArrayList<ZenBypassingApp> bypassing = new ArrayList<>(); synchronized (mLock) { for (PackagePreferences p : mPackagePreferences.values()) { - if (p.userId != userId) { + if (UserHandle.getUserId(p.uid) != userId) { continue; } int totalChannelCount = p.channels.size(); @@ -3189,7 +3189,7 @@ public class PreferencesHelper implements RankingConfig { // Until we enable the UI, we should return false. boolean canHavePromotedNotifs = android.app.Flags.uiRichOngoing(); - @UserIdInt int userId; + @UserIdInt int userIdWhenUidUnknown; Delegate delegate = null; ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index c4d1cc723804..ec0f25169d75 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4068,7 +4068,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Nullable IBinder focusedToken) { boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event, focusedToken); - if (handled && Arrays.stream(event.getKeycodes()).anyMatch( + if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch( (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) { mPowerKeyHandled = true; } diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig index a6948fcbae49..a975da32f2fd 100644 --- a/services/core/java/com/android/server/power/feature/power_flags.aconfig +++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig @@ -31,7 +31,7 @@ flag { name: "framework_wakelock_info" namespace: "power" description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms" - bug: "352602149" + bug: "380847722" } flag { diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index aae7417970eb..83461125b404 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -20,6 +20,7 @@ import static android.os.Flags.adpfUseFmqChannel; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.power.hint.Flags.adpfSessionTag; +import static com.android.server.power.hint.Flags.cpuHeadroomAffinityCheck; import static com.android.server.power.hint.Flags.powerhintThreadCleanup; import static com.android.server.power.hint.Flags.resetOnForkEnabled; @@ -191,7 +192,7 @@ public final class HintManagerService extends SystemService { private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms"; private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid"; - + private static final String PROPERTY_CHECK_HEADROOM_AFFINITY = "persist.hms.check_affinity"; private Boolean mFMQUsesIntegratedEventFlag = false; private final Object mCpuHeadroomLock = new Object(); @@ -1501,6 +1502,10 @@ public final class HintManagerService extends SystemService { } } } + if (cpuHeadroomAffinityCheck() && params.tids.length > 1 + && SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_AFFINITY, true)) { + checkThreadAffinityForTids(params.tids); + } halParams.tids = params.tids; } if (halParams.calculationWindowMillis @@ -1529,6 +1534,27 @@ public final class HintManagerService extends SystemService { return null; } } + private void checkThreadAffinityForTids(int[] tids) { + long[] reference = null; + for (int tid : tids) { + long[] affinity; + try { + affinity = Process.getSchedAffinity(tid); + } catch (Exception e) { + Slog.e(TAG, "Failed to get affinity " + tid, e); + throw new IllegalStateException("Could not check affinity for tid " + tid); + } + if (reference == null) { + reference = affinity; + } else if (!Arrays.equals(reference, affinity)) { + Slog.d(TAG, "Thread affinity is different: tid " + + tids[0] + "->" + Arrays.toString(reference) + ", tid " + + tid + "->" + Arrays.toString(affinity)); + throw new IllegalStateException("Thread affinity is not the same for tids " + + Arrays.toString(tids)); + } + } + } private void checkCpuHeadroomParams(CpuHeadroomParamsInternal params) { boolean calculationTypeMatched = false; diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index fe14f6b172f1..95690cd63994 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -600,13 +600,7 @@ public class BatteryStatsImpl extends BatteryStats { private final int mFlags; private final Long mDefaultPowerStatsThrottlePeriod; private final Map<String, Long> mPowerStatsThrottlePeriods; - - @VisibleForTesting - public BatteryStatsConfig() { - mFlags = 0; - mDefaultPowerStatsThrottlePeriod = 0L; - mPowerStatsThrottlePeriods = Map.of(); - } + private final int mMaxHistorySizeBytes; private BatteryStatsConfig(Builder builder) { int flags = 0; @@ -619,6 +613,7 @@ public class BatteryStatsImpl extends BatteryStats { mFlags = flags; mDefaultPowerStatsThrottlePeriod = builder.mDefaultPowerStatsThrottlePeriod; mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods; + mMaxHistorySizeBytes = builder.mMaxHistorySizeBytes; } /** @@ -648,18 +643,24 @@ public class BatteryStatsImpl extends BatteryStats { mDefaultPowerStatsThrottlePeriod); } + public int getMaxHistorySizeBytes() { + return mMaxHistorySizeBytes; + } + /** * Builder for BatteryStatsConfig */ public static class Builder { private boolean mResetOnUnplugHighBatteryLevel; private boolean mResetOnUnplugAfterSignificantCharge; - public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD = + private static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD = TimeUnit.HOURS.toMillis(1); - public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU = + private static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU = TimeUnit.MINUTES.toMillis(1); + private static final int DEFAULT_MAX_HISTORY_SIZE = 4 * 1024 * 1024; private long mDefaultPowerStatsThrottlePeriod = DEFAULT_POWER_STATS_THROTTLE_PERIOD; private final Map<String, Long> mPowerStatsThrottlePeriods = new HashMap<>(); + private int mMaxHistorySizeBytes = DEFAULT_MAX_HISTORY_SIZE; public Builder() { mResetOnUnplugHighBatteryLevel = true; @@ -712,6 +713,15 @@ public class BatteryStatsImpl extends BatteryStats { mDefaultPowerStatsThrottlePeriod = periodMs; return this; } + + /** + * Sets the maximum amount of disk space, in bytes, that battery history can + * utilize. As this space fills up, the oldest history chunks must be expunged. + */ + public Builder setMaxHistorySizeBytes(int maxHistorySizeBytes) { + mMaxHistorySizeBytes = maxHistorySizeBytes; + return this; + } } } @@ -11425,7 +11435,7 @@ public class BatteryStatsImpl extends BatteryStats { } mHistory = new BatteryStatsHistory(null /* historyBuffer */, systemDir, - mConstants.MAX_HISTORY_FILES, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, + mConstants.MAX_HISTORY_SIZE, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock, traceDelegate, eventLogger); mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector); @@ -11970,9 +11980,8 @@ public class BatteryStatsImpl extends BatteryStats { return mNextMaxDailyDeadlineMs; } - @GuardedBy("this") public int getHistoryTotalSize() { - return mConstants.MAX_HISTORY_BUFFER * mConstants.MAX_HISTORY_FILES; + return mHistory.getMaxHistorySize(); } public int getHistoryUsedSize() { @@ -16101,7 +16110,7 @@ public class BatteryStatsImpl extends BatteryStats { = "battery_level_collection_delay_ms"; public static final String KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS = "procstate_change_collection_delay_ms"; - public static final String KEY_MAX_HISTORY_FILES = "max_history_files"; + public static final String KEY_MAX_HISTORY_SIZE = "max_history_size"; public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb"; public static final String KEY_BATTERY_CHARGED_DELAY_MS = "battery_charged_delay_ms"; @@ -16152,9 +16161,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000; private static final long DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS = 300_000; private static final long DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS = 60_000; - private static final int DEFAULT_MAX_HISTORY_FILES = 32; private static final int DEFAULT_MAX_HISTORY_BUFFER_KB = 128; /*Kilo Bytes*/ - private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64; private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/ private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */ private static final int DEFAULT_BATTERY_CHARGING_ENFORCE_LEVEL = 90; @@ -16176,7 +16183,7 @@ public class BatteryStatsImpl extends BatteryStats { = DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS; public long PROC_STATE_CHANGE_COLLECTION_DELAY_MS = DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS; - public int MAX_HISTORY_FILES; + public int MAX_HISTORY_SIZE; public int MAX_HISTORY_BUFFER; /*Bytes*/ public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS; public int BATTERY_CHARGING_ENFORCE_LEVEL = DEFAULT_BATTERY_CHARGING_ENFORCE_LEVEL; @@ -16192,12 +16199,11 @@ public class BatteryStatsImpl extends BatteryStats { public Constants(Handler handler) { super(handler); if (isLowRamDevice()) { - MAX_HISTORY_FILES = DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE; MAX_HISTORY_BUFFER = DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB * 1024; } else { - MAX_HISTORY_FILES = DEFAULT_MAX_HISTORY_FILES; MAX_HISTORY_BUFFER = DEFAULT_MAX_HISTORY_BUFFER_KB * 1024; } + MAX_HISTORY_SIZE = mBatteryStatsConfig.getMaxHistorySizeBytes(); } public void startObserving(ContentResolver resolver) { @@ -16260,13 +16266,23 @@ public class BatteryStatsImpl extends BatteryStats { PROC_STATE_CHANGE_COLLECTION_DELAY_MS = mParser.getLong( KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS, DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS); - MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES, - isLowRamDevice() ? DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE - : DEFAULT_MAX_HISTORY_FILES); MAX_HISTORY_BUFFER = mParser.getInt(KEY_MAX_HISTORY_BUFFER_KB, isLowRamDevice() ? DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB : DEFAULT_MAX_HISTORY_BUFFER_KB) * 1024; + int maxHistorySize = mParser.getInt(KEY_MAX_HISTORY_SIZE, -1); + if (maxHistorySize == -1) { + // Process the deprecated max_history_files parameter for compatibility + int maxHistoryFiles = mParser.getInt("max_history_files", -1); + if (maxHistoryFiles != -1) { + maxHistorySize = maxHistoryFiles * MAX_HISTORY_BUFFER; + } + } + if (maxHistorySize == -1) { + maxHistorySize = mBatteryStatsConfig.getMaxHistorySizeBytes(); + } + MAX_HISTORY_SIZE = maxHistorySize; + final String perUidModemModel = mParser.getString(KEY_PER_UID_MODEM_POWER_MODEL, ""); PER_UID_MODEM_MODEL = getPerUidModemModel(perUidModemModel); @@ -16291,7 +16307,7 @@ public class BatteryStatsImpl extends BatteryStats { */ @VisibleForTesting public void onChange() { - mHistory.setMaxHistoryFiles(MAX_HISTORY_FILES); + mHistory.setMaxHistorySize(MAX_HISTORY_SIZE); mHistory.setMaxHistoryBufferSize(MAX_HISTORY_BUFFER); } @@ -16354,8 +16370,8 @@ public class BatteryStatsImpl extends BatteryStats { pw.println(BATTERY_LEVEL_COLLECTION_DELAY_MS); pw.print(KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS); pw.print("="); pw.println(PROC_STATE_CHANGE_COLLECTION_DELAY_MS); - pw.print(KEY_MAX_HISTORY_FILES); pw.print("="); - pw.println(MAX_HISTORY_FILES); + pw.print(KEY_MAX_HISTORY_SIZE); pw.print("="); + pw.println(MAX_HISTORY_SIZE); pw.print(KEY_MAX_HISTORY_BUFFER_KB); pw.print("="); pw.println(MAX_HISTORY_BUFFER/1024); pw.print(KEY_BATTERY_CHARGED_DELAY_MS); pw.print("="); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index f8877ad912b5..c18918fd9f8b 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -2175,6 +2175,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } } + /** + * Called when the notification should be unbundled. + * @param key the notification key + */ + @Override + public void unbundleNotification(@Nullable String key) { + enforceStatusBarService(); + enforceValidCallingUser(); + Binder.withCleanCallingIdentity(() -> { + mNotificationDelegate.unbundleNotification(key); + }); + } + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java index dad3a784cfb9..bb163ef4c1d5 100644 --- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java +++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java @@ -369,10 +369,9 @@ public class CacheQuotaStrategy implements RemoteCallback.OnResultListener { tagName = parser.getName(); if (TAG_QUOTA.equals(tagName)) { CacheQuotaHint request = getRequestFromXml(parser); - if (request == null) { - continue; + if (request != null) { + quotas.add(request); } - quotas.add(request); } } eventType = parser.next(); diff --git a/services/core/java/com/android/server/updates/Android.bp b/services/core/java/com/android/server/updates/Android.bp deleted file mode 100644 index 10beebb82711..000000000000 --- a/services/core/java/com/android/server/updates/Android.bp +++ /dev/null @@ -1,11 +0,0 @@ -aconfig_declarations { - name: "updates_flags", - package: "com.android.server.updates", - container: "system", - srcs: ["*.aconfig"], -} - -java_aconfig_library { - name: "updates_flags_lib", - aconfig_declarations: "updates_flags", -} diff --git a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java deleted file mode 100644 index af4025e1db7c..000000000000 --- a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.updates; - -import android.content.Context; -import android.content.Intent; -import android.os.FileUtils; -import android.system.ErrnoException; -import android.system.Os; -import android.util.Slog; - -import libcore.io.Streams; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileFilter; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInstallReceiver { - - private static final String TAG = "CTLogInstallReceiver"; - private static final String LOGDIR_PREFIX = "logs-"; - - public CertificateTransparencyLogInstallReceiver() { - super("/data/misc/keychain/ct/", "ct_logs", "metadata/", "version"); - } - - @Override - protected void install(InputStream inputStream, int version) throws IOException { - if (!Flags.certificateTransparencyInstaller()) { - return; - } - // To support atomically replacing the old configuration directory with the new there's a - // bunch of steps. We create a new directory with the logs and then do an atomic update of - // the current symlink to point to the new directory. - // 1. Ensure that the update dir exists and is readable - updateDir.mkdir(); - if (!updateDir.isDirectory()) { - throw new IOException("Unable to make directory " + updateDir.getCanonicalPath()); - } - if (!updateDir.setReadable(true, false)) { - throw new IOException("Unable to set permissions on " + updateDir.getCanonicalPath()); - } - File currentSymlink = new File(updateDir, "current"); - File newVersion = new File(updateDir, LOGDIR_PREFIX + String.valueOf(version)); - // 2. Handle the corner case where the new directory already exists. - if (newVersion.exists()) { - // If the symlink has already been updated then the update died between steps 7 and 8 - // and so we cannot delete the directory since its in use. Instead just bump the version - // and return. - if (newVersion.getCanonicalPath().equals(currentSymlink.getCanonicalPath())) { - writeUpdate( - updateDir, - updateVersion, - new ByteArrayInputStream(Long.toString(version).getBytes())); - deleteOldLogDirectories(); - return; - } else { - FileUtils.deleteContentsAndDir(newVersion); - } - } - try { - // 3. Create /data/misc/keychain/ct/<new_version>/ . - newVersion.mkdir(); - if (!newVersion.isDirectory()) { - throw new IOException("Unable to make directory " + newVersion.getCanonicalPath()); - } - if (!newVersion.setReadable(true, false)) { - throw new IOException( - "Failed to set " + newVersion.getCanonicalPath() + " readable"); - } - - // 4. Validate the log list json and move the file in <new_version>/ . - installLogList(newVersion, inputStream); - - // 5. Create the temp symlink. We'll rename this to the target symlink to get an atomic - // update. - File tempSymlink = new File(updateDir, "new_symlink"); - try { - Os.symlink(newVersion.getCanonicalPath(), tempSymlink.getCanonicalPath()); - } catch (ErrnoException e) { - throw new IOException("Failed to create symlink", e); - } - - // 6. Update the symlink target, this is the actual update step. - tempSymlink.renameTo(currentSymlink.getAbsoluteFile()); - } catch (IOException | RuntimeException e) { - FileUtils.deleteContentsAndDir(newVersion); - throw e; - } - Slog.i(TAG, "CT log directory updated to " + newVersion.getAbsolutePath()); - // 7. Update the current version information - writeUpdate( - updateDir, - updateVersion, - new ByteArrayInputStream(Long.toString(version).getBytes())); - // 8. Cleanup - deleteOldLogDirectories(); - } - - @Override - protected void postInstall(Context context, Intent intent) { - if (!Flags.certificateTransparencyInstaller()) { - return; - } - } - - private void installLogList(File directory, InputStream inputStream) throws IOException { - try { - byte[] content = Streams.readFullyNoClose(inputStream); - if (new JSONObject(new String(content, StandardCharsets.UTF_8)).length() == 0) { - throw new IOException("Log list data not valid"); - } - - File file = new File(directory, "log_list.json"); - try (FileOutputStream outputStream = new FileOutputStream(file)) { - outputStream.write(content); - } - if (!file.setReadable(true, false)) { - throw new IOException("Failed to set permissions on " + file.getCanonicalPath()); - } - } catch (JSONException e) { - throw new IOException("Malformed json in log list", e); - } - } - - private void deleteOldLogDirectories() throws IOException { - if (!updateDir.exists()) { - return; - } - File currentTarget = new File(updateDir, "current").getCanonicalFile(); - FileFilter filter = - new FileFilter() { - @Override - public boolean accept(File file) { - return !currentTarget.equals(file) - && file.getName().startsWith(LOGDIR_PREFIX); - } - }; - for (File f : updateDir.listFiles(filter)) { - FileUtils.deleteContentsAndDir(f); - } - } -} diff --git a/services/core/java/com/android/server/updates/flags.aconfig b/services/core/java/com/android/server/updates/flags.aconfig deleted file mode 100644 index 476cb3723c97..000000000000 --- a/services/core/java/com/android/server/updates/flags.aconfig +++ /dev/null @@ -1,10 +0,0 @@ -package: "com.android.server.updates" -container: "system" - -flag { - name: "certificate_transparency_installer" - is_exported: true - namespace: "network_security" - description: "Enable certificate transparency installer for log list data" - bug: "319829948" -} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 64758eb5144a..83b273c04648 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5624,13 +5624,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // drawn, they never will be, and we are sad. setClientVisible(true); - requestUpdateWallpaperIfNeeded(); + if (!mWmService.mFlags.mEnsureWallpaperInTransitions) { + requestUpdateWallpaperIfNeeded(); + } ProtoLog.v(WM_DEBUG_ADD_REMOVE, "No longer Stopped: %s", this); mAppStopped = false; transferStartingWindowFromHiddenAboveTokenIfNeeded(); } + if (mWmService.mFlags.mEnsureWallpaperInTransitions) { + requestUpdateWallpaperIfNeeded(); + } // Defer committing visibility until transition starts. if (isCollecting) { diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 1a9d21187ddb..6709e3a72db0 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -25,6 +25,9 @@ import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.app.admin.DevicePolicyManager.EXTRA_RESTRICTION; import static android.app.admin.DevicePolicyManager.POLICY_SUSPEND_PACKAGES; import static android.content.Context.KEYGUARD_SERVICE; +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.CATEGORY_HOME; +import static android.content.Intent.CATEGORY_SECONDARY_HOME; import static android.content.Intent.EXTRA_INTENT; import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.EXTRA_TASK_ID; @@ -40,6 +43,7 @@ import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.TaskInfo; import android.app.admin.DevicePolicyManagerInternal; +import android.content.ComponentName; import android.content.Context; import android.content.IIntentSender; import android.content.Intent; @@ -67,6 +71,7 @@ import com.android.internal.app.UnlaunchableAppActivity; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult; +import com.android.window.flags.Flags; /** * A class that contains activity intercepting logic for {@link ActivityStarter#execute()} @@ -119,6 +124,11 @@ class ActivityStartInterceptor { */ TaskDisplayArea mPresumableLaunchDisplayArea; + /** + * Whether the component is specified originally in the given Intent. + */ + boolean mComponentSpecified; + ActivityStartInterceptor( ActivityTaskManagerService service, ActivityTaskSupervisor supervisor) { this(service, supervisor, service.mContext); @@ -185,6 +195,14 @@ class ActivityStartInterceptor { return TaskFragment.fromTaskFragmentToken(taskFragToken, mService); } + // TODO: consolidate this method with the one below since this is used for test only. + boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, + Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid, + ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea) { + return intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment, callingPid, + callingUid, activityOptions, presumableLaunchDisplayArea, false); + } + /** * Intercept the launch intent based on various signals. If an interception happened the * internal variables get assigned and need to be read explicitly by the caller. @@ -193,7 +211,8 @@ class ActivityStartInterceptor { */ boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid, - ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea) { + ActivityOptions activityOptions, TaskDisplayArea presumableLaunchDisplayArea, + boolean componentSpecified) { mUserManager = UserManager.get(mServiceContext); mIntent = intent; @@ -206,6 +225,7 @@ class ActivityStartInterceptor { mInTaskFragment = inTaskFragment; mActivityOptions = activityOptions; mPresumableLaunchDisplayArea = presumableLaunchDisplayArea; + mComponentSpecified = componentSpecified; if (interceptQuietProfileIfNeeded()) { // If work profile is turned off, skip the work challenge since the profile can only @@ -230,7 +250,8 @@ class ActivityStartInterceptor { } if (interceptHomeIfNeeded()) { // Replace primary home intents directed at displays that do not support primary home - // but support secondary home with the relevant secondary home activity. + // but support secondary home with the relevant secondary home activity. Or the home + // intent is not in the correct format. return true; } @@ -479,9 +500,78 @@ class ActivityStartInterceptor { if (mPresumableLaunchDisplayArea == null || mService.mRootWindowContainer == null) { return false; } - if (!ActivityRecord.isHomeIntent(mIntent)) { - return false; + + boolean intercepted = false; + if (Flags.normalizeHomeIntent()) { + if (!ACTION_MAIN.equals(mIntent.getAction()) || (!mIntent.hasCategory(CATEGORY_HOME) + && !mIntent.hasCategory(CATEGORY_SECONDARY_HOME))) { + // not a home intent + return false; + } + + if (mComponentSpecified) { + final ComponentName homeComponent = mIntent.getComponent(); + final Intent homeIntent = mService.getHomeIntent(); + final ActivityInfo aInfo = mService.mRootWindowContainer.resolveHomeActivity( + mUserId, homeIntent); + if (!aInfo.getComponentName().equals(homeComponent)) { + // Do nothing if the intent is not for the default home component. + return false; + } + } + + if (!ActivityRecord.isHomeIntent(mIntent) || mComponentSpecified) { + // This is not a standard home intent, make it so if possible. + normalizeHomeIntent(); + intercepted = true; + } + } else { + if (!ActivityRecord.isHomeIntent(mIntent)) { + return false; + } + } + + intercepted |= replaceToSecondaryHomeIntentIfNeeded(); + if (intercepted) { + mCallingPid = mRealCallingPid; + mCallingUid = mRealCallingUid; + mResolvedType = null; + + mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, /* flags= */ 0, + mRealCallingUid, mRealCallingPid); + mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, /*profilerInfo=*/ + null); + } + return intercepted; + } + + private void normalizeHomeIntent() { + Slog.w(TAG, "The home Intent is not correctly formatted"); + if (mIntent.getCategories().size() > 1) { + Slog.d(TAG, "Purge home intent categories"); + boolean isSecondaryHome = false; + final Object[] categories = mIntent.getCategories().toArray(); + for (int i = categories.length - 1; i >= 0; i--) { + final String category = (String) categories[i]; + if (CATEGORY_SECONDARY_HOME.equals(category)) { + isSecondaryHome = true; + } + mIntent.removeCategory(category); + } + mIntent.addCategory(isSecondaryHome ? CATEGORY_SECONDARY_HOME : CATEGORY_HOME); + } + if (mIntent.getType() != null || mIntent.getData() != null) { + Slog.d(TAG, "Purge home intent data/type"); + mIntent.setType(null); } + if (mComponentSpecified) { + Slog.d(TAG, "Purge home intent component, " + mIntent.getComponent()); + mIntent.setComponent(null); + } + mIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); + } + + private boolean replaceToSecondaryHomeIntentIfNeeded() { if (!mIntent.hasCategory(Intent.CATEGORY_HOME)) { // Already a secondary home intent, leave it alone. return false; @@ -506,13 +596,6 @@ class ActivityStartInterceptor { // and should not be moved to the caller's task. Also, activities cannot change their type, // e.g. a standard activity cannot become a home activity. mIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); - mCallingPid = mRealCallingPid; - mCallingUid = mRealCallingUid; - mResolvedType = null; - - mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, /* flags= */ 0, - mRealCallingUid, mRealCallingPid); - mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, /*profilerInfo=*/ null); return true; } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index acb93844c945..0ab2ffe3e298 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1341,7 +1341,8 @@ class ActivityStarter { callingPackage, callingFeatureId); if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment, - callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) { + callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea, + request.componentSpecified)) { // activity start was intercepted, e.g. because the target user is currently in quiet // mode (turn off work) or the target application is suspended intent = mInterceptor.mIntent; diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 0aff1de72cb1..bf57f56df7c2 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -77,6 +77,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_TASK_MSG; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; +import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled; import static com.android.server.wm.ClientLifecycleManager.shouldDispatchLaunchActivityItemIndependently; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE; @@ -2525,7 +2526,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { void scheduleUpdatePictureInPictureModeIfNeeded(Task task, Rect targetRootTaskBounds) { task.forAllActivities(r -> { if (!r.attachedToProcess()) return; - mPipModeChangedActivities.add(r); + if (!isPip2ExperimentEnabled()) mPipModeChangedActivities.add(r); // If we are scheduling pip change, then remove this activity from multi-window // change list as the processing of pip change will make sure multi-window changed // message is processed in the right order relative to pip changed. @@ -2534,7 +2535,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { mPipModeChangedTargetRootTaskBounds = targetRootTaskBounds; - if (!mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) { + if (!isPip2ExperimentEnabled() && !mHandler.hasMessages(REPORT_PIP_MODE_CHANGED_MSG)) { mHandler.sendEmptyMessage(REPORT_PIP_MODE_CHANGED_MSG); } } diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java index 7aed33d94223..16e20297dcf3 100644 --- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java @@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCA import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; import static android.content.pm.ActivityInfo.isFixedOrientation; import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; +import static android.content.pm.ActivityInfo.isFixedOrientationPortrait; import static android.content.pm.ActivityInfo.screenOrientationToString; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; @@ -66,6 +67,7 @@ class AppCompatOrientationPolicy { final boolean shouldCameraCompatControlOrientation = AppCompatCameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord); if (hasFullscreenOverride && isIgnoreOrientationRequestEnabled + && (isFixedOrientationLandscape(candidate) || isFixedOrientationPortrait(candidate)) // Do not override orientation to fullscreen for camera activities. // Fixed-orientation activities are rarely tested in other orientations, and it // often results in sideways or stretched previews. As the camera compat treatment diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java index d0d3d4321a0a..f3b043bb51dd 100644 --- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java @@ -60,7 +60,7 @@ class AppCompatSizeCompatModePolicy { /** * The precomputed display insets for resolving configuration. It will be non-null if - * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}. + * {@link ActivityRecord#shouldCreateAppCompatDisplayInsets} returns {@code true}. */ @Nullable private AppCompatDisplayInsets mAppCompatDisplayInsets; @@ -84,7 +84,7 @@ class AppCompatSizeCompatModePolicy { } /** - * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without + * @return The {@code true} if the current instance has {@link #mAppCompatDisplayInsets} without * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()} */ boolean hasAppCompatDisplayInsetsWithoutInheritance() { diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index a41832498880..0369a0ff4c76 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -285,7 +285,7 @@ final class AppCompatUtils { info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxBounds = null; info.cameraCompatTaskInfo.freeformCameraCompatMode = - CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; + CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED; info.clearTopActivityFlags(); } } diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java index 1b9454ec2272..f5bc9f0f9a47 100644 --- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -21,6 +21,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED; import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS; import static android.app.WindowConfiguration.WINDOW_CONFIG_DISPLAY_ROTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; @@ -235,7 +236,8 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa } boolean isInFreeformCameraCompatMode(@NonNull ActivityRecord activity) { - return getCameraCompatMode(activity) != CAMERA_COMPAT_FREEFORM_NONE; + return getCameraCompatMode(activity) != CAMERA_COMPAT_FREEFORM_UNSPECIFIED + && getCameraCompatMode(activity) != CAMERA_COMPAT_FREEFORM_NONE; } float getCameraCompatAspectRatio(@NonNull ActivityRecord activityRecord) { diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 8eccffd8fe3b..a4e58ef923b8 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -368,6 +368,15 @@ final class ContentRecorder implements WindowContainerListener { return; } + final SurfaceControl sourceSurface = mRecordedWindowContainer.getSurfaceControl(); + if (sourceSurface == null || !sourceSurface.isValid()) { + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Unable to start recording for display %d since the " + + "surface is null or have been released.", + mDisplayContent.getDisplayId()); + return; + } + final int contentToRecord = mContentRecordingSession.getContentToRecord(); // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are inaccurate. @@ -395,8 +404,7 @@ final class ContentRecorder implements WindowContainerListener { mDisplayContent.getDisplayId(), mDisplayContent.getDisplayInfo().state); // Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture. - mRecordedSurface = SurfaceControl.mirrorSurface( - mRecordedWindowContainer.getSurfaceControl()); + mRecordedSurface = SurfaceControl.mirrorSurface(sourceSurface); SurfaceControl.Transaction transaction = mDisplayContent.mWmService.mTransactionFactory.get() // Set the mMirroredSurface's parent to the root SurfaceControl for this diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f8086615b7d1..4dd950ba6ee9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2908,6 +2908,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp && !mDisplayRotation.isRotatingSeamlessly()) { clearFixedRotationLaunchingApp(); } + // If there won't be a transition to notify the launch is done, then it should be ready to + // update with display orientation. E.g. a translucent activity enters pip from a task which + // contains another opaque activity. + if (mFixedRotationLaunchingApp != null && mFixedRotationLaunchingApp.isVisible() + && !mTransitionController.isCollecting() + && !mAtmService.mBackNavigationController.isMonitoringFinishTransition()) { + final Transition finishTransition = mTransitionController.mFinishingTransition; + if (finishTransition == null || !finishTransition.mParticipants.contains( + mFixedRotationLaunchingApp)) { + continueUpdateOrientationForDiffOrienLaunchingApp(); + } + } } /** diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index 98521d36ad44..dede7676a4b6 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -21,6 +21,7 @@ jiamingliu@google.com pdwilliams@google.com charlesccchen@google.com marziana@google.com +mcarli@google.com # Files related to background activity launches per-file Background*Start* = set noparent diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 7fdc2c67b5ce..44f000da3d73 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1515,9 +1515,9 @@ class RecentTasks { boolean skipExcludedCheck) { if (!skipExcludedCheck) { // Keep the most recent task of home display even if it is excluded from recents. - final boolean isExcludeFromRecents = - (task.getBaseIntent().getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - == FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; + final boolean isExcludeFromRecents = task.getBaseIntent() != null + && (task.getBaseIntent().getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + == FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; if (isExcludeFromRecents) { if (DEBUG_RECENTS_TRIM_TASKS) { Slog.d(TAG, diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 76d8861022bb..9062afb50acb 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3652,14 +3652,6 @@ class Task extends TaskFragment { // If the developer has persist a different configuration, we need to override it to the // starting window because persisted configuration does not effect to Task. info.taskInfo.configuration.setTo(activity.getConfiguration()); - if (!Flags.drawSnapshotAspectRatioMatch()) { - final WindowState mainWindow = getTopFullscreenMainWindow(); - if (mainWindow != null) { - info.topOpaqueWindowInsetsState = - mainWindow.getInsetsStateWithVisibilityOverride(); - info.topOpaqueWindowLayoutParams = mainWindow.getAttrs(); - } - } return info; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index b4a22b0dd034..1f539a129e7d 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1439,7 +1439,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } } - if (wt == null) continue; + if (wt == null || !wt.isVisible()) continue; final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget(); final boolean isTargetInvisible = target == null || !target.mToken.isVisible(); final boolean isWallpaperVisibleAtEnd = @@ -2277,19 +2277,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } - /** - - * Wallpaper will set itself as target if it wants to keep itself visible without a target. - */ - private static boolean wallpaperIsOwnTarget(WallpaperWindowToken wallpaper) { - final WindowState target = - wallpaper.getDisplayContent().mWallpaperController.getWallpaperTarget(); - return target != null && target.isDescendantOf(wallpaper); - } - - /** - * Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones - */ private void commitVisibleWallpapers(SurfaceControl.Transaction t) { boolean showWallpaper = shouldWallpaperBeVisible(); for (int i = mParticipants.size() - 1; i >= 0; --i) { @@ -2297,11 +2284,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (wallpaper != null) { if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) { wallpaper.commitVisibility(showWallpaper); - } else if (wallpaper.mWmService.mFlags.mEnsureWallpaperInTransitions - && wallpaper.isVisible() - && !showWallpaper && !wallpaper.getDisplayContent().isKeyguardLocked() - && !wallpaperIsOwnTarget(wallpaper)) { - wallpaper.setVisibleRequested(false); } if (showWallpaper && wallpaper.isVisibleRequested()) { for (int j = wallpaper.mChildren.size() - 1; j >= 0; --j) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 7565b5d9fd4e..c1ef208d1d4d 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -895,7 +895,11 @@ class WallpaperController { } } - updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked()); + boolean visibleRequested = visible; + if (mDisplayContent.mWmService.mFlags.mEnsureWallpaperInTransitions) { + visibleRequested = mWallpaperTarget != null && mWallpaperTarget.isVisibleRequested(); + } + updateWallpaperTokens(visibleRequested, mDisplayContent.isKeyguardLocked()); ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper at display %d - visibility: %b, keyguardLocked: %b", @@ -1104,7 +1108,6 @@ class WallpaperController { for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken t = mWallpaperTokens.get(i); pw.print(prefix); pw.println("token " + t + ":"); - pw.print(prefix); pw.print(" canShowWhenLocked="); pw.println(t.canShowWhenLocked()); dumpValue(pw, prefix, "mWallpaperX", t.mWallpaperX); dumpValue(pw, prefix, "mWallpaperY", t.mWallpaperY); dumpValue(pw, prefix, "mWallpaperXStep", t.mWallpaperXStep); diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 2c926fcae26a..0ecd0251ca94 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -33,6 +33,7 @@ import android.util.SparseArray; import com.android.internal.protolog.ProtoLog; +import java.io.PrintWriter; import java.util.function.Consumer; /** @@ -103,6 +104,7 @@ class WallpaperWindowToken extends WindowToken { return; } mShowWhenLocked = showWhenLocked; + stringName = null; // Move the window token to the front (private) or back (showWhenLocked). This is possible // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER windows. final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP; @@ -286,13 +288,18 @@ class WallpaperWindowToken extends WindowToken { } @Override + void dump(PrintWriter pw, String prefix, boolean dumpAll) { + super.dump(pw, prefix, dumpAll); + pw.print(prefix); pw.print("visibleRequested="); pw.print(mVisibleRequested); + pw.print(" visible="); pw.println(isVisible()); + } + + @Override public String toString() { if (stringName == null) { - StringBuilder sb = new StringBuilder(); - sb.append("WallpaperWindowToken{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" token="); sb.append(token); sb.append('}'); - stringName = sb.toString(); + stringName = "WallpaperWindowToken{" + + Integer.toHexString(System.identityHashCode(this)) + + " showWhenLocked=" + mShowWhenLocked + '}'; } return stringName; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index fb197c566b7d..e45ada9438ae 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -80,6 +80,7 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_ORG import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission; +import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled; import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; @@ -716,6 +717,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if (forceHiddenForPip) { wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */); + } + if (forceHiddenForPip && !isPip2ExperimentEnabled()) { // When removing pip, make sure that onStop is sent to the app ahead of // onPictureInPictureModeChanged. // See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 01639cc3b516..d26539c377a9 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -80,6 +80,7 @@ cc_library_static { ":lib_oomConnection_native", ":lib_anrTimer_native", ":lib_lazilyRegisteredServices_native", + ":lib_phantomProcessList_native", ], include_dirs: [ @@ -265,3 +266,10 @@ filegroup { "com_android_server_vr_VrManagerService.cpp", ], } + +filegroup { + name: "lib_phantomProcessList_native", + srcs: [ + "com_android_server_am_PhantomProcessList.cpp", + ], +} diff --git a/services/core/jni/com_android_server_am_PhantomProcessList.cpp b/services/core/jni/com_android_server_am_PhantomProcessList.cpp new file mode 100644 index 000000000000..0c5e6d85919a --- /dev/null +++ b/services/core/jni/com_android_server_am_PhantomProcessList.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <processgroup/processgroup.h> + +namespace android { +namespace { + +jstring getCgroupProcsPath(JNIEnv* env, jobject clazz, jint uid, jint pid) { + if (uid < 0) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "uid is negative: %d", uid); + return nullptr; + } + + std::string path; + if (!CgroupGetAttributePathForProcess("CgroupProcs", uid, pid, path)) { + path.clear(); + } + + return env->NewStringUTF(path.c_str()); +} + +const JNINativeMethod sMethods[] = { + {"nativeGetCgroupProcsPath", "(II)Ljava/lang/String;", (void*)getCgroupProcsPath}, +}; + +} // anonymous namespace + +int register_android_server_am_PhantomProcessList(JNIEnv* env) { + const char* className = "com/android/server/am/PhantomProcessList"; + return jniRegisterNativeMethods(env, className, sMethods, NELEM(sMethods)); +} + +} // namespace android
\ No newline at end of file diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 04642302ce45..2403934cbeb8 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -553,6 +553,9 @@ private: PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type); bool isDisplayInteractive(ui::LogicalDisplayId displayId); + // TODO(b/362719483) remove when the real topology is available + void populateFakeDisplayTopology(const std::vector<DisplayViewport>& viewports); + static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); } }; @@ -641,6 +644,49 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO mInputManager->getChoreographer().setDisplayViewports(viewports); mInputManager->getReader().requestRefreshConfiguration( InputReaderConfiguration::Change::DISPLAY_INFO); + + // TODO(b/362719483) remove when the real topology is available + populateFakeDisplayTopology(viewports); +} + +void NativeInputManager::populateFakeDisplayTopology( + const std::vector<DisplayViewport>& viewports) { + if (!com::android::input::flags::connected_displays_cursor()) { + return; + } + + // create a fake topology assuming following order + // default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ... + // This also adds a 100px offset on corresponding edge for better manual testing + // ┌────────┐ + // │ next ├─────────┐ + // ┌─└───────┐┤ next 2 │ ... + // │ default │└─────────┘ + // └─────────┘ + DisplayTopologyGraph displaytopology; + displaytopology.primaryDisplayId = ui::LogicalDisplayId::DEFAULT; + + // treat default display as base, in real topology it should be the primary-display + ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT; + for (const auto& viewport : viewports) { + if (viewport.displayId == ui::LogicalDisplayId::DEFAULT) { + continue; + } + if (previousDisplay == ui::LogicalDisplayId::DEFAULT) { + displaytopology.graph[previousDisplay].push_back( + {viewport.displayId, DisplayTopologyPosition::TOP, 100}); + displaytopology.graph[viewport.displayId].push_back( + {previousDisplay, DisplayTopologyPosition::BOTTOM, -100}); + } else { + displaytopology.graph[previousDisplay].push_back( + {viewport.displayId, DisplayTopologyPosition::RIGHT, 100}); + displaytopology.graph[viewport.displayId].push_back( + {previousDisplay, DisplayTopologyPosition::LEFT, -100}); + } + previousDisplay = viewport.displayId; + } + + mInputManager->getChoreographer().setDisplayTopology(displaytopology); } void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) { diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index e3bd69c30de7..569383e71a06 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -72,6 +72,7 @@ int register_com_android_server_display_DisplayControl(JNIEnv* env); int register_com_android_server_SystemClockTime(JNIEnv* env); int register_android_server_display_smallAreaDetectionController(JNIEnv* env); int register_com_android_server_accessibility_BrailleDisplayConnection(JNIEnv* env); +int register_android_server_am_PhantomProcessList(JNIEnv* env); // Note: Consider adding new JNI entrypoints for optional services to // LazyJniRegistrar instead, and relying on lazy registration. @@ -139,5 +140,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_com_android_server_SystemClockTime(env); register_android_server_display_smallAreaDetectionController(env); register_com_android_server_accessibility_BrailleDisplayConnection(env); + register_android_server_am_PhantomProcessList(env); return JNI_VERSION_1_4; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 29e0487dad0a..60130d1f97be 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -109,6 +109,7 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.ApplicationSharedMemory; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; +import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.pm.RoSystemFeatures; import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.ProtoLog; @@ -1002,6 +1003,17 @@ public final class SystemServer implements Dumpable { } }); + // Register callback to report native memory metrics post GC cleanup + // for system_server + if (android.app.Flags.reportPostgcMemoryMetrics() && + com.android.libcore.readonly.Flags.postCleanupApis()) { + VMRuntime.addPostCleanupCallback(new Runnable() { + @Override public void run() { + MetricsLoggerWrapper.logPostGcMemorySnapshot(); + } + }); + } + // Loop forever. Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); @@ -2155,13 +2167,14 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startServiceFromJar( WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); t.traceEnd(); - // Start USD service - if (android.net.wifi.flags.Flags.usd()) { - t.traceBegin("StartUsd"); - mSystemServiceManager.startServiceFromJar( - WIFI_USD_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); - t.traceEnd(); - } + } + + if (android.net.wifi.flags.Flags.usd() && context.getResources().getBoolean( + com.android.internal.R.bool.config_deviceSupportsWifiUsd)) { + t.traceBegin("StartWifiUsd"); + mSystemServiceManager.startServiceFromJar(WIFI_USD_SERVICE_CLASS, + WIFI_APEX_SERVICE_JAR_PATH); + t.traceEnd(); } if (context.getPackageManager().hasSystemFeature( diff --git a/services/manifest_services_android.frameworks.devicestate.xml b/services/manifest_services_android.frameworks.devicestate.xml new file mode 100644 index 000000000000..dc189ec0b40a --- /dev/null +++ b/services/manifest_services_android.frameworks.devicestate.xml @@ -0,0 +1,7 @@ +<manifest version="1.0" type="framework"> + <hal format="aidl"> + <name>android.frameworks.devicestate</name> + <version>1</version> + <fqname>IDeviceStateService/default</fqname> + </hal> +</manifest> diff --git a/services/manifest_services.xml b/services/manifest_services_android.frameworks.location.xml index 945720544991..114fe324f016 100644 --- a/services/manifest_services.xml +++ b/services/manifest_services_android.frameworks.location.xml @@ -4,9 +4,4 @@ <version>2</version> <fqname>IAltitudeService/default</fqname> </hal> - <hal format="aidl"> - <name>android.frameworks.devicestate</name> - <version>1</version> - <fqname>IDeviceStateService/default</fqname> - </hal> </manifest> diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 228e32e98cc7..fc585c9e0f96 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -16,6 +16,11 @@ package com.android.server.profcollect; +import static android.content.Intent.ACTION_BATTERY_LOW; +import static android.content.Intent.ACTION_BATTERY_OKAY; +import static android.content.Intent.ACTION_SCREEN_OFF; +import static android.content.Intent.ACTION_SCREEN_ON; + import android.Manifest; import android.annotation.RequiresPermission; import android.app.job.JobInfo; @@ -32,6 +37,7 @@ import android.hardware.usb.UsbManager; import android.os.Handler; import android.os.IBinder.DeathRecipient; import android.os.Looper; +import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -70,10 +76,12 @@ public final class ProfcollectForwardingService extends SystemService { private int mUsageSetting; private boolean mUploadEnabled; - private static boolean sVerityEnforced; - private boolean mAdbActive; + static boolean sVerityEnforced; + static boolean sIsInteractive; + static boolean sAdbActive; + static boolean sIsBatteryLow; - private IProfCollectd mIProfcollect; + private static IProfCollectd sIProfcollect; private static ProfcollectForwardingService sSelfService; private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper()); @@ -86,17 +94,28 @@ public final class ProfcollectForwardingService extends SystemService { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) { + if (ACTION_BATTERY_LOW.equals(intent.getAction())) { + sIsBatteryLow = true; + } else if (ACTION_BATTERY_OKAY.equals(intent.getAction())) { + sIsBatteryLow = false; + } else if (ACTION_SCREEN_ON.equals(intent.getAction())) { + Log.d(LOG_TAG, "Received broadcast that the device became interactive, was " + + sIsInteractive); + sIsInteractive = true; + } else if (ACTION_SCREEN_OFF.equals(intent.getAction())) { + Log.d(LOG_TAG, "Received broadcast that the device became noninteractive, was " + + sIsInteractive); + sIsInteractive = false; + } else if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) { Log.d(LOG_TAG, "Received broadcast to pack and upload reports"); createAndUploadReport(sSelfService); - } - if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) { + } else if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) { boolean isADB = intent.getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false); if (isADB) { boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); Log.d(LOG_TAG, "Received broadcast that ADB became " + connected - + ", was " + mAdbActive); - mAdbActive = connected; + + ", was " + sAdbActive); + sAdbActive = connected; } } } @@ -129,6 +148,10 @@ public final class ProfcollectForwardingService extends SystemService { context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled); final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_BATTERY_LOW); + filter.addAction(ACTION_BATTERY_OKAY); + filter.addAction(ACTION_SCREEN_ON); + filter.addAction(ACTION_SCREEN_OFF); filter.addAction(INTENT_UPLOAD_PROFILES); filter.addAction(UsbManager.ACTION_USB_STATE); context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); @@ -153,14 +176,24 @@ public final class ProfcollectForwardingService extends SystemService { if (phase == PHASE_SYSTEM_SERVICES_READY) { UsbManager usbManager = getContext().getSystemService(UsbManager.class); if (usbManager == null) { - mAdbActive = false; - return; + sAdbActive = false; + Log.d(LOG_TAG, "USBManager is not ready"); + } else { + sAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1); + Log.d(LOG_TAG, "ADB is " + sAdbActive + " on system startup"); + } + + PowerManager powerManager = getContext().getSystemService(PowerManager.class); + if (powerManager == null) { + sIsInteractive = true; + Log.d(LOG_TAG, "PowerManager is not ready"); + } else { + sIsInteractive = powerManager.isInteractive(); + Log.d(LOG_TAG, "Device is interactive " + sIsInteractive + " on system startup"); } - mAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1); - Log.d(LOG_TAG, "ADB is " + mAdbActive + " on system startup"); } if (phase == PHASE_BOOT_COMPLETED) { - if (mIProfcollect == null) { + if (sIProfcollect == null) { return; } BackgroundThread.get().getThreadHandler().post(() -> { @@ -172,22 +205,22 @@ public final class ProfcollectForwardingService extends SystemService { } private void registerProviderStatusCallback() { - if (mIProfcollect == null) { + if (sIProfcollect == null) { return; } try { - mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback); + sIProfcollect.registerProviderStatusCallback(mProviderStatusCallback); } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to register provider status callback: " + e.getMessage()); } } private boolean serviceHasSupportedTraceProvider() { - if (mIProfcollect == null) { + if (sIProfcollect == null) { return false; } try { - return !mIProfcollect.get_supported_provider().isEmpty(); + return !sIProfcollect.get_supported_provider().isEmpty(); } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to get supported provider: " + e.getMessage()); return false; @@ -209,7 +242,7 @@ public final class ProfcollectForwardingService extends SystemService { IProfCollectd.Stub.asInterface( ServiceManager.getServiceOrThrow("profcollectd")); profcollectd.asBinder().linkToDeath(new ProfcollectdDeathRecipient(), /*flags*/0); - mIProfcollect = profcollectd; + sIProfcollect = profcollectd; return true; } catch (ServiceManager.ServiceNotFoundException | RemoteException e) { Log.w(LOG_TAG, "Failed to connect profcollectd binder service."); @@ -233,7 +266,8 @@ public final class ProfcollectForwardingService extends SystemService { break; case MESSAGE_REGISTER_SCHEDULERS: registerObservers(); - ProfcollectBGJobService.schedule(getContext()); + PeriodicTraceJobService.schedule(getContext()); + ReportProcessJobService.schedule(getContext()); break; default: throw new AssertionError("Unknown message: " + message); @@ -246,27 +280,66 @@ public final class ProfcollectForwardingService extends SystemService { public void binderDied() { Log.w(LOG_TAG, "profcollectd has died"); - mIProfcollect = null; + sIProfcollect = null; tryConnectNativeService(); } } /** - * Background trace process service. + * Background report process and upload service. */ - public static class ProfcollectBGJobService extends JobService { - // Unique ID in system service - private static final int JOB_IDLE_PROCESS = 260817; + public static class PeriodicTraceJobService extends JobService { + // Unique ID in system server + private static final int PERIODIC_TRACE_JOB_ID = 241207; private static final ComponentName JOB_SERVICE_NAME = new ComponentName( "android", - ProfcollectBGJobService.class.getName()); + PeriodicTraceJobService.class.getName()); /** * Attach the service to the system job scheduler. */ public static void schedule(Context context) { + final int interval = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, + "collection_interval", 600); JobScheduler js = context.getSystemService(JobScheduler.class); - js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME) + js.schedule(new JobInfo.Builder(PERIODIC_TRACE_JOB_ID, JOB_SERVICE_NAME) + .setPeriodic(TimeUnit.SECONDS.toMillis(interval)) + // PRIORITY_DEFAULT is the highest priority we can request for a periodic job. + .setPriority(JobInfo.PRIORITY_DEFAULT) + .build()); + } + + @Override + public boolean onStartJob(JobParameters params) { + if (sIProfcollect != null) { + Utils.traceSystem(sIProfcollect, "periodic"); + } + jobFinished(params, false); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } + } + + /** + * Background report process and upload service. + */ + public static class ReportProcessJobService extends JobService { + // Unique ID in system server + private static final int REPORT_PROCESS_JOB_ID = 260817; + private static final ComponentName JOB_SERVICE_NAME = new ComponentName( + "android", + ReportProcessJobService.class.getName()); + + /** + * Attach the service to the system job scheduler. + */ + public static void schedule(Context context) { + JobScheduler js = context.getSystemService(JobScheduler.class); + js.schedule(new JobInfo.Builder(REPORT_PROCESS_JOB_ID, JOB_SERVICE_NAME) .setRequiresDeviceIdle(true) .setRequiresCharging(true) .setPeriodic(BG_PROCESS_INTERVAL) @@ -283,7 +356,6 @@ public final class ProfcollectForwardingService extends SystemService { @Override public boolean onStopJob(JobParameters params) { - // TODO: Handle this? return false; } } @@ -311,14 +383,8 @@ public final class ProfcollectForwardingService extends SystemService { private class AppLaunchObserver extends ActivityMetricsLaunchObserver { @Override public void onIntentStarted(Intent intent, long timestampNanos) { - if (mIProfcollect == null) { - return; - } - if (mAdbActive) { - return; - } if (Utils.withFrequency("applaunch_trace_freq", 5)) { - Utils.traceSystem(mIProfcollect, "applaunch"); + Utils.traceSystem(sIProfcollect, "applaunch"); } } } @@ -336,15 +402,9 @@ public final class ProfcollectForwardingService extends SystemService { } private void traceOnDex2oatStart() { - if (mIProfcollect == null) { - return; - } - if (mAdbActive) { - return; - } if (Utils.withFrequency("dex2oat_trace_freq", 25)) { // Dex2oat could take a while before it starts. Add a short delay before start tracing. - Utils.traceSystem(mIProfcollect, "dex2oat", /* delayMs */ 1000); + Utils.traceSystem(sIProfcollect, "dex2oat", /* delayMs */ 1000); } } @@ -367,12 +427,12 @@ public final class ProfcollectForwardingService extends SystemService { private static void createAndUploadReport(ProfcollectForwardingService pfs) { BackgroundThread.get().getThreadHandler().post(() -> { - if (pfs.mIProfcollect == null) { + if (pfs.sIProfcollect == null) { return; } String reportName; try { - reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip"; + reportName = pfs.sIProfcollect.report(pfs.mUsageSetting) + ".zip"; } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to create report: " + e.getMessage()); return; @@ -411,7 +471,7 @@ public final class ProfcollectForwardingService extends SystemService { return; } if (Utils.withFrequency("camera_trace_freq", 10)) { - Utils.traceProcess(mIProfcollect, + Utils.traceProcess(sIProfcollect, "camera", "android.hardware.camera.provider", /* durationMs */ 5000); diff --git a/services/profcollect/src/com/android/server/profcollect/Utils.java b/services/profcollect/src/com/android/server/profcollect/Utils.java index a8016a0b641e..c109f5cf05b6 100644 --- a/services/profcollect/src/com/android/server/profcollect/Utils.java +++ b/services/profcollect/src/com/android/server/profcollect/Utils.java @@ -28,28 +28,29 @@ import com.android.internal.os.BackgroundThread; import java.time.Instant; import java.util.concurrent.ThreadLocalRandom; -public final class Utils { +final class Utils { private static Instant lastTraceTime = Instant.EPOCH; private static final int TRACE_COOLDOWN_SECONDS = 30; - public static boolean withFrequency(String configName, int defaultFrequency) { + static boolean withFrequency(String configName, int defaultFrequency) { int threshold = DeviceConfig.getInt( DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, configName, defaultFrequency); int randomNum = ThreadLocalRandom.current().nextInt(100); return randomNum < threshold; } - public static boolean traceSystem(IProfCollectd mIProfcollect, String eventName) { - if (mIProfcollect == null) { - return false; - } - if (isInCooldownOrReset()) { + /** + * Request a system-wide trace. + * Will be ignored if the device does not meet trace criteria or is being rate limited. + */ + static boolean traceSystem(IProfCollectd iprofcollectd, String eventName) { + if (!checkPrerequisites(iprofcollectd)) { return false; } BackgroundThread.get().getThreadHandler().post(() -> { try { - mIProfcollect.trace_system(eventName); + iprofcollectd.trace_system(eventName); } catch (RemoteException | ServiceSpecificException e) { Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage()); } @@ -57,16 +58,17 @@ public final class Utils { return true; } - public static boolean traceSystem(IProfCollectd mIProfcollect, String eventName, int delayMs) { - if (mIProfcollect == null) { - return false; - } - if (isInCooldownOrReset()) { + /** + * Request a system-wide trace after a delay. + * Will be ignored if the device does not meet trace criteria or is being rate limited. + */ + static boolean traceSystem(IProfCollectd iprofcollectd, String eventName, int delayMs) { + if (!checkPrerequisites(iprofcollectd)) { return false; } BackgroundThread.get().getThreadHandler().postDelayed(() -> { try { - mIProfcollect.trace_system(eventName); + iprofcollectd.trace_system(eventName); } catch (RemoteException | ServiceSpecificException e) { Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage()); } @@ -74,17 +76,18 @@ public final class Utils { return true; } - public static boolean traceProcess(IProfCollectd mIProfcollect, + /** + * Request a single-process trace. + * Will be ignored if the device does not meet trace criteria or is being rate limited. + */ + static boolean traceProcess(IProfCollectd iprofcollectd, String eventName, String processName, int durationMs) { - if (mIProfcollect == null) { - return false; - } - if (isInCooldownOrReset()) { + if (!checkPrerequisites(iprofcollectd)) { return false; } BackgroundThread.get().getThreadHandler().post(() -> { try { - mIProfcollect.trace_process(eventName, + iprofcollectd.trace_process(eventName, processName, durationMs); } catch (RemoteException | ServiceSpecificException e) { @@ -105,4 +108,17 @@ public final class Utils { } return true; } + + private static boolean checkPrerequisites(IProfCollectd iprofcollectd) { + if (iprofcollectd == null) { + return false; + } + if (isInCooldownOrReset()) { + return false; + } + return ProfcollectForwardingService.sVerityEnforced + && !ProfcollectForwardingService.sAdbActive + && ProfcollectForwardingService.sIsInteractive + && !ProfcollectForwardingService.sIsBatteryLow; + } } diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java index 5db6a8f12e52..9117cc8e5ab8 100644 --- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java +++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java @@ -918,6 +918,30 @@ public class VpnTest extends VpnTestBase { } @Test + public void testOnUserAddedAndRemoved_nullUserInfo() throws Exception { + final Vpn vpn = createVpn(PRIMARY_USER.id); + final Set<Range<Integer>> initialRange = rangeSet(PRIMARY_USER_RANGE); + // Note since mVpnProfile is a Ikev2VpnProfile, this starts an IkeV2VpnRunner. + startLegacyVpn(vpn, mVpnProfile); + // Set an initial Uid range and mock the network agent + vpn.mNetworkCapabilities.setUids(initialRange); + vpn.mNetworkAgent = mMockNetworkAgent; + + // Add the restricted user and then remove it immediately. So the getUserInfo() will return + // null for the given restricted user id. + setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A); + doReturn(null).when(mUserManager).getUserInfo(RESTRICTED_PROFILE_A.id); + vpn.onUserAdded(RESTRICTED_PROFILE_A.id); + // Expect no range change to the NetworkCapabilities. + assertEquals(initialRange, vpn.mNetworkCapabilities.getUids()); + + // Remove the restricted user + vpn.onUserRemoved(RESTRICTED_PROFILE_A.id); + // Expect no range change to the NetworkCapabilities. + assertEquals(initialRange, vpn.mNetworkCapabilities.getUids()); + } + + @Test public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller() throws Exception { mTestDeps.mIgnoreCallingUidChecks = false; diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index f96294ed4ca8..b7b4f0424165 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -214,7 +214,8 @@ public class DisplayManagerServiceTest { private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests"; private static final long STANDARD_DISPLAY_EVENTS = DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED - | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS = STANDARD_DISPLAY_EVENTS @@ -222,7 +223,7 @@ public class DisplayManagerServiceTest { private static final String EVENT_DISPLAY_ADDED = "EVENT_DISPLAY_ADDED"; private static final String EVENT_DISPLAY_REMOVED = "EVENT_DISPLAY_REMOVED"; - private static final String EVENT_DISPLAY_CHANGED = "EVENT_DISPLAY_CHANGED"; + private static final String EVENT_DISPLAY_BASIC_CHANGED = "EVENT_DISPLAY_BASIC_CHANGED"; private static final String EVENT_DISPLAY_BRIGHTNESS_CHANGED = "EVENT_DISPLAY_BRIGHTNESS_CHANGED"; private static final String EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = @@ -889,7 +890,6 @@ public class DisplayManagerServiceTest { FakeDisplayManagerCallback callback = registerDisplayListenerCallback( displayManager, bs, displayDevice); - // Simulate DisplayDevice change DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo(); displayDeviceInfo2.copyFrom(displayDeviceInfo); @@ -900,7 +900,8 @@ public class DisplayManagerServiceTest { Handler handler = displayManager.getDisplayHandler(); waitForIdleHandler(handler); - assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED, + EVENT_DISPLAY_REFRESH_RATE_CHANGED); } /** @@ -2145,7 +2146,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(myUid, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED); callback.clear(); updateFrameRateOverride(displayManager, displayDevice, @@ -2154,7 +2155,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(1234, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED); updateFrameRateOverride(displayManager, displayDevice, new DisplayEventReceiver.FrameRateOverride[]{ @@ -2163,7 +2164,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(5678, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED); callback.clear(); updateFrameRateOverride(displayManager, displayDevice, @@ -2172,7 +2173,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(5678, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED); callback.clear(); updateFrameRateOverride(displayManager, displayDevice, @@ -2180,7 +2181,7 @@ public class DisplayManagerServiceTest { new DisplayEventReceiver.FrameRateOverride(5678, 30f), }); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED); } /** @@ -2303,16 +2304,16 @@ public class DisplayManagerServiceTest { updateRenderFrameRate(displayManager, displayDevice, 30f); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED); callback.clear(); updateRenderFrameRate(displayManager, displayDevice, 30f); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED); updateRenderFrameRate(displayManager, displayDevice, 20f); waitForIdleHandler(displayManager.getDisplayHandler()); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED); callback.clear(); } @@ -3888,7 +3889,7 @@ public class DisplayManagerServiceTest { observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY)); waitForIdleHandler(handler); - assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED); } @Test @@ -3919,7 +3920,7 @@ public class DisplayManagerServiceTest { observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY)); waitForIdleHandler(handler); - assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED); + assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED); } private void initDisplayPowerController(DisplayManagerInternal localService) { @@ -4389,8 +4390,8 @@ public class DisplayManagerServiceTest { return EVENT_DISPLAY_ADDED; case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED: return EVENT_DISPLAY_REMOVED; - case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED: - return EVENT_DISPLAY_CHANGED; + case DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED: + return EVENT_DISPLAY_BASIC_CHANGED; case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED: return EVENT_DISPLAY_BRIGHTNESS_CHANGED; case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED: diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index ad30f22fe060..0dbb6ba58b3c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -36,9 +36,9 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DE import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED; -import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED; +import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED; import static com.android.server.display.layout.Layout.Display.POSITION_REAR; import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN; import static com.android.server.utils.FoldSettingProvider.SETTING_VALUE_SELECTIVE_STAY_AWAKE; @@ -1170,18 +1170,20 @@ public class LogicalDisplayMapperTest { @Test public void updateAndGetMaskForDisplayPropertyChanges_getsPropertyChangedFlags() { - // Change the display state + // Change the refresh rate override DisplayInfo newDisplayInfo = new DisplayInfo(); - newDisplayInfo.state = STATE_OFF; - assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED, + newDisplayInfo.refreshRateOverride = 30; + assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED, mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo)); - // Change the refresh rate override + // Change the display state + when(mFlagsMock.isDisplayListenerPerformanceImprovementsEnabled()).thenReturn(true); newDisplayInfo = new DisplayInfo(); - newDisplayInfo.refreshRateOverride = 30; - assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED, + newDisplayInfo.state = STATE_OFF; + assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED, mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo)); + // Change multiple properties newDisplayInfo = new DisplayInfo(); newDisplayInfo.refreshRateOverride = 30; diff --git a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java index fc4cc25243c1..f0a77bba4b00 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java @@ -52,7 +52,9 @@ public final class DisplayStateControllerTest { @Before public void before() { MockitoAnnotations.initMocks(this); - mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController); + final boolean shouldSkipScreenOffTransition = false; + mDisplayStateController = new DisplayStateController( + mDisplayPowerProximityStateController, /* shouldSkipScreenOffTransition= */ false); } @Test @@ -236,6 +238,38 @@ public final class DisplayStateControllerTest { assertTrue(Display.STATE_REASON_OFFLOAD == stateAndReason.second); } + @Test + public void shouldPerformScreenOffTransition_whenRequestedOffAndNotConfiguredToSkip_true() { + mDisplayStateController = new DisplayStateController( + mDisplayPowerProximityStateController, /* shouldSkipScreenOffTransition= */ false); + when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn( + false); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF; + displayPowerRequest.policyReason = Display.STATE_REASON_KEY; + mDisplayStateController.updateDisplayState( + displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION); + assertEquals(true, mDisplayStateController.shouldPerformScreenOffTransition()); + } + + @Test + public void shouldPerformScreenOffTransition_whenRequestedOffAndConfiguredToSkip_false() { + mDisplayStateController = new DisplayStateController( + mDisplayPowerProximityStateController, /* shouldSkipScreenOffTransition= */ true); + when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn( + false); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF; + displayPowerRequest.policyReason = Display.STATE_REASON_KEY; + mDisplayStateController.updateDisplayState( + displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION); + assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition()); + } + private void validDisplayState(int policy, int displayState, boolean isEnabled, boolean isInTransition) { DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp index 94d4b9522d60..03bd73c52083 100644 --- a/services/tests/mockingservicestests/jni/Android.bp +++ b/services/tests/mockingservicestests/jni/Android.bp @@ -24,6 +24,7 @@ cc_library_shared { ":lib_freezer_native", ":lib_oomConnection_native", ":lib_lazilyRegisteredServices_native", + ":lib_phantomProcessList_native", "onload.cpp", ], diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp index 9b4c8178b092..30fa7de94af1 100644 --- a/services/tests/mockingservicestests/jni/onload.cpp +++ b/services/tests/mockingservicestests/jni/onload.cpp @@ -28,6 +28,7 @@ int register_android_server_am_CachedAppOptimizer(JNIEnv* env); int register_android_server_am_Freezer(JNIEnv* env); int register_android_server_am_OomConnection(JNIEnv* env); int register_android_server_utils_LazyJniRegistrar(JNIEnv* env); +int register_android_server_am_PhantomProcessList(JNIEnv* env); }; using namespace android; @@ -46,5 +47,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_am_Freezer(env); register_android_server_am_OomConnection(env); register_android_server_utils_LazyJniRegistrar(env); + register_android_server_am_PhantomProcessList(env); return JNI_VERSION_1_4; } diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index f40d8038da3b..db04d39e772c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -25,6 +25,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; import static com.android.server.RescueParty.DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN; import static com.android.server.RescueParty.LEVEL_FACTORY_RESET; @@ -357,11 +359,13 @@ public class RescuePartyTest { SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation( - sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false); + sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), + MITIGATION_RESULT_SKIPPED); SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); - assertTrue(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation( - sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1)); + assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation( + sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), + MITIGATION_RESULT_SUCCESS); } @Test @@ -370,7 +374,8 @@ public class RescuePartyTest { SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).onExecuteHealthCheckMitigation( - sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false); + sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), + MITIGATION_RESULT_SKIPPED); // Restore the property value initialized in SetUp() SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java index 3b6c86e3c94f..0c92c10e2523 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java @@ -29,15 +29,20 @@ import android.app.job.IJobCallback; import android.app.job.JobParameters; import android.net.Uri; import android.os.Parcel; -import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; +import libcore.junit.util.compat.CoreCompatChangeRule; +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @@ -47,7 +52,10 @@ public class JobParametersTest { private static final int TEST_JOB_ID_1 = 123; private static final String TEST_NAMESPACE = "TEST_NAMESPACE"; private static final String TEST_DEBUG_STOP_REASON = "TEST_DEBUG_STOP_REASON"; - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public TestRule compatChangeRule = new CoreCompatChangeRule(); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @@ -129,9 +137,10 @@ public class JobParametersTest { } /** Test to verify that the JobParameters Cleaner is disabled */ - @RequiresFlagsEnabled(FLAG_HANDLE_ABANDONED_JOBS) @Test - public void testCleanerWithLeakedJobCleanerDisabled_flagHandleAbandonedJobs() { + @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) + @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) + public void testCleanerWithLeakedNoJobCleaner_EnableFlagDisableCompatHandleAbandonedJobs() { // Inject real JobCallbackCleanup JobParameters jobParameters = JobParameters.CREATOR.createFromParcel(mMockParcel); @@ -150,4 +159,31 @@ public class JobParametersTest { assertThat(jobParameters.getCleanable()).isNull(); assertThat(jobParameters.getJobCleanupCallback()).isNull(); } + + /** + * Test to verify that the JobParameters Cleaner is not enabled + * when the compat change is enabled and the flag is enabled + */ + @Test + @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) + @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) + public void testCleanerWithLeakedNoJobCleaner_EnableFlagEnableCompatHandleAbandonedJobs() { + // Inject real JobCallbackCleanup + JobParameters jobParameters = JobParameters.CREATOR.createFromParcel(mMockParcel); + + // Enable the cleaner + jobParameters.enableCleaner(); + + // Verify the cleaner is not enabled + assertThat(jobParameters.getCleanable()).isNull(); + assertThat(jobParameters.getJobCleanupCallback()).isNull(); + + // Disable the cleaner + jobParameters.disableCleaner(); + + // Verify the cleaner is disabled + assertThat(jobParameters.getCleanable()).isNull(); + assertThat(jobParameters.getJobCleanupCallback()).isNull(); + } + } 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 1e7a4f6cf51b..8c09f26bb7fa 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -53,6 +53,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.AppGlobals; import android.app.IActivityManager; import android.app.UiModeManager; import android.app.job.JobInfo; @@ -60,6 +61,7 @@ import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobWorkItem; import android.app.usage.UsageStatsManagerInternal; +import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -79,7 +81,6 @@ import android.os.BatteryManagerInternal.ChargingPolicyChangeListener; import android.os.Looper; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.WorkSource; import android.os.WorkSource.WorkChain; @@ -105,10 +106,14 @@ import com.android.server.job.restrictions.ThermalStatusRestriction; import com.android.server.pm.UserManagerInternal; import com.android.server.usage.AppStandbyInternal; +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; @@ -120,6 +125,7 @@ import java.time.Duration; import java.time.ZoneOffset; public class JobSchedulerServiceTest { + private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; private static final String TAG = JobSchedulerServiceTest.class.getSimpleName(); private static final int TEST_UID = 10123; @@ -141,8 +147,13 @@ public class JobSchedulerServiceTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + private ChargingPolicyChangeListener mChargingPolicyChangeListener; + private int mSourceUid; + private class TestJobSchedulerService extends JobSchedulerService { TestJobSchedulerService(Context context) { super(context); @@ -157,7 +168,6 @@ public class JobSchedulerServiceTest { .strictness(Strictness.LENIENT) .mockStatic(LocalServices.class) .mockStatic(PermissionChecker.class) - .mockStatic(ServiceManager.class) .startMocking(); // Called in JobSchedulerService constructor. @@ -226,6 +236,7 @@ public class JobSchedulerServiceTest { verify(mBatteryManagerInternal).registerChargingPolicyChangeListener( chargingPolicyChangeListenerCaptor.capture()); mChargingPolicyChangeListener = chargingPolicyChangeListenerCaptor.getValue(); + mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0); } @After @@ -1063,6 +1074,7 @@ public class JobSchedulerServiceTest { */ @Test @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) + @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) public void testGetRescheduleJobForFailure_abandonedJob() { final long nowElapsed = sElapsedRealtimeClock.millis(); final long initialBackoffMs = MINUTE_IN_MILLIS; @@ -1074,6 +1086,9 @@ public class JobSchedulerServiceTest { assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed()); + spyOn(originalJob); + doReturn(mSourceUid).when(originalJob).getSourceUid(); + // failure = 1, systemStop = 0, abandoned = 1 JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob, JobParameters.STOP_REASON_DEVICE_STATE, @@ -1081,6 +1096,8 @@ public class JobSchedulerServiceTest { assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime()); assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); + spyOn(rescheduledJob); + doReturn(mSourceUid).when(rescheduledJob).getSourceUid(); // failure = 2, systemstop = 0, abandoned = 2 rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, JobParameters.STOP_REASON_DEVICE_STATE, @@ -1126,6 +1143,44 @@ public class JobSchedulerServiceTest { } /** + * Confirm that {@link JobSchedulerService#shouldUseAggressiveBackoff(int, int)} returns true + * when the number of abandoned jobs is greater than the threshold. + */ + @Test + @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) + @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) + public void testGetRescheduleJobForFailure_EnableFlagDisableCompatCheckAggressiveBackoff() { + assertFalse(mService.shouldUseAggressiveBackoff( + mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1, + mSourceUid)); + assertFalse(mService.shouldUseAggressiveBackoff( + mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF, + mSourceUid)); + assertTrue(mService.shouldUseAggressiveBackoff( + mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF + 1, + mSourceUid)); + } + + /** + * Confirm that {@link JobSchedulerService#shouldUseAggressiveBackoff(int, int)} returns false + * always when the compat change is enabled and the flag is enabled. + */ + @Test + @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) + @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) + public void testGetRescheduleJobForFailure_EnableFlagEnableCompatCheckAggressiveBackoff() { + assertFalse(mService.shouldUseAggressiveBackoff( + mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1, + mSourceUid)); + assertFalse(mService.shouldUseAggressiveBackoff( + mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF, + mSourceUid)); + assertFalse(mService.shouldUseAggressiveBackoff( + mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF + 1, + mSourceUid)); + } + + /** * Confirm that * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)} * returns a job that is correctly marked as demoted by the user. diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java index 8c66fd0e684a..904545bd3cc3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify; import android.app.AppGlobals; import android.app.job.JobParameters; +import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.os.Looper; import android.os.PowerManager; @@ -43,11 +44,15 @@ import com.android.internal.app.IBatteryStats; import com.android.server.job.JobServiceContext.JobCallback; import com.android.server.job.controllers.JobStatus; +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoSession; @@ -58,11 +63,14 @@ import java.time.Duration; import java.time.ZoneOffset; public class JobServiceContextTest { + private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; private static final String TAG = JobServiceContextTest.class.getSimpleName(); @ClassRule public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule(); @Rule public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule(); + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); @Mock private JobSchedulerService mMockJobSchedulerService; @Mock @@ -86,13 +94,13 @@ public class JobServiceContextTest { private MockitoSession mMockingSession; private JobServiceContext mJobServiceContext; private Object mLock; + private int mSourceUid; @Before public void setUp() throws Exception { mMockingSession = mockitoSession() .initMocks(this) - .mockStatic(AppGlobals.class) .strictness(Strictness.LENIENT) .startMocking(); JobSchedulerService.sElapsedRealtimeClock = @@ -111,6 +119,7 @@ public class JobServiceContextTest { mMockLooper); spyOn(mJobServiceContext); mJobServiceContext.setJobParamsLockedForTest(mMockJobParameters); + mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0); } @After @@ -130,11 +139,14 @@ public class JobServiceContextTest { } /** - * Test that Abandoned jobs that are timed out are stopped with the correct stop reason + * Test that with the compat change disabled and the flag enabled, abandoned + * jobs that are timed out are stopped with the correct stop reason and the + * job is marked as abandoned. */ @Test @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) - public void testJobServiceContext_TimeoutAbandonedJob() { + @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) + public void testJobServiceContext_TimeoutAbandonedJob_EnableFlagDisableCompat() { mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING; ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture()); @@ -143,6 +155,7 @@ public class JobServiceContextTest { mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED); mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus); + doReturn(mSourceUid).when(mMockJobStatus).getSourceUid(); doReturn(true).when(mMockJobStatus).isAbandoned(); mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING; @@ -158,11 +171,14 @@ public class JobServiceContextTest { } /** - * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason + * Test that with the compat change enabled and the flag enabled, abandoned + * jobs that are timed out are stopped with the correct stop reason and the + * job is not marked as abandoned. */ @Test @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) - public void testJobServiceContext_TimeoutNoAbandonedJob() { + @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) + public void testJobServiceContext_TimeoutAbandonedJob_EnableFlagEnableCompat() { mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING; ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture()); @@ -171,7 +187,8 @@ public class JobServiceContextTest { mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED); mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus); - doReturn(false).when(mMockJobStatus).isAbandoned(); + doReturn(mSourceUid).when(mMockJobStatus).getSourceUid(); + doReturn(true).when(mMockJobStatus).isAbandoned(); mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING; mJobServiceContext.handleOpTimeoutLocked(); @@ -186,12 +203,14 @@ public class JobServiceContextTest { } /** - * Test that abandoned jobs that are timed out while the flag is disabled - * are stopped with the correct stop reason + * Test that with the compat change disabled and the flag disabled, abandoned + * jobs that are timed out are stopped with the correct stop reason and the + * job is not marked as abandoned. */ @Test @DisableFlags(FLAG_HANDLE_ABANDONED_JOBS) - public void testJobServiceContext_TimeoutAbandonedJob_flagHandleAbandonedJobsDisabled() { + @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) + public void testJobServiceContext_TimeoutAbandonedJob_DisableFlagDisableCompat() { mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING; ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture()); @@ -201,6 +220,39 @@ public class JobServiceContextTest { mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus); doReturn(true).when(mMockJobStatus).isAbandoned(); + doReturn(mSourceUid).when(mMockJobStatus).getSourceUid(); + mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING; + + synchronized (mLock) { + mJobServiceContext.handleOpTimeoutLocked(); + } + + String stopMessage = captor.getValue(); + assertEquals("timeout while executing", stopMessage); + verify(mMockJobParameters) + .setStopReason( + JobParameters.STOP_REASON_TIMEOUT, + JobParameters.INTERNAL_STOP_REASON_TIMEOUT, + "client timed out"); + } + + /** + * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason + */ + @Test + @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS) + @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS}) + public void testJobServiceContext_TimeoutNoAbandonedJob() { + mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING; + ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); + synchronized (mLock) { + doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture()); + } + advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes + mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED); + + mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus); + doReturn(false).when(mMockJobStatus).isAbandoned(); mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING; mJobServiceContext.handleOpTimeoutLocked(); diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java index 4b2e850d08e7..35f421e582d8 100644 --- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -70,9 +70,12 @@ import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.SessionCreationConfig; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Log; import com.android.server.FgThread; @@ -89,6 +92,8 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -160,6 +165,8 @@ public class HintManagerServiceTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private HintManagerService mService; private ChannelConfig mConfig; @@ -1322,6 +1329,7 @@ public class HintManagerServiceTest { @Test + @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK}) public void testCpuHeadroomCache() throws Exception { CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal(); CpuHeadroomParams halParams1 = new CpuHeadroomParams(); @@ -1335,11 +1343,14 @@ public class HintManagerServiceTest { halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN; halParams2.tids = new int[]{}; + CountDownLatch latch = new CountDownLatch(2); + int[] tids = createThreads(2, latch); CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal(); + params3.tids = tids; params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; CpuHeadroomParams halParams3 = new CpuHeadroomParams(); + halParams3.tids = tids; halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE; - halParams3.tids = new int[]{Process.myPid()}; // this params should not be cached as the window is not default CpuHeadroomParamsInternal params4 = new CpuHeadroomParamsInternal(); @@ -1411,6 +1422,65 @@ public class HintManagerServiceTest { verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2)); verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3)); verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4)); + latch.countDown(); + } + + @Test + @EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK}) + public void testGetCpuHeadroomDifferentAffinity_flagOn() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + int[] tids = createThreads(2, latch); + CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); + params.tids = tids; + CpuHeadroomParams halParams = new CpuHeadroomParams(); + halParams.tids = tids; + float headroom = 0.1f; + CpuHeadroomResult halRet = CpuHeadroomResult.globalHeadroom(headroom); + String ret1 = runAndWaitForCommand("taskset -p 1 " + tids[0]); + String ret2 = runAndWaitForCommand("taskset -p 3 " + tids[1]); + + HintManagerService service = createService(); + clearInvocations(mIPowerMock); + when(mIPowerMock.getCpuHeadroom(eq(halParams))).thenReturn(halRet); + assertThrows("taskset cmd return: " + ret1 + "\n" + ret2, IllegalStateException.class, + () -> service.getBinderServiceInstance().getCpuHeadroom(params)); + verify(mIPowerMock, times(0)).getCpuHeadroom(any()); + } + + @Test + @DisableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK}) + public void testGetCpuHeadroomDifferentAffinity_flagOff() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + int[] tids = createThreads(2, latch); + CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); + params.tids = tids; + CpuHeadroomParams halParams = new CpuHeadroomParams(); + halParams.tids = tids; + float headroom = 0.1f; + CpuHeadroomResult halRet = CpuHeadroomResult.globalHeadroom(headroom); + String ret1 = runAndWaitForCommand("taskset -p 1 " + tids[0]); + String ret2 = runAndWaitForCommand("taskset -p 3 " + tids[1]); + + HintManagerService service = createService(); + clearInvocations(mIPowerMock); + when(mIPowerMock.getCpuHeadroom(eq(halParams))).thenReturn(halRet); + assertEquals("taskset cmd return: " + ret1 + "\n" + ret2, halRet, + service.getBinderServiceInstance().getCpuHeadroom(params)); + verify(mIPowerMock, times(1)).getCpuHeadroom(any()); + } + + private String runAndWaitForCommand(String command) throws Exception { + java.lang.Process process = Runtime.getRuntime().exec(command); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + StringBuilder res = new StringBuilder(); + while ((line = reader.readLine()) != null) { + res.append(line); + } + process.waitFor(); + // somehow the exit code can be 1 for the taskset command though it exits successfully, + // thus we just return the output + return res.toString(); } @Test diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index b67ec8b2c828..bc81feb3f7c7 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -68,6 +68,7 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class BatteryStatsHistoryTest { private static final String TAG = "BatteryStatsHistoryTest"; + private static final int MAX_HISTORY_BUFFER_SIZE = 1024; private final Parcel mHistoryBuffer = Parcel.obtain(); private File mSystemDir; private File mHistoryDir; @@ -98,8 +99,9 @@ public class BatteryStatsHistoryTest { mClock.realtime = 123; - mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, - mStepDetailsCalculator, mClock, mMonotonicClock, mTracer, mEventLogger); + mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32768, + MAX_HISTORY_BUFFER_SIZE, mStepDetailsCalculator, mClock, mMonotonicClock, mTracer, + mEventLogger); when(mStepDetailsCalculator.getHistoryStepDetails()) .thenReturn(new BatteryStats.HistoryStepDetails()); @@ -196,12 +198,15 @@ public class BatteryStatsHistoryTest { } @Test - public void testStartNextFile() { + public void testStartNextFile() throws Exception { + mHistory.forceRecordAllHistory(); + mClock.realtime = 123; List<String> fileList = new ArrayList<>(); fileList.add("123.bh"); createActiveFile(mHistory); + fillActiveFile(mHistory); // create file 1 to 31. for (int i = 1; i < 32; i++) { @@ -210,6 +215,8 @@ public class BatteryStatsHistoryTest { mHistory.startNextFile(mClock.realtime); createActiveFile(mHistory); + fillActiveFile(mHistory); + verifyFileNames(mHistory, fileList); verifyActiveFile(mHistory, mClock.realtime + ".bh"); } @@ -225,6 +232,8 @@ public class BatteryStatsHistoryTest { verifyFileNames(mHistory, fileList); verifyActiveFile(mHistory, "32000.bh"); + fillActiveFile(mHistory); + // create file 33 mClock.realtime = 1000 * 33; mHistory.startNextFile(mClock.realtime); @@ -401,6 +410,14 @@ public class BatteryStatsHistoryTest { } } + private void fillActiveFile(BatteryStatsHistory history) { + // Create roughly 1K of history + int initialSize = history.getHistoryUsedSize(); + while (history.getHistoryUsedSize() < initialSize + 1000) { + history.recordCurrentTimeChange(mClock.realtime, mClock.uptime, 0xFFFFFFFFL); + } + } + @Test public void recordPowerStats() { PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, null, 0, 2, diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 9a38209a7d17..4b6fcc39dcef 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -92,7 +92,8 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { powerStatsUidResolver, mock(FrameworkStatsLogger.class), mock(BatteryStatsHistory.TraceDelegate.class), mock(BatteryStatsHistory.EventLogger.class)); - setMaxHistoryBuffer(128 * 1024); + mConstants.MAX_HISTORY_BUFFER = 128 * 1024; + mConstants.onChange(); setExternalStatsSyncLocked(mExternalStatsSync); informThatAllExternalStatsAreFlushed(); @@ -257,20 +258,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { } @GuardedBy("this") - public MockBatteryStatsImpl setMaxHistoryFiles(int maxHistoryFiles) { - mConstants.MAX_HISTORY_FILES = maxHistoryFiles; - mConstants.onChange(); - return this; - } - - @GuardedBy("this") - public MockBatteryStatsImpl setMaxHistoryBuffer(int maxHistoryBuffer) { - mConstants.MAX_HISTORY_BUFFER = maxHistoryBuffer; - mConstants.onChange(); - return this; - } - - @GuardedBy("this") public MockBatteryStatsImpl setPerUidModemModel(int perUidModemModel) { mConstants.PER_UID_MODEM_MODEL = perUidModemModel; mConstants.onChange(); 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 a2965b3c51f1..fa78dfce0a17 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -64,6 +64,7 @@ import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.PendingIntent; import android.app.RemoteAction; import android.app.admin.DevicePolicyManager; @@ -1652,10 +1653,17 @@ public class AccessibilityManagerServiceTest { @Test @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT) - public void restoreShortcutTargets_qs_a11yQsTargetsRestored() { - // TODO: remove the assumption when we fix b/381294327 + @DisableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargetsAssumeUser0_qs_a11yQsTargetsRestored() { assumeTrue("The test is setup to run as a user 0", mTestableContext.getUserId() == UserHandle.USER_SYSTEM); + restoreShortcutTargets_qs_a11yQsTargetsRestored(); + } + + @Test + @EnableFlags({android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT, + android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE}) + public void restoreShortcutTargets_qs_a11yQsTargetsRestored() { String daltonizerTile = AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(); String colorInversionTile = @@ -1667,7 +1675,7 @@ public class AccessibilityManagerServiceTest { broadcastSettingRestored( ShortcutUtils.convertToKey(QUICK_SETTINGS), - /*newValue=*/colorInversionTile); + /*newValue=*/colorInversionTile, userState.mUserId); Set<String> expected = Set.of(daltonizerTile, colorInversionTile); assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS))) @@ -1677,11 +1685,18 @@ public class AccessibilityManagerServiceTest { } @Test - @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT) - public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() { - // TODO: remove the assumption when we fix b/381294327 + @DisableFlags({android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT, + android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE}) + public void restoreShortcutTargetsAssumeUser0_qs_a11yQsTargetsNotRestored() { assumeTrue("The test is setup to run as a user 0", mTestableContext.getUserId() == UserHandle.USER_SYSTEM); + restoreShortcutTargets_qs_a11yQsTargetsNotRestored(); + } + + @Test + @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT) + @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() { String daltonizerTile = AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(); String colorInversionTile = @@ -1694,7 +1709,7 @@ public class AccessibilityManagerServiceTest { broadcastSettingRestored( ShortcutUtils.convertToKey(QUICK_SETTINGS), - /*newValue=*/colorInversionTile); + /*newValue=*/colorInversionTile, userState.mUserId); Set<String> expected = Set.of(daltonizerTile); assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS))) @@ -1762,10 +1777,16 @@ public class AccessibilityManagerServiceTest { } @Test - public void restoreShortcutTargets_hardware_targetsMerged() { - // TODO: remove the assumption when we fix b/381294327 + @DisableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargetsAssumeUser0_hardware_targetsMerged() { assumeTrue("The test is setup to run as a user 0", mTestableContext.getUserId() == UserHandle.USER_SYSTEM); + restoreShortcutTargets_hardware_targetsMerged(); + } + + @Test + @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargets_hardware_targetsMerged() { mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY); final String servicePrevious = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); final String otherPrevious = TARGET_MAGNIFICATION; @@ -1779,7 +1800,7 @@ public class AccessibilityManagerServiceTest { broadcastSettingRestored( ShortcutUtils.convertToKey(HARDWARE), - /*newValue=*/serviceRestored); + /*newValue=*/serviceRestored, userState.mUserId); final Set<String> expected = Set.of(servicePrevious, otherPrevious, serviceRestored); assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE))) @@ -1789,10 +1810,16 @@ public class AccessibilityManagerServiceTest { } @Test - public void restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear() { - // TODO: remove the assumption when we fix b/381294327 + @DisableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargetsAssumeUser0_hardware_alreadyHadDefaultService_doesNotClear() { assumeTrue("The test is setup to run as a user 0", mTestableContext.getUserId() == UserHandle.USER_SYSTEM); + restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear(); + } + + @Test + @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear() { final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME; mTestableContext.getOrCreateTestableResources().addOverride( R.string.config_defaultAccessibilityService, serviceDefault); @@ -1807,7 +1834,7 @@ public class AccessibilityManagerServiceTest { broadcastSettingRestored( Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, - /*newValue=*/serviceDefault); + /*newValue=*/serviceDefault, userState.mUserId); final Set<String> expected = Set.of(serviceDefault); assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE))) @@ -1817,10 +1844,16 @@ public class AccessibilityManagerServiceTest { } @Test - public void restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService() { - // TODO: remove the assumption when we fix b/381294327 + @DisableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargetsAsUser0_hardware_noDefaultService_clearsDefaultService() { assumeTrue("The test is setup to run as a user 0", mTestableContext.getUserId() == UserHandle.USER_SYSTEM); + restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService(); + } + + @Test + @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService() { final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME; final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); // Restored value from the broadcast contains both default and non-default service. @@ -1833,7 +1866,7 @@ public class AccessibilityManagerServiceTest { setupShortcutTargetServices(userState); broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE), - /*newValue=*/combinedRestored); + /*newValue=*/combinedRestored, userState.mUserId); // The default service is cleared from the final restored value. final Set<String> expected = Set.of(serviceRestored); @@ -1844,10 +1877,16 @@ public class AccessibilityManagerServiceTest { } @Test - public void restoreShortcutTargets_hardware_nullSetting_clearsDefaultService() { - // TODO: remove the assumption when we fix b/381294327 + @DisableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargetsAssumeUser0_hardware_nullSetting_clearsDefaultService() { assumeTrue("The test is setup to run as a user 0", mTestableContext.getUserId() == UserHandle.USER_SYSTEM); + restoreShortcutTargets_hardware_nullSetting_clearsDefaultService(); + } + + @Test + @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE) + public void restoreShortcutTargets_hardware_nullSetting_clearsDefaultService() { final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME; final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(); // Restored value from the broadcast contains both default and non-default service. @@ -1864,7 +1903,7 @@ public class AccessibilityManagerServiceTest { putShortcutSettingForUser(HARDWARE, null, userState.mUserId); broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE), - /*newValue=*/combinedRestored); + /*newValue=*/combinedRestored, userState.mUserId); // The default service is cleared from the final restored value. final Set<String> expected = Set.of(serviceRestored); @@ -2332,12 +2371,12 @@ public class AccessibilityManagerServiceTest { setting, mA11yms.getCurrentUserIdLocked(), strings, str -> str); } - private void broadcastSettingRestored(String setting, String newValue) { + private void broadcastSettingRestored(String setting, String newValue, @UserIdInt int userId) { Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) .putExtra(Intent.EXTRA_SETTING_NAME, setting) .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, newValue); - sendBroadcastToAccessibilityManagerService(intent); + sendBroadcastToAccessibilityManagerService(intent, userId); mTestableLooper.processAllMessages(); } @@ -2421,12 +2460,24 @@ public class AccessibilityManagerServiceTest { userState.updateTileServiceMapForAccessibilityServiceLocked(); } - private void sendBroadcastToAccessibilityManagerService(Intent intent) { + private void sendBroadcastToAccessibilityManagerService(Intent intent, @UserIdInt int userId) { if (!mTestableContext.getBroadcastReceivers().containsKey(intent.getAction())) { return; } mTestableContext.getBroadcastReceivers().get(intent.getAction()).forEach( - broadcastReceiver -> broadcastReceiver.onReceive(mTestableContext, intent)); + broadcastReceiver -> { + BroadcastReceiver.PendingResult result = mock( + BroadcastReceiver.PendingResult.class); + try { + FieldSetter.setField(result, + BroadcastReceiver.PendingResult.class.getDeclaredField( + "mSendingUser"), userId); + } catch (NoSuchFieldException e) { + // do nothing + } + broadcastReceiver.setPendingResult(result); + broadcastReceiver.onReceive(mTestableContext, intent); + }); } public static class FakeInputFilter extends AccessibilityInputFilter { @@ -2449,6 +2500,7 @@ public class AccessibilityManagerServiceTest { A11yTestableContext(Context base) { super(base); mMockContext = mock(Context.class); + } @Override diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index c878799109dc..4ef602f1a64c 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -25,6 +25,7 @@ import static com.android.server.accessibility.AccessibilityManagerService.MAGNI import static com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -34,6 +35,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.floatThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; @@ -50,9 +52,11 @@ import android.animation.TimeAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.os.Looper; import android.os.RemoteException; @@ -60,6 +64,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.test.mock.MockContentResolver; import android.testing.DexmakerShareClassLoaderRule; +import android.util.DisplayMetrics; import android.view.Display; import android.view.DisplayInfo; import android.view.accessibility.IRemoteMagnificationAnimationCallback; @@ -79,6 +84,8 @@ import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.input.InputManagerInternal; import com.android.server.wm.WindowManagerInternal; +import com.google.common.truth.Expect; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -96,6 +103,9 @@ import org.mockito.stubbing.Answer; @RunWith(AndroidJUnit4.class) public class MagnificationControllerTest { + @Rule + public final Expect expect = Expect.create(); + private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; private static final int TEST_SERVICE_ID = 1; private static final Region INITIAL_SCREEN_MAGNIFICATION_REGION = @@ -119,6 +129,8 @@ public class MagnificationControllerTest { @Mock private Context mContext; @Mock + private Resources mResources; + @Mock private PackageManager mPackageManager; @Mock @@ -156,6 +168,7 @@ public class MagnificationControllerTest { @Mock private DisplayManagerInternal mDisplayManagerInternal; + private Display mDisplay; @Mock private Scroller mMockScroller; @@ -210,6 +223,12 @@ public class MagnificationControllerTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal); + DisplayManager displayManager = new DisplayManager(mContext); + mDisplay = displayManager.getDisplay(TEST_DISPLAY); + when(mContext.getSystemServiceName(DisplayManager.class)).thenReturn( + Context.DISPLAY_SERVICE); + when(mContext.getSystemService(DisplayManager.class)).thenReturn(displayManager); + mScreenMagnificationController = spy( new FullScreenMagnificationController( @@ -686,16 +705,19 @@ public class MagnificationControllerTest { float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY); while (currentScale < SCALE_MAX_VALUE) { - assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, - MagnificationController.ZOOM_DIRECTION_IN)).isTrue(); + mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_IN); final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY); assertThat(nextScale).isGreaterThan(currentScale); currentScale = nextScale; } assertThat(currentScale).isEqualTo(SCALE_MAX_VALUE); - assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, - MagnificationController.ZOOM_DIRECTION_IN)).isFalse(); + // Trying to scale further does not change the scale. + mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_IN); + final float finalScale = mScreenMagnificationController.getScale(TEST_DISPLAY); + assertThat(finalScale).isEqualTo(currentScale); } @Test @@ -706,16 +728,19 @@ public class MagnificationControllerTest { float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY); while (currentScale > SCALE_MIN_VALUE) { - assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, - MagnificationController.ZOOM_DIRECTION_OUT)).isTrue(); + mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_OUT); final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY); assertThat(nextScale).isLessThan(currentScale); currentScale = nextScale; } assertThat(currentScale).isEqualTo(SCALE_MIN_VALUE); - assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, - MagnificationController.ZOOM_DIRECTION_OUT)).isFalse(); + // Trying to scale further does not change the scale. + mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY, + MagnificationController.ZOOM_DIRECTION_OUT); + final float finalScale = mScreenMagnificationController.getScale(TEST_DISPLAY); + assertThat(finalScale).isEqualTo(currentScale); } @Test @@ -740,6 +765,121 @@ public class MagnificationControllerTest { } @Test + public void panMagnificationByStep_fullscreenMode_stepSizeAtScale2() throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + // At scale 2.0f, each step should be about 40 dpi. + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 2.0f, false); + reset(mScreenMagnificationController); + + testFullscreenMagnificationPanWithStepSize(40.0f); + } + + @Test + public void panMagnificationByStep_fullscreenMode_stepSizeAtScale8() throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + // At scale 8.0f, each step should be about 27 dpi. + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false); + reset(mScreenMagnificationController); + + testFullscreenMagnificationPanWithStepSize(27.0f); + } + + @Test + public void panMagnificationByStep_windowMode_stepSizeAtScale2() throws RemoteException { + mMagnificationConnectionManager.enableWindowMagnification(TEST_DISPLAY, 2.0f, 100f, 200f); + + testWindowMagnificationPanWithStepSize(40.0f); + } + + @Test + public void panMagnificationByStep_windowMode_stepSizeAtScale8() throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + // At scale 8.0f, each step should be about 27. + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false); + reset(mMagnificationConnectionManager); + + testWindowMagnificationPanWithStepSize(27.0f); + } + + @Test + public void panMagnificationByStep_fullscreenMode_reachesRightEdgeOfScreen() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + // At scale 2.0f, each step should be about 40. + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, DEFAULT_SCALE, false); + reset(mScreenMagnificationController); + + float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + + DisplayMetrics metrics = new DisplayMetrics(); + mDisplay.getMetrics(metrics); + float expectedStep = 40.0f * metrics.density; + + // Move right, eventually we should reach the edge. + int maxNumSteps = (int) (metrics.widthPixels / expectedStep) + 1; + int numSteps = 0; + while (numSteps < maxNumSteps) { + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_RIGHT); + float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + assertThat(currentCenterY).isEqualTo(newCenterY); + + assertThat(newCenterX).isAtLeast(currentCenterX); + if (newCenterX == currentCenterX) { + break; + } + + currentCenterX = newCenterX; + currentCenterY = newCenterY; + numSteps++; + } + assertWithMessage("Still not at edge after panning right " + numSteps + + " steps. Current position: " + currentCenterX + "," + currentCenterY) + .that(numSteps).isLessThan(maxNumSteps); + } + + @Test + public void panMagnificationByStep_fullscreenMode_reachesBottomEdgeOfScreen() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + // At scale 2.0f, each step should be about 40. + mMagnificationController.onPerformScaleAction(TEST_DISPLAY, DEFAULT_SCALE, false); + reset(mScreenMagnificationController); + + float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + + DisplayMetrics metrics = new DisplayMetrics(); + mDisplay.getMetrics(metrics); + float expectedStep = 40.0f * metrics.density; + + // Move down, eventually we should reach the edge. + int maxNumSteps = (int) (metrics.heightPixels / expectedStep) + 1; + int numSteps = 0; + while (numSteps < maxNumSteps) { + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_DOWN); + float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + assertThat(currentCenterX).isEqualTo(newCenterX); + + assertThat(newCenterY).isAtLeast(currentCenterY); + if (newCenterY == currentCenterY) { + break; + } + + currentCenterX = newCenterX; + currentCenterY = newCenterY; + numSteps++; + } + assertWithMessage("Still not at edge after panning down " + + numSteps + " steps. Current position: " + currentCenterX + "," + currentCenterY) + .that(numSteps).isLessThan(maxNumSteps); + } + + @Test public void enableWindowMode_notifyMagnificationChanged() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); @@ -1425,6 +1565,91 @@ public class MagnificationControllerTest { return captor.getValue(); } + private void testFullscreenMagnificationPanWithStepSize(float expectedStep) { + DisplayMetrics metrics = new DisplayMetrics(); + mDisplay.getMetrics(metrics); + expectedStep *= metrics.density; + + float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + + // Move right. + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_RIGHT); + float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + expect.that(currentCenterX).isLessThan(newCenterX); + expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep); + expect.that(currentCenterY).isEqualTo(newCenterY); + + currentCenterX = newCenterX; + currentCenterY = newCenterY; + + // Move left. + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_LEFT); + newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + expect.that(currentCenterX).isGreaterThan(newCenterX); + expect.that(currentCenterX - newCenterX).isWithin(0.01f).of(expectedStep); + expect.that(currentCenterY).isEqualTo(newCenterY); + + currentCenterX = newCenterX; + currentCenterY = newCenterY; + + // Move down. + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_DOWN); + newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + expect.that(currentCenterX).isEqualTo(newCenterX); + expect.that(currentCenterY).isLessThan(newCenterY); + expect.that(newCenterY - currentCenterY).isWithin(0.1f).of(expectedStep); + + currentCenterX = newCenterX; + currentCenterY = newCenterY; + + // Move up. + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_UP); + newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY); + newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY); + expect.that(currentCenterX).isEqualTo(newCenterX); + expect.that(currentCenterY).isGreaterThan(newCenterY); + expect.that(currentCenterY - newCenterY).isWithin(0.01f).of(expectedStep); + } + + private void testWindowMagnificationPanWithStepSize(float expectedStepDip) + throws RemoteException { + DisplayMetrics metrics = new DisplayMetrics(); + mDisplay.getMetrics(metrics); + final float expectedStep = expectedStepDip * metrics.density; + + // Move right. + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_RIGHT); + verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY), + floatThat(step -> Math.abs(step - expectedStep) < 0.0001), eq(0.0f)); + + // Move left. + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_LEFT); + verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY), + floatThat(step -> Math.abs(expectedStep - step) < 0.0001), eq(0.0f)); + + // Move down. + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_DOWN); + verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY), + eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001)); + + // Move up. + mMagnificationController.panMagnificationByStep(TEST_DISPLAY, + MagnificationController.PAN_DIRECTION_UP); + verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY), + eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001)); + } + private static class WindowMagnificationMgrCallbackDelegate implements MagnificationConnectionManager.Callback { private MagnificationConnectionManager.Callback mCallback; diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 2fe6918630f6..7dbbff22a537 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -33,6 +33,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.server.am.UserController.CLEAR_USER_JOURNEY_SESSION_MSG; import static com.android.server.am.UserController.COMPLETE_USER_SWITCH_MSG; import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG; +import static com.android.server.am.UserController.DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS; import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG; import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG; import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG; @@ -94,6 +95,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManagerInternal; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; @@ -181,14 +183,12 @@ public class UserControllerTest { Intent.ACTION_USER_STARTING); private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES = newHashSet( - 0, // for startUserInternalOnHandler REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, USER_START_MSG, USER_CURRENT_MSG); private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet( - 0, // for startUserInternalOnHandler USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG); @@ -376,7 +376,7 @@ public class UserControllerTest { // and the cascade effect goes on...). In fact, a better approach would to not assert the // binder calls, but their side effects (in this case, that the user is stopped right away) assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes()) - .containsExactly(/* for startUserInternalOnHandler */ 0, USER_START_MSG); + .containsExactly(USER_START_MSG); } private void startUserAssertions( @@ -419,17 +419,12 @@ public class UserControllerTest { @Test public void testDispatchUserSwitch() throws RemoteException { // Prepare mock observer and register it - IUserSwitchObserver observer = mock(IUserSwitchObserver.class); - when(observer.asBinder()).thenReturn(new Binder()); - doAnswer(invocation -> { - IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1]; - callback.sendResult(null); - return null; - }).when(observer).onUserSwitching(anyInt(), any()); - mUserController.registerUserSwitchObserver(observer, "mock"); + IUserSwitchObserver observer = registerUserSwitchObserver( + /* replyToOnBeforeUserSwitchingCallback= */ true, + /* replyToOnUserSwitchingCallback= */ true); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); - verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any()); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -453,14 +448,26 @@ public class UserControllerTest { } @Test + public void testShouldCrashWhenOnBeforeUserSwitchingTimeouts() throws RemoteException { + IUserSwitchObserver observer = registerUserSwitchObserver( + /* replyToOnBeforeUserSwitchingCallback= */ false, + /* replyToOnUserSwitchingCallback= */ true); + mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any()); + assertThrows("Should have crashed when observers don't reply to onBeforeUserSwitching in " + + DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS + " ms", RuntimeException.class, + mInjector.mHandler::runPendingCallbacks); + } + + @Test public void testDispatchUserSwitchBadReceiver() throws RemoteException { - // Prepare mock observer which doesn't notify the callback and register it - IUserSwitchObserver observer = mock(IUserSwitchObserver.class); - when(observer.asBinder()).thenReturn(new Binder()); - mUserController.registerUserSwitchObserver(observer, "mock"); + // Prepare mock observer which doesn't notify the onUserSwitching callback and register it + IUserSwitchObserver observer = registerUserSwitchObserver( + /* replyToOnBeforeUserSwitchingCallback= */ true, + /* replyToOnUserSwitchingCallback= */ false); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); - verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any()); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -551,7 +558,6 @@ public class UserControllerTest { expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG); if (backgroundUserStopping) { expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG); - expectedCodes.add(0); // this is for directly posting in stopping. } if (expectScheduleBackgroundUserStopping) { expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG); @@ -567,9 +573,9 @@ public class UserControllerTest { @Test public void testDispatchUserSwitchComplete() throws RemoteException { // Prepare mock observer and register it - IUserSwitchObserver observer = mock(IUserSwitchObserver.class); - when(observer.asBinder()).thenReturn(new Binder()); - mUserController.registerUserSwitchObserver(observer, "mock"); + IUserSwitchObserver observer = registerUserSwitchObserver( + /* replyToOnBeforeUserSwitchingCallback= */ true, + /* replyToOnUserSwitchingCallback= */ true); // Start user -- this will update state of mUserController mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); @@ -1752,6 +1758,29 @@ public class UserControllerTest { verify(mInjector, never()).onSystemUserVisibilityChanged(anyBoolean()); } + private IUserSwitchObserver registerUserSwitchObserver( + boolean replyToOnBeforeUserSwitchingCallback, boolean replyToOnUserSwitchingCallback) + throws RemoteException { + IUserSwitchObserver observer = mock(IUserSwitchObserver.class); + when(observer.asBinder()).thenReturn(new Binder()); + if (replyToOnBeforeUserSwitchingCallback) { + doAnswer(invocation -> { + IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1]; + callback.sendResult(null); + return null; + }).when(observer).onBeforeUserSwitching(anyInt(), any()); + } + if (replyToOnUserSwitchingCallback) { + doAnswer(invocation -> { + IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1]; + callback.sendResult(null); + return null; + }).when(observer).onUserSwitching(anyInt(), any()); + } + mUserController.registerUserSwitchObserver(observer, "mock"); + return observer; + } + // Should be public to allow mocking private static class TestInjector extends UserController.Injector { public final TestHandler mHandler; @@ -1957,6 +1986,7 @@ public class UserControllerTest { * fix this, but in the meantime, this is your warning. */ private final List<Message> mMessages = new ArrayList<>(); + private final List<Runnable> mPendingCallbacks = new ArrayList<>(); TestHandler(Looper looper) { super(looper); @@ -1989,14 +2019,24 @@ public class UserControllerTest { @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - Message copy = new Message(); - copy.copyFrom(msg); - mMessages.add(copy); - if (msg.getCallback() != null) { - msg.getCallback().run(); + if (msg.getCallback() == null) { + Message copy = new Message(); + copy.copyFrom(msg); + mMessages.add(copy); + } else { + if (SystemClock.uptimeMillis() >= uptimeMillis) { + msg.getCallback().run(); + } else { + mPendingCallbacks.add(msg.getCallback()); + } msg.setCallback(null); } return super.sendMessageAtTime(msg, uptimeMillis); } + + private void runPendingCallbacks() { + mPendingCallbacks.forEach(Runnable::run); + mPendingCallbacks.clear(); + } } } diff --git a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java index 9c61d95bc5e5..9528a05d38a0 100644 --- a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java @@ -23,13 +23,13 @@ import android.test.AndroidTestCase; import android.util.Pair; import android.util.Xml; -import com.android.internal.util.FastXmlSerializer; import com.android.modules.utils.TypedXmlSerializer; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.StringWriter; @@ -123,8 +123,24 @@ public class CacheQuotaStrategyTest extends AndroidTestCase { buildCacheQuotaHint("uuid2", 10, 250)); } + @Test + public void testReadInvalidInput() throws Exception { + String input = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<cache-info previousBytes=\"1000\">\n" + + "<quota/>\n" + + "</cache-info>\n"; + + try { + CacheQuotaStrategy.readFromXml(new ByteArrayInputStream( + input.getBytes("UTF-8"))); + fail("Expected XML parsing exception"); + } catch (XmlPullParserException e) { + // Expected XmlPullParserException exception + } + } + private CacheQuotaHint buildCacheQuotaHint(String volumeUuid, int uid, long quota) { return new CacheQuotaHint.Builder() .setVolumeUuid(volumeUuid).setUid(uid).setQuota(quota).build(); } -}
\ No newline at end of file +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java index fa1372d9f4ef..87b9154cfb4d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java @@ -88,6 +88,8 @@ public class EventConditionProviderTest extends UiServiceTestCase { mService.mContext = this.getContext(); mContext.addMockSystemService(UserManager.class, mUserManager); + when(mUserManager.getProfiles(eq(UserHandle.USER_SYSTEM))).thenReturn( + List.of(new UserInfo(UserHandle.USER_SYSTEM, "USER_SYSTEM", 0))); when(mUserManager.getProfiles(eq(mUserId))).thenReturn( List.of(new UserInfo(mUserId, "mUserId", 0))); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index decbaacdcef9..d1dc8d6e81c8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -274,6 +274,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { assertEquals(new ArraySet<>(), approved.get(true)); } + @SuppressWarnings("GuardedBy") @Test public void testReadXml_userDisabled_restore() throws Exception { String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">" @@ -289,7 +290,8 @@ public class NotificationAssistantsTest extends UiServiceTestCase { mAssistants.readXml(parser, mNm::canUseManagedServices, true, ActivityManager.getCurrentUser()); - ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0); + ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get( + ActivityManager.getCurrentUser()); // approved should not be null assertNotNull(approved); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 1fc0d245fbe5..601023f89656 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -16712,6 +16712,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mResources.getResourceName(eq(iconResId))).thenReturn(iconResName); when(mResources.getIdentifier(eq(iconResName), any(), any())).thenReturn(iconResId); when(mPackageManagerClient.getResourcesForApplication(eq(pkg))).thenReturn(mResources); + + // Ensure that there is a zen configuration for the user running the test (won't be + // USER_SYSTEM if running on HSUM). + mService.mZenModeHelper.onUserSwitched(mUserId); } @Test @@ -17679,4 +17683,145 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.mNotificationList).isEmpty(); } + + @Test + @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testUnbundleNotification_ungrouped_restoresOriginalChannel() throws Exception { + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); + when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); + + // Post a single notification + final boolean hasOriginalSummary = false; + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + final String keyToUnbundle = r.getKey(); + mService.addNotification(r); + + // Classify notification into the NEWS bundle + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS); + Adjustment adjustment = new Adjustment( + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + mBinderService.applyAdjustmentFromAssistant(null, adjustment); + waitForIdle(); + r.applyAdjustments(); + // Check that the NotificationRecord channel is updated + assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID); + // Check that the Notification mChannelId is not updated + assertThat(r.getNotification().getChannelId()).isEqualTo(TEST_CHANNEL_ID); + + // Unbundle the notification + mService.mNotificationDelegate.unbundleNotification(keyToUnbundle); + + // Check that the original channel was restored + assertThat(r.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID); + verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r), eq(hasOriginalSummary)); + } + + @Test + @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testUnbundleNotification_grouped_restoresOriginalChannel() throws Exception { + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); + when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); + + // Post grouped notifications + final String originalGroupName = "originalGroup"; + final int summaryId = 0; + final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel, + summaryId + 1, originalGroupName, false); + mService.addNotification(r1); + final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, + summaryId + 2, originalGroupName, false); + mService.addNotification(r2); + final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel, + summaryId, originalGroupName, true); + mService.addNotification(summary); + final String originalGroupKey = summary.getGroupKey(); + assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary); + + // Classify a child notification into the NEWS bundle + final String keyToUnbundle = r1.getKey(); + final boolean hasOriginalSummary = true; + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS); + Adjustment adjustment = new Adjustment(r1.getSbn().getPackageName(), r1.getKey(), signals, + "", r1.getUser().getIdentifier()); + mBinderService.applyAdjustmentFromAssistant(null, adjustment); + waitForIdle(); + r1.applyAdjustments(); + assertThat(r1.getChannel().getId()).isEqualTo(NEWS_ID); + + // Unbundle the notification + mService.mNotificationDelegate.unbundleNotification(keyToUnbundle); + + // Check that the original channel was restored + assertThat(r1.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID); + verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary)); + } + + @Test + @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testUnbundleNotification_groupedSummaryCanceled_restoresOriginalChannel() + throws Exception { + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); + when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true); + + // Post grouped notifications + final String originalGroupName = "originalGroup"; + final int summaryId = 0; + final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel, + summaryId + 1, originalGroupName, false); + mService.addNotification(r1); + final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, + summaryId + 2, originalGroupName, false); + mService.addNotification(r2); + final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel, + summaryId, originalGroupName, true); + mService.addNotification(summary); + final String originalGroupKey = summary.getGroupKey(); + assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary); + + // Classify a child notification into the NEWS bundle + final String keyToUnbundle = r1.getKey(); + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS); + Adjustment adjustment = new Adjustment(r1.getSbn().getPackageName(), r1.getKey(), signals, + "", r1.getUser().getIdentifier()); + mBinderService.applyAdjustmentFromAssistant(null, adjustment); + waitForIdle(); + r1.applyAdjustments(); + assertThat(r1.getChannel().getId()).isEqualTo(NEWS_ID); + + // Cancel original summary + final boolean hasOriginalSummary = false; + mService.mSummaryByGroupKey.remove(summary.getGroupKey()); + + // Unbundle the notification + mService.mNotificationDelegate.unbundleNotification(keyToUnbundle); + + // Check that the original channel was restored + assertThat(r1.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID); + verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary)); + } + } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 80e86a15156a..8e79514c875e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -66,7 +66,6 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; -import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; import static com.android.server.notification.Flags.FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI; import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA; import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER; @@ -155,7 +154,6 @@ import android.util.proto.ProtoOutputStream; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.config.sysui.TestableFlagResolver; @@ -167,9 +165,6 @@ import com.android.os.AtomsProto.PackageNotificationPreferences; import com.android.server.UiServiceTestCase; import com.android.server.notification.PermissionHelper.PackagePermission; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.protobuf.InvalidProtocolBufferException; @@ -204,6 +199,9 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA) @@ -2640,6 +2638,35 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void getPackagesBypassingDnd_multipleUsers() { + int uidUser1 = UserHandle.getUid(1, UID_P); + NotificationChannel channelUser1Bypass = new NotificationChannel("id11", "name1", + NotificationManager.IMPORTANCE_MAX); + channelUser1Bypass.setBypassDnd(true); + NotificationChannel channelUser1NoBypass = new NotificationChannel("id12", "name2", + NotificationManager.IMPORTANCE_MAX); + channelUser1NoBypass.setBypassDnd(false); + + int uidUser2 = UserHandle.getUid(2, UID_P); + NotificationChannel channelUser2Bypass = new NotificationChannel("id21", "name1", + NotificationManager.IMPORTANCE_MAX); + channelUser2Bypass.setBypassDnd(true); + + mHelper.createNotificationChannel(PKG_P, uidUser1, channelUser1Bypass, true, + /* hasDndAccess= */ true, uidUser1, false); + mHelper.createNotificationChannel(PKG_P, uidUser1, channelUser1NoBypass, true, + /* hasDndAccess= */ true, uidUser1, false); + mHelper.createNotificationChannel(PKG_P, uidUser2, channelUser2Bypass, true, + /* hasDndAccess= */ true, uidUser2, false); + + assertThat(mHelper.getPackagesBypassingDnd(0)).isEmpty(); + assertThat(mHelper.getPackagesBypassingDnd(1)) + .containsExactly(new ZenBypassingApp(PKG_P, false)); + assertThat(mHelper.getPackagesBypassingDnd(2)) + .containsExactly(new ZenBypassingApp(PKG_P, true)); + } + + @Test public void getPackagesBypassingDnd_oneChannelBypassing_groupBlocked() { int uid = UID_N_MR1; NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1"); @@ -3169,7 +3196,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { doThrow(new SecurityException("no access")).when(mUgmInternal) .checkGrantUriPermission(eq(UID_N_MR1), any(), eq(sound), - anyInt(), eq(Process.myUserHandle().getIdentifier())); + anyInt(), eq(UserHandle.getUserId(UID_N_MR1))); final NotificationChannel channel = new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_DEFAULT); @@ -3189,7 +3216,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { doThrow(new SecurityException("no access")).when(mUgmInternal) .checkGrantUriPermission(eq(UID_N_MR1), any(), any(), - anyInt(), eq(Process.myUserHandle().getIdentifier())); + anyInt(), eq(UserHandle.getUserId(UID_N_MR1))); final NotificationChannel channel = new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_DEFAULT); @@ -3208,7 +3235,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { doThrow(new SecurityException("no access")).when(mUgmInternal) .checkGrantUriPermission(eq(UID_N_MR1), any(), any(), - anyInt(), eq(Process.myUserHandle().getIdentifier())); + anyInt(), eq(UserHandle.getUserId(UID_N_MR1))); final NotificationChannel channel = new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_DEFAULT); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index 670f9f697a5c..bacf5ed9d81f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -53,7 +53,9 @@ import android.content.pm.UserPackage; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.DexmakerShareClassLoaderRule; import android.util.Pair; import android.util.SparseArray; @@ -66,6 +68,7 @@ import com.android.internal.app.SuspendedAppActivity; import com.android.internal.app.UnlaunchableAppActivity; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; +import com.android.window.flags.Flags; import org.junit.After; import org.junit.Before; @@ -133,6 +136,8 @@ public class ActivityStartInterceptorTest { private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks = new SparseArray<>(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); @@ -237,6 +242,20 @@ public class ActivityStartInterceptorTest { } @Test + @EnableFlags(Flags.FLAG_NORMALIZE_HOME_INTENT) + public void testInterceptIncorrectHomeIntent() { + // Create a non-standard home intent + final Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + homeIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + // Ensure the intent is intercepted and normalized to standard home intent. + assertTrue(mInterceptor.intercept(homeIntent, null, mAInfo, null, null, null, 0, 0, null, + mTaskDisplayArea, false)); + assertTrue(ActivityRecord.isHomeIntent(homeIntent)); + } + + @Test public void testInterceptLockTaskModeViolationPackage() { when(mLockTaskController.isActivityAllowed( TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)) diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java index 09ed9baba096..90bf5f03bb1f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java @@ -302,15 +302,15 @@ public class AppCompatOrientationPolicyTest extends WindowTestsBase { } @Test - public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() { + public void testOverrideOrientationIfNeeded_userFullscreenOverride_notLetterboxed_unchanged() { runTestScenarioWithActivity((robot) -> { robot.applyOnActivity((a) -> { a.setShouldApplyUserFullscreenOverride(true); a.setIgnoreOrientationRequest(true); }); - robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_UNSPECIFIED, - /* expected */ SCREEN_ORIENTATION_USER); + robot.checkOverrideOrientation(/* candidate */ SCREEN_ORIENTATION_LOCKED, + /* expected */ SCREEN_ORIENTATION_LOCKED); }); } diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 579ed6659976..e0e8aa45231b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -447,8 +447,8 @@ public class BackNavigationControllerTests extends WindowTestsBase { @Test public void backInfoWindowWithNoActivity() { - WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_WALLPAPER, - "Wallpaper"); + WindowState window = newWindowBuilder("Wallpaper", + WindowManager.LayoutParams.TYPE_WALLPAPER).build(); addToWindowMap(window, true); makeWindowVisibleAndDrawn(window); @@ -468,8 +468,8 @@ public class BackNavigationControllerTests extends WindowTestsBase { @Test public void backInfoWithAnimationCallback() { - WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_WALLPAPER, - "Wallpaper"); + WindowState window = newWindowBuilder("Wallpaper", + WindowManager.LayoutParams.TYPE_WALLPAPER).build(); addToWindowMap(window, true); makeWindowVisibleAndDrawn(window); @@ -535,8 +535,8 @@ public class BackNavigationControllerTests extends WindowTestsBase { .build(); testActivity.info.applicationInfo.privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; - final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, testActivity, - "window"); + final WindowState window = newWindowBuilder("window", TYPE_BASE_APPLICATION).setWindowToken( + testActivity).build(); addToWindowMap(window, true); makeWindowVisibleAndDrawn(window); IOnBackInvokedCallback callback = withSystemCallback(testActivity.getTask()); @@ -610,8 +610,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { @Test public void backInfoWindowWithoutDrawn() { - WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_APPLICATION, - "TestWindow"); + WindowState window = newWindowBuilder("TestWindow", TYPE_APPLICATION).build(); addToWindowMap(window, true); IOnBackInvokedCallback callback = createOnBackInvokedCallback(); @@ -677,7 +676,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { assertEquals("change focus back, callback should not have been called", 1, navigationObserver.getCount()); - WindowState newWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlayWindow"); + WindowState newWindow = newWindowBuilder("overlayWindow", TYPE_APPLICATION_OVERLAY).build(); addToWindowMap(newWindow, true); mBackNavigationController.onFocusChanged(newWindow); assertEquals("Focus change, callback should have been called", @@ -902,7 +901,8 @@ public class BackNavigationControllerTests extends WindowTestsBase { // enable OnBackInvokedCallbacks record.info.applicationInfo.privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; - WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, record, "window"); + WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).setWindowToken( + record).build(); when(record.mSurfaceControl.isValid()).thenReturn(true); Mockito.doNothing().when(task).reparentSurfaceControl(any(), any()); mAtm.setFocusedTask(task.mTaskId, record); @@ -918,8 +918,10 @@ public class BackNavigationControllerTests extends WindowTestsBase { // enable OnBackInvokedCallbacks record.info.applicationInfo.privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; - WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, record, "window"); - WindowState dialog = createWindow(null, TYPE_APPLICATION, record, "dialog"); + WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).setWindowToken( + record).build(); + WindowState dialog = newWindowBuilder("dialog", TYPE_APPLICATION).setWindowToken( + record).build(); when(record.mSurfaceControl.isValid()).thenReturn(true); Mockito.doNothing().when(task).reparentSurfaceControl(any(), any()); mAtm.setFocusedTask(task.mTaskId, record); @@ -944,8 +946,10 @@ public class BackNavigationControllerTests extends WindowTestsBase { // enable OnBackInvokedCallbacks record2.info.applicationInfo.privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; - WindowState window1 = createWindow(null, FIRST_APPLICATION_WINDOW, record1, "window1"); - WindowState window2 = createWindow(null, FIRST_APPLICATION_WINDOW, record2, "window2"); + WindowState window1 = newWindowBuilder("window1", FIRST_APPLICATION_WINDOW).setWindowToken( + record1).build(); + WindowState window2 = newWindowBuilder("window2", FIRST_APPLICATION_WINDOW).setWindowToken( + record2).build(); when(task.mSurfaceControl.isValid()).thenReturn(true); when(record1.mSurfaceControl.isValid()).thenReturn(true); when(record2.mSurfaceControl.isValid()).thenReturn(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index 0a7df5a305bc..0af41ea1f634 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -450,7 +450,7 @@ public class DisplayAreaTest extends WindowTestsBase { public void testGetOrientation() { final DisplayArea.Tokens area = new DisplayArea.Tokens(mWm, ABOVE_TASKS, "test"); mDisplayContent.addChild(area, POSITION_TOP); - final WindowState win = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay"); + final WindowState win = newWindowBuilder("overlay", TYPE_APPLICATION_OVERLAY).build(); win.mAttrs.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; win.mToken.reparent(area, POSITION_TOP); spyOn(win); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index db71f2bf039d..57aacd36b16b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -178,8 +178,8 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addAllCommonWindows = true) @Test public void testForAllWindows() { - final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION, - mDisplayContent, "exiting app"); + final WindowState exitingAppWindow = newWindowBuilder("exiting app", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); final ActivityRecord exitingApp = exitingAppWindow.mActivityRecord; exitingApp.startAnimation(exitingApp.getPendingTransaction(), mock(AnimationAdapter.class), false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION); @@ -211,8 +211,8 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addAllCommonWindows = true) @Test public void testForAllWindows_WithAppImeTarget() { - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); @@ -289,8 +289,8 @@ public class DisplayContentTests extends WindowTestsBase { public void testForAllWindows_WithInBetweenWindowToken() { // This window is set-up to be z-ordered between some windows that go in the same token like // the nav bar and status bar. - final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION, - mDisplayContent, "voiceInteractionWindow"); + final WindowState voiceInteractionWindow = newWindowBuilder("voiceInteractionWindow", + TYPE_VOICE_INTERACTION).setDisplay(mDisplayContent).build(); assertForAllWindowsOrder(Arrays.asList( mWallpaperWindow, @@ -310,7 +310,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeTarget() { // Verify that an app window can be an ime target. - final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); + final WindowState appWin = newWindowBuilder("appWin", TYPE_APPLICATION).setDisplay( + mDisplayContent).build(); appWin.setHasSurface(true); assertTrue(appWin.canBeImeTarget()); WindowState imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); @@ -318,8 +319,8 @@ public class DisplayContentTests extends WindowTestsBase { appWin.mHidden = false; // Verify that an child window can be an ime target. - final WindowState childWin = createWindow(appWin, - TYPE_APPLICATION_ATTACHED_DIALOG, "childWin"); + final WindowState childWin = newWindowBuilder("childWin", + TYPE_APPLICATION_ATTACHED_DIALOG).setParent(appWin).build(); childWin.setHasSurface(true); assertTrue(childWin.canBeImeTarget()); imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); @@ -331,8 +332,8 @@ public class DisplayContentTests extends WindowTestsBase { public void testComputeImeTarget_startingWindow() { ActivityRecord activity = createActivityRecord(mDisplayContent); - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, - "startingWin"); + final WindowState startingWin = newWindowBuilder("startingWin", + TYPE_APPLICATION_STARTING).setWindowToken(activity).build(); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); @@ -342,7 +343,8 @@ public class DisplayContentTests extends WindowTestsBase { // Verify that the starting window still be an ime target even an app window launching // behind it. - final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, activity, "appWin"); + final WindowState appWin = newWindowBuilder("appWin", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); appWin.setHasSurface(true); assertTrue(appWin.canBeImeTarget()); @@ -352,8 +354,8 @@ public class DisplayContentTests extends WindowTestsBase { // Verify that the starting window still be an ime target even the child window behind a // launching app window - final WindowState childWin = createWindow(appWin, - TYPE_APPLICATION_ATTACHED_DIALOG, "childWin"); + final WindowState childWin = newWindowBuilder("childWin", + TYPE_APPLICATION_ATTACHED_DIALOG).setParent(appWin).build(); childWin.setHasSurface(true); assertTrue(childWin.canBeImeTarget()); imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); @@ -365,8 +367,8 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); final ActivityRecord activity = createActivityRecord(mDisplayContent); - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, - "startingWin"); + final WindowState startingWin = newWindowBuilder("startingWin", + TYPE_APPLICATION_STARTING).setWindowToken(activity).build(); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class); @@ -385,10 +387,10 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeTargetReturnsNull_windowDidntRequestIme() { - final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, - new ActivityBuilder(mAtm).setCreateTask(true).build(), "app"); - final WindowState win2 = createWindow(null, TYPE_BASE_APPLICATION, - new ActivityBuilder(mAtm).setCreateTask(true).build(), "app2"); + final WindowState win1 = newWindowBuilder("app", TYPE_BASE_APPLICATION).setWindowToken( + new ActivityBuilder(mAtm).setCreateTask(true).build()).build(); + final WindowState win2 = newWindowBuilder("app2", TYPE_BASE_APPLICATION).setWindowToken( + new ActivityBuilder(mAtm).setCreateTask(true).build()).build(); mDisplayContent.setImeInputTarget(win1); mDisplayContent.setImeLayeringTarget(win2); @@ -404,8 +406,8 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); final ActivityRecord activity = createActivityRecord(mDisplayContent); - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, - "startingWin"); + final WindowState startingWin = newWindowBuilder("startingWin", + TYPE_APPLICATION_STARTING).setWindowToken(activity).build(); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class); @@ -433,8 +435,8 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); final ActivityRecord activity = createActivityRecord(mDisplayContent); - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, - "startingWin"); + final WindowState startingWin = newWindowBuilder("startingWin", + TYPE_APPLICATION_STARTING).setWindowToken(activity).build(); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); @@ -532,8 +534,8 @@ public class DisplayContentTests extends WindowTestsBase { mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled; // Create a focusable window and check that focus is calculated correctly - final WindowState window1 = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1"); + final WindowState window1 = newWindowBuilder("window1", TYPE_BASE_APPLICATION).setDisplay( + mDisplayContent).build(); window1.mActivityRecord.mTargetSdk = targetSdk; updateFocusedWindow(); assertTrue(window1.isFocused()); @@ -549,7 +551,8 @@ public class DisplayContentTests extends WindowTestsBase { final ActivityRecord app2 = new ActivityBuilder(mAtm) .setTask(new TaskBuilder(mSupervisor).setDisplay(dc).build()) .setUseProcess(window1.getProcess()).setOnTop(true).build(); - final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, app2, "window2"); + final WindowState window2 = newWindowBuilder("window2", + TYPE_BASE_APPLICATION).setWindowToken(app2).build(); window2.mActivityRecord.mTargetSdk = targetSdk; updateFocusedWindow(); assertTrue(window2.isFocused()); @@ -616,7 +619,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testDisplayHasContent() { - final WindowState window = createWindow(null, TYPE_APPLICATION_OVERLAY, "window"); + final WindowState window = newWindowBuilder("window", TYPE_APPLICATION_OVERLAY).build(); setDrawnState(WindowStateAnimator.COMMIT_DRAW_PENDING, window); assertFalse(mDisplayContent.getLastHasContent()); // The pending draw state should be committed and the has-content state is also updated. @@ -632,7 +635,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testImeIsAttachedToDisplayForLetterboxedApp() { final DisplayContent dc = mDisplayContent; - final WindowState ws = createWindow(null, TYPE_APPLICATION, dc, "app window"); + final WindowState ws = newWindowBuilder("app window", TYPE_APPLICATION).setDisplay( + dc).build(); dc.setImeLayeringTarget(ws); dc.setImeInputTarget(ws); @@ -655,7 +659,8 @@ public class DisplayContentTests extends WindowTestsBase { final WindowState[] windows = new WindowState[types.length]; for (int i = 0; i < types.length; i++) { final int type = types[i]; - windows[i] = createWindow(null /* parent */, type, displayContent, "window-" + type); + windows[i] = newWindowBuilder("window-" + type, type).setDisplay( + displayContent).build(); windows[i].setHasSurface(true); windows[i].mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING; } @@ -887,7 +892,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testLayoutSeq_assignedDuringLayout() { final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); + final WindowState win = newWindowBuilder("w", TYPE_BASE_APPLICATION).setDisplay(dc).build(); performLayout(dc); @@ -902,10 +907,12 @@ public class DisplayContentTests extends WindowTestsBase { // Create a window that requests landscape orientation. It will define device orientation // by default. - final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); + final WindowState window = newWindowBuilder("w", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); window.mActivityRecord.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - final WindowState keyguard = createWindow(null, TYPE_NOTIFICATION_SHADE , dc, "keyguard"); + final WindowState keyguard = newWindowBuilder("keyguard", + TYPE_NOTIFICATION_SHADE).setDisplay(dc).build(); keyguard.mHasSurface = true; keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; @@ -936,8 +943,8 @@ public class DisplayContentTests extends WindowTestsBase { // Create a window that requests a fixed orientation. It will define device orientation // by default. - final WindowState window = createWindow(null /* parent */, TYPE_APPLICATION_OVERLAY, dc, - "window"); + final WindowState window = newWindowBuilder("window", TYPE_APPLICATION_OVERLAY).setDisplay( + dc).build(); window.mHasSurface = true; window.mAttrs.screenOrientation = SCREEN_ORIENTATION_LANDSCAPE; @@ -1003,12 +1010,14 @@ public class DisplayContentTests extends WindowTestsBase { public void testInputMethodTargetUpdateWhenSwitchingOnDisplays() { final DisplayContent newDisplay = createNewDisplay(); - final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); + final WindowState appWin = newWindowBuilder("appWin", TYPE_APPLICATION).setDisplay( + mDisplayContent).build(); final Task rootTask = mDisplayContent.getTopRootTask(); final ActivityRecord activity = rootTask.topRunningActivity(); doReturn(true).when(activity).shouldBeVisibleUnchecked(); - final WindowState appWin1 = createWindow(null, TYPE_APPLICATION, newDisplay, "appWin1"); + final WindowState appWin1 = newWindowBuilder("appWin1", TYPE_APPLICATION).setDisplay( + newDisplay).build(); final Task rootTask1 = newDisplay.getTopRootTask(); final ActivityRecord activity1 = rootTask1.topRunningActivity(); doReturn(true).when(activity1).shouldBeVisibleUnchecked(); @@ -1203,7 +1212,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_app() throws Exception { final DisplayContent dc = createNewDisplay(); - dc.setImeLayeringTarget(createWindow(null, TYPE_BASE_APPLICATION, "app")); + dc.setImeLayeringTarget(newWindowBuilder("app", TYPE_BASE_APPLICATION).build()); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); assertEquals(dc.getImeTarget( IME_TARGET_LAYERING).getWindow().mActivityRecord.getSurfaceControl(), @@ -1213,7 +1222,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_app_notFullscreen() throws Exception { final DisplayContent dc = createNewDisplay(); - dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "app")); + dc.setImeLayeringTarget(newWindowBuilder("app", TYPE_STATUS_BAR).build()); dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode( WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); @@ -1235,7 +1244,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_noApp() throws Exception { final DisplayContent dc = createNewDisplay(); - dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "statusBar")); + dc.setImeLayeringTarget(newWindowBuilder("statusBar", TYPE_STATUS_BAR).build()); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent().getSurfaceControl()); @@ -1244,8 +1253,8 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_ACTIVITY) @Test public void testComputeImeParent_inputTargetNotUpdate() throws Exception { - WindowState app1 = createWindow(null, TYPE_BASE_APPLICATION, "app1"); - WindowState app2 = createWindow(null, TYPE_BASE_APPLICATION, "app2"); + WindowState app1 = newWindowBuilder("app1", TYPE_BASE_APPLICATION).build(); + WindowState app2 = newWindowBuilder("app2", TYPE_BASE_APPLICATION).build(); doReturn(true).when(mDisplayContent).shouldImeAttachedToApp(); mDisplayContent.setImeLayeringTarget(app1); mDisplayContent.setImeInputTarget(app1); @@ -1260,10 +1269,10 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_ACTIVITY) @Test public void testComputeImeParent_updateParentWhenTargetNotUseIme() throws Exception { - WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay"); + WindowState overlay = newWindowBuilder("overlay", TYPE_APPLICATION_OVERLAY).build(); overlay.setBounds(100, 100, 200, 200); overlay.mAttrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM; - WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app"); + WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).build(); mDisplayContent.setImeLayeringTarget(overlay); mDisplayContent.setImeInputTarget(app); assertFalse(mDisplayContent.shouldImeAttachedToApp()); @@ -1274,8 +1283,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeParent_remoteControlTarget() throws Exception { final DisplayContent dc = mDisplayContent; - WindowState app1 = createWindow(null, TYPE_BASE_APPLICATION, "app1"); - WindowState app2 = createWindow(null, TYPE_BASE_APPLICATION, "app2"); + WindowState app1 = newWindowBuilder("app1", TYPE_BASE_APPLICATION).build(); + WindowState app2 = newWindowBuilder("app2", TYPE_BASE_APPLICATION).build(); dc.setImeLayeringTarget(app1); dc.setImeInputTarget(app2); @@ -1301,7 +1310,7 @@ public class DisplayContentTests extends WindowTestsBase { public void testInputMethodInputTarget_isClearedWhenWindowStateIsRemoved() throws Exception { final DisplayContent dc = createNewDisplay(); - WindowState app = createWindow(null, TYPE_BASE_APPLICATION, dc, "app"); + WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).setDisplay(dc).build(); dc.setImeInputTarget(app); assertEquals(app, dc.computeImeControlTarget()); @@ -1316,7 +1325,7 @@ public class DisplayContentTests extends WindowTestsBase { public void testComputeImeControlTarget() throws Exception { final DisplayContent dc = createNewDisplay(); dc.setRemoteInsetsController(createDisplayWindowInsetsController()); - dc.mCurrentFocus = createWindow(null, TYPE_BASE_APPLICATION, "app"); + dc.mCurrentFocus = newWindowBuilder("app", TYPE_BASE_APPLICATION).build(); // Expect returning null IME control target when the focus window has not yet been the // IME input target (e.g. IME is restarting) in fullscreen windowing mode. @@ -1332,7 +1341,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testComputeImeControlTarget_splitscreen() throws Exception { final DisplayContent dc = createNewDisplay(); - dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app")); + dc.setImeInputTarget(newWindowBuilder("app", TYPE_BASE_APPLICATION).build()); dc.getImeInputTarget().getWindowState().setWindowingMode( WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); dc.setImeLayeringTarget(dc.getImeInputTarget().getWindowState()); @@ -1346,7 +1355,7 @@ public class DisplayContentTests extends WindowTestsBase { public void testImeSecureFlagGetUpdatedAfterImeInputTarget() { // Verify IME window can get up-to-date secure flag update when the IME input target // set before setCanScreenshot called. - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); SurfaceControl.Transaction t = mDisplayContent.mInputMethodWindow.getPendingTransaction(); spyOn(t); mDisplayContent.setImeInputTarget(app); @@ -1391,7 +1400,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testUpdateSystemGestureExclusion() throws Exception { final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40))); @@ -1423,11 +1433,12 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testCalculateSystemGestureExclusion() throws Exception { final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40))); - final WindowState win2 = createWindow(null, TYPE_APPLICATION, dc, "win2"); + final WindowState win2 = newWindowBuilder("win2", TYPE_APPLICATION).setDisplay(dc).build(); win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win2.setSystemGestureExclusion(Collections.singletonList(new Rect(20, 30, 40, 50))); @@ -1451,11 +1462,12 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testCalculateSystemGestureExclusion_modal() throws Exception { final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "base"); + final WindowState win = newWindowBuilder("base", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.setSystemGestureExclusion(Collections.singletonList(new Rect(0, 0, 1000, 1000))); - final WindowState win2 = createWindow(null, TYPE_APPLICATION, dc, "modal"); + final WindowState win2 = newWindowBuilder("modal", TYPE_APPLICATION).setDisplay(dc).build(); win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win2.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; win2.getAttrs().width = 10; @@ -1476,7 +1488,8 @@ public class DisplayContentTests extends WindowTestsBase { mWm.mConstants.mSystemGestureExcludedByPreQStickyImmersive = true; final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; @@ -1500,7 +1513,8 @@ public class DisplayContentTests extends WindowTestsBase { mWm.mConstants.mSystemGestureExcludedByPreQStickyImmersive = true; final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setDisplay( + dc).build(); win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; win.getAttrs().privateFlags |= PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; @@ -1559,9 +1573,9 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testHybridRotationAnimation() { final DisplayContent displayContent = mDefaultDisplay; - final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar"); - final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar"); - final WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app"); + final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build(); + final WindowState navBar = newWindowBuilder("navBar", TYPE_NAVIGATION_BAR).build(); + final WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).build(); final WindowState[] windows = { statusBar, navBar, app }; makeWindowVisible(windows); final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); @@ -1863,6 +1877,11 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals("Display must be portrait after closing the translucent activity", Configuration.ORIENTATION_PORTRAIT, mDisplayContent.getConfiguration().orientation); + + mDisplayContent.setFixedRotationLaunchingAppUnchecked(nonTopVisible); + mDisplayContent.onTransitionFinished(); + assertFalse("Complete fixed rotation if not in a transition", + mDisplayContent.hasTopFixedRotationLaunchingApp()); } @Test @@ -2183,7 +2202,8 @@ public class DisplayContentTests extends WindowTestsBase { Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window"); - WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot"); + WindowState behindWindow = newWindowBuilder("Screenshot", TYPE_SCREENSHOT).setDisplay( + display).build(); WindowState result = display.findScrollCaptureTargetWindow(behindWindow, ActivityTaskManager.INVALID_TASK_ID); @@ -2196,7 +2216,7 @@ public class DisplayContentTests extends WindowTestsBase { Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window"); - WindowState invisible = createWindow(null, TYPE_APPLICATION, "invisible"); + WindowState invisible = newWindowBuilder("invisible", TYPE_APPLICATION).build(); invisible.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false WindowState result = display.findScrollCaptureTargetWindow(null, @@ -2209,7 +2229,7 @@ public class DisplayContentTests extends WindowTestsBase { DisplayContent display = createNewDisplay(); Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); - WindowState secureWindow = createWindow(null, TYPE_APPLICATION, "Secure Window"); + WindowState secureWindow = newWindowBuilder("Secure Window", TYPE_APPLICATION).build(); secureWindow.mAttrs.flags |= FLAG_SECURE; WindowState result = display.findScrollCaptureTargetWindow(null, @@ -2222,7 +2242,7 @@ public class DisplayContentTests extends WindowTestsBase { DisplayContent display = createNewDisplay(); Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); - WindowState secureWindow = createWindow(null, TYPE_APPLICATION, "Secure Window"); + WindowState secureWindow = newWindowBuilder("Secure window", TYPE_APPLICATION).build(); secureWindow.mAttrs.flags |= FLAG_SECURE; WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId); @@ -2235,7 +2255,8 @@ public class DisplayContentTests extends WindowTestsBase { Task rootTask = createTask(display); Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window"); - WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot"); + WindowState behindWindow = newWindowBuilder("Screenshot", TYPE_SCREENSHOT).setDisplay( + display).build(); WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId); assertEquals(window, result); @@ -2248,7 +2269,8 @@ public class DisplayContentTests extends WindowTestsBase { Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window"); window.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false - WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot"); + WindowState behindWindow = newWindowBuilder("Screenshot", TYPE_SCREENSHOT).setDisplay( + display).build(); WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId); assertEquals(window, result); @@ -2317,9 +2339,10 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD }) @Test public void testComputeImeTarget_shouldNotCheckOutdatedImeTargetLayerWhenRemoved() { - final WindowState child1 = createWindow(mAppWindow, FIRST_SUB_WINDOW, "child1"); - final WindowState nextImeTargetApp = createWindow(null /* parent */, - TYPE_BASE_APPLICATION, "nextImeTargetApp"); + final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent( + mAppWindow).build(); + final WindowState nextImeTargetApp = newWindowBuilder("nextImeTargetApp", + TYPE_BASE_APPLICATION).build(); spyOn(child1); doReturn(false).when(mDisplayContent).shouldImeAttachedToApp(); mDisplayContent.setImeLayeringTarget(child1); @@ -2353,7 +2376,8 @@ public class DisplayContentTests extends WindowTestsBase { // Preparation: Simulate snapshot Task. ActivityRecord act1 = createActivityRecord(mDisplayContent); - final WindowState appWin1 = createWindow(null, TYPE_BASE_APPLICATION, act1, "appWin1"); + final WindowState appWin1 = newWindowBuilder("appWin1", + TYPE_BASE_APPLICATION).setWindowToken(act1).build(); spyOn(appWin1); spyOn(appWin1.mWinAnimator); appWin1.setHasSurface(true); @@ -2372,7 +2396,8 @@ public class DisplayContentTests extends WindowTestsBase { // Test step 2: Simulate launching appWin2 and appWin1 is in app transition. ActivityRecord act2 = createActivityRecord(mDisplayContent); - final WindowState appWin2 = createWindow(null, TYPE_BASE_APPLICATION, act2, "appWin2"); + final WindowState appWin2 = newWindowBuilder("appWin2", + TYPE_BASE_APPLICATION).setWindowToken(act2).build(); appWin2.setHasSurface(true); assertTrue(appWin2.canBeImeTarget()); doReturn(true).when(appWin1).inTransitionSelfOrParent(); @@ -2394,7 +2419,8 @@ public class DisplayContentTests extends WindowTestsBase { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); doReturn(true).when(task).okToAnimate(); ArrayList<WindowContainer> sources = new ArrayList<>(); @@ -2420,7 +2446,8 @@ public class DisplayContentTests extends WindowTestsBase { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); mDisplayContent.setImeLayeringTarget(win); mDisplayContent.setImeInputTarget(win); @@ -2446,7 +2473,8 @@ public class DisplayContentTests extends WindowTestsBase { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); win.onSurfaceShownChanged(true); makeWindowVisible(win, mDisplayContent.mInputMethodWindow); task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); @@ -2471,7 +2499,8 @@ public class DisplayContentTests extends WindowTestsBase { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); makeWindowVisible(mDisplayContent.mInputMethodWindow); mDisplayContent.setImeLayeringTarget(win); @@ -2687,7 +2716,8 @@ public class DisplayContentTests extends WindowTestsBase { public void testKeyguardGoingAwayWhileAodShown() { mDisplayContent.getDisplayPolicy().setAwake(true); - final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); + final WindowState appWin = newWindowBuilder("appWin", TYPE_APPLICATION).setDisplay( + mDisplayContent).build(); final ActivityRecord activity = appWin.mActivityRecord; mAtm.mKeyguardController.setKeyguardShown(appWin.getDisplayId(), true /* keyguardShowing */, @@ -2713,15 +2743,15 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test public void testImeChildWindowFocusWhenImeLayeringTargetChanges() { - final WindowState imeChildWindow = - createWindow(mImeWindow, TYPE_APPLICATION_ATTACHED_DIALOG, "imeChildWindow"); + final WindowState imeChildWindow = newWindowBuilder("imeChildWindow", + TYPE_APPLICATION_ATTACHED_DIALOG).setParent(mImeWindow).build(); makeWindowVisibleAndDrawn(imeChildWindow, mImeWindow); assertTrue(imeChildWindow.canReceiveKeys()); mDisplayContent.setInputMethodWindowLocked(mImeWindow); // Verify imeChildWindow can be focused window if the next IME target requests IME visible. - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); spyOn(imeAppTarget); doReturn(true).when(imeAppTarget).isRequestedVisible(ime()); @@ -2729,8 +2759,8 @@ public class DisplayContentTests extends WindowTestsBase { // Verify imeChildWindow doesn't be focused window if the next IME target does not // request IME visible. - final WindowState nextImeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "nextImeAppTarget"); + final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(nextImeAppTarget); assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow()); } @@ -2738,22 +2768,22 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() { - final WindowState imeMenuDialog = - createWindow(null, TYPE_INPUT_METHOD_DIALOG, "imeMenuDialog"); + final WindowState imeMenuDialog = newWindowBuilder("imeMenuDialog", + TYPE_INPUT_METHOD_DIALOG).build(); makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow); assertTrue(imeMenuDialog.canReceiveKeys()); mDisplayContent.setInputMethodWindowLocked(mImeWindow); // Verify imeMenuDialog can be focused window if the next IME target requests IME visible. - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); imeAppTarget.setRequestedVisibleTypes(ime()); assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); // Verify imeMenuDialog doesn't be focused window if the next IME target is closing. - final WindowState nextImeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "nextImeAppTarget"); + final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); makeWindowVisibleAndDrawn(nextImeAppTarget); // Even if the app still requests IME, the ime dialog should not gain focus if the target // app is invisible. @@ -2765,10 +2795,12 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testKeepClearAreasMultipleWindows() { - final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1"); + final WindowState w1 = newWindowBuilder("w1", TYPE_NAVIGATION_BAR).setDisplay( + mDisplayContent).build(); final Rect rect1 = new Rect(0, 0, 10, 10); w1.setKeepClearAreas(Arrays.asList(rect1), Collections.emptyList()); - final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2"); + final WindowState w2 = newWindowBuilder("w2", TYPE_NOTIFICATION_SHADE).setDisplay( + mDisplayContent).build(); final Rect rect2 = new Rect(10, 10, 20, 20); w2.setKeepClearAreas(Arrays.asList(rect2), Collections.emptyList()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 1015651438c3..ceb06497adbc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -76,7 +76,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Before public void setUp() throws Exception { - mWindow = spy(createWindow(null, TYPE_APPLICATION, "window")); + mWindow = spy(newWindowBuilder("window", TYPE_APPLICATION).build()); spyOn(mStatusBarWindow); spyOn(mNavBarWindow); @@ -147,7 +147,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { public void addingWindow_withInsetsTypes() { mDisplayPolicy.removeWindowLw(mStatusBarWindow); // Removes the existing one. - final WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "statusBar"); + final WindowState win = newWindowBuilder("statusBar", TYPE_STATUS_BAR_SUB_PANEL).build(); final Binder owner = new Binder(); win.mAttrs.providedInsets = new InsetsFrameProvider[] { new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()), diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 27d46fc4e39e..ea925c019b77 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -76,7 +76,7 @@ import org.junit.runner.RunWith; public class DisplayPolicyTests extends WindowTestsBase { private WindowState createOpaqueFullscreen(boolean hasLightNavBar) { - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "opaqueFullscreen"); + final WindowState win = newWindowBuilder("opaqueFullscreen", TYPE_BASE_APPLICATION).build(); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; @@ -99,7 +99,7 @@ public class DisplayPolicyTests extends WindowTestsBase { } private WindowState createDimmingDialogWindow(boolean canBeImTarget) { - final WindowState win = spy(createWindow(null, TYPE_APPLICATION, "dimmingDialog")); + final WindowState win = spy(newWindowBuilder("dimmingDialog", TYPE_APPLICATION).build()); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = WRAP_CONTENT; attrs.height = WRAP_CONTENT; @@ -111,7 +111,7 @@ public class DisplayPolicyTests extends WindowTestsBase { private WindowState createInputMethodWindow(boolean visible, boolean drawNavBar, boolean hasLightNavBar) { - final WindowState win = createWindow(null, TYPE_INPUT_METHOD, "inputMethod"); + final WindowState win = newWindowBuilder("inputMethod", TYPE_INPUT_METHOD).build(); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; @@ -301,7 +301,7 @@ public class DisplayPolicyTests extends WindowTestsBase { } private WindowState createApplicationWindow() { - final WindowState win = createWindow(null, TYPE_APPLICATION, "Application"); + final WindowState win = newWindowBuilder("Application", TYPE_APPLICATION).build(); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; @@ -312,7 +312,7 @@ public class DisplayPolicyTests extends WindowTestsBase { } private WindowState createBaseApplicationWindow() { - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "Application"); + final WindowState win = newWindowBuilder("Application", TYPE_BASE_APPLICATION).build(); final WindowManager.LayoutParams attrs = win.mAttrs; attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; @@ -450,7 +450,7 @@ public class DisplayPolicyTests extends WindowTestsBase { displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90, di.logicalHeight, di.logicalWidth); // Add a window that provides the same insets in current rotation. But it specifies // different insets in other rotations. - final WindowState bar2 = createWindow(null, navbar.mAttrs.type, "bar2"); + final WindowState bar2 = newWindowBuilder("bar2", navbar.mAttrs.type).build(); bar2.mAttrs.providedInsets = new InsetsFrameProvider[] { new InsetsFrameProvider(bar2, 0, WindowInsets.Type.navigationBars()) .setInsetsSize(Insets.of(0, 0, 0, NAV_BAR_HEIGHT)) diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 9602ae29604f..eb89a9fb20c5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -31,6 +31,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.window.flags.Flags.multiCrop; import static com.google.common.truth.Truth.assertThat; @@ -412,6 +413,49 @@ public class WallpaperControllerTests extends WindowTestsBase { assertFalse(token.isVisible()); } + @Test + public void testWallpaperTokenVisibilityWithTarget() { + mSetFlagsRule.enableFlags( + com.android.window.flags.Flags.FLAG_ENSURE_WALLPAPER_IN_TRANSITIONS); + final DisplayContent dc = mDisplayContent; + final WindowState wallpaperWindow = createWallpaperWindow(dc); + final WallpaperWindowToken wallpaperToken = wallpaperWindow.mToken.asWallpaperToken(); + final WindowState wallpaperTarget = createWallpaperTargetWindow(dc); + dc.mWallpaperController.adjustWallpaperWindows(); + assertEquals(wallpaperTarget, dc.mWallpaperController.getWallpaperTarget()); + assertTrue(wallpaperToken.isVisibleRequested()); + assertTrue(wallpaperToken.isVisible()); + + registerTestTransitionPlayer(); + // Assume that another activity is opening and occludes the wallpaper target activity. + Transition transition = dc.mTransitionController.createTransition(TRANSIT_OPEN); + transition.start(); + wallpaperTarget.mActivityRecord.setVisibility(false); + assertTrue(wallpaperToken.inTransition()); + waitUntilHandlersIdle(); + assertFalse("Invisible requested with target", wallpaperToken.isVisibleRequested()); + assertTrue(wallpaperToken.isVisible()); + + transition.onTransactionReady(transition.getSyncId(), mTransaction); + dc.mTransitionController.finishTransition(ActionChain.testFinish(transition)); + assertFalse(wallpaperToken.isVisibleRequested()); + assertFalse("Commit wallpaper to invisible", wallpaperToken.isVisible()); + assertTrue((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0); + dc.pendingLayoutChanges = 0; + dc.mWallpaperController.adjustWallpaperWindows(); + assertNull(dc.mWallpaperController.getWallpaperTarget()); + + // Assume that top activity is closing and the wallpaper target activity becomes visible. + transition = dc.mTransitionController.createTransition(TRANSIT_CLOSE); + transition.start(); + wallpaperTarget.mActivityRecord.setVisibility(true); + assertTrue((dc.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0); + dc.mWallpaperController.adjustWallpaperWindows(); + assertTrue(wallpaperToken.inTransition()); + assertTrue("Visible requested with target", wallpaperToken.isVisibleRequested()); + assertEquals(wallpaperTarget, dc.mWallpaperController.getWallpaperTarget()); + } + private static void prepareSmallerSecondDisplay(DisplayContent dc, int width, int height) { spyOn(dc.mWmService); DisplayInfo firstDisplay = dc.getDisplayInfo(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 8b9849e1fcd8..94c7a325cad4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -253,7 +253,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid); spyOn(session); assertTrue(session.mCanAddInternalSystemWindow); - final WindowState window = createWindow(null, LayoutParams.TYPE_PHONE, "win"); + final WindowState window = newWindowBuilder("win", LayoutParams.TYPE_PHONE).build(); session.onWindowSurfaceVisibilityChanged(window, true /* visible */); verify(session).setHasOverlayUi(true); session.onWindowSurfaceVisibilityChanged(window, false /* visible */); @@ -262,7 +262,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testRelayoutExitingWindow() { - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin"); + final WindowState win = newWindowBuilder("appWin", TYPE_BASE_APPLICATION).build(); win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class); spyOn(win.mTransitionController); @@ -396,7 +396,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { int startPrivateFlags, int newFlags, int newPrivateFlags, int expectedChangedFlags, int expectedChangedPrivateFlags, int expectedFlagsValue, int expectedPrivateFlagsValue) { - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin"); + final WindowState win = newWindowBuilder("appWin", TYPE_BASE_APPLICATION).build(); win.mRelayoutCalled = !firstRelayout; mWm.mWindowMap.put(win.mClient.asBinder(), win); spyOn(mDisplayContent.mDwpcHelper); @@ -529,7 +529,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { public void testAddWindowWithSubWindowTypeByWindowContext() { spyOn(mWm.mWindowContextListenerController); - final WindowState parentWin = createWindow(null, TYPE_INPUT_METHOD, "ime"); + final WindowState parentWin = newWindowBuilder("ime", TYPE_INPUT_METHOD).build(); final IBinder parentToken = parentWin.mToken.token; parentWin.mAttrs.token = parentToken; mWm.mWindowMap.put(parentToken, parentWin); @@ -1260,8 +1260,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { final IWindow window = mock(IWindow.class); final IBinder binder = mock(IBinder.class); doReturn(binder).when(window).asBinder(); - final WindowState windowState = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appWin", window); + final WindowState windowState = newWindowBuilder("appWin", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).setClientWindow(window).build(); doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString()); doReturn(windowState).when(mWm).getFocusedWindowLocked(); doReturn(windowState).when(mWm.mRoot).getCurrentInputMethodWindow(); 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 b61dada809d8..ce0d91264063 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -585,14 +585,6 @@ public class WindowTestsBase extends SystemServiceTestsBase { } // TODO: Move these calls to a builder? - WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name, - IWindow iwindow) { - final WindowToken token = createWindowToken( - dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type); - return createWindow(parent, type, token, name, 0 /* ownerId */, - false /* ownerCanAddInternalSystemWindow */, iwindow); - } - WindowState createWindow(WindowState parent, int type, String name) { return (parent == null) ? createWindow(parent, type, mDisplayContent, name) diff --git a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java index 0cdba83415c2..556ec1aa2246 100644 --- a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java +++ b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java @@ -223,6 +223,10 @@ public final class SatelliteSessionStats implements Parcelable { return mCountOfUserMessagesInQueueToBeSent; } + public void incrementUserMessagesInQueueToBeSent() { + mCountOfUserMessagesInQueueToBeSent++; + } + public long getLatencyOfAllSuccessfulUserMessages() { return mLatencyOfSuccessfulUserMessages; } @@ -288,6 +292,18 @@ public final class SatelliteSessionStats implements Parcelable { } } + public void updateCountOfUserMessagesInQueueToBeSent( + @SatelliteManager.DatagramType int datagramType) { + try { + datagramStats.putIfAbsent(datagramType, new SatelliteSessionStats.Builder().build()); + SatelliteSessionStats data = datagramStats.get(datagramType); + data.incrementUserMessagesInQueueToBeSent(); + } catch (Exception e) { + Log.e("SatelliteSessionStats", + "Error while addCountOfUserMessagesInQueueToBeSent: " + e.getMessage()); + } + } + public int getCountOfUnsuccessfulUserMessages(@SatelliteManager.DatagramType int datagramType) { SatelliteSessionStats data = datagramStats.get(datagramType); return data.getCountOfUnsuccessfulUserMessages(); diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java index 135f7102b8a9..9ac08ed449fd 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java @@ -203,12 +203,10 @@ public class SurfaceControlPictureProfileTest { transaction .setBuffer(mSurfaceControls[i], buffer) .setPictureProfileHandle(mSurfaceControls[i], handle) - .setContentPriority(mSurfaceControls[i], 0); + .setContentPriority(mSurfaceControls[i], 1); } - // Make the first layer low priority (high value) - transaction.setContentPriority(mSurfaceControls[0], 2); - // Make the last layer higher priority (lower value) - transaction.setContentPriority(mSurfaceControls[maxPictureProfiles], 1); + transaction.setContentPriority(mSurfaceControls[0], -1); + transaction.setContentPriority(mSurfaceControls[maxPictureProfiles], 0); transaction.apply(); pictures = pollMs(picturesQueue, 200); @@ -219,8 +217,8 @@ public class SurfaceControlPictureProfileTest { assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId())) .containsExactlyElementsIn(toIterableRange(2, maxPictureProfiles + 1)); - // Change priority and ensure that the first layer gets access - new SurfaceControl.Transaction().setContentPriority(mSurfaceControls[0], 0).apply(); + // Elevate priority for the first layer and verify it gets to use a profile + new SurfaceControl.Transaction().setContentPriority(mSurfaceControls[0], 2).apply(); pictures = pollMs(picturesQueue, 200); assertThat(pictures).isNotNull(); // Expect all but the last layer to be listed as an active picture diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt index c1c5dc66bac1..2db8b1e18ec8 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt @@ -28,6 +28,10 @@ import android.tools.device.apphelpers.IStandardAppHelper import android.tools.helpers.SYSTEMUI_PACKAGE import android.tools.traces.parsers.WindowManagerStateHelper import android.tools.traces.wm.WindowingMode +import android.view.KeyEvent.KEYCODE_LEFT_BRACKET +import android.view.KeyEvent.KEYCODE_MINUS +import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET +import android.view.KeyEvent.META_META_ON import android.view.WindowInsets import android.view.WindowManager import android.window.DesktopModeFlags @@ -157,10 +161,21 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n") } - fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice, isPip: Boolean = false) { - val caption = getCaptionForTheApp(wmHelper, device) - val minimizeButton = getMinimizeButtonForTheApp(caption) - minimizeButton.click() + fun minimizeDesktopApp( + wmHelper: WindowManagerStateHelper, + device: UiDevice, + isPip: Boolean = false, + usingKeyboard: Boolean = false, + ) { + if (usingKeyboard) { + val keyEventHelper = KeyEventHelper(getInstrumentation()) + keyEventHelper.press(KEYCODE_MINUS, META_META_ON) + } else { + val caption = getCaptionForTheApp(wmHelper, device) + val minimizeButton = getMinimizeButtonForTheApp(caption) + minimizeButton.click() + } + wmHelper .StateSyncBuilder() .withAppTransitionIdle() @@ -213,6 +228,25 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : ?: error("Unable to find object with resource id $buttonResId") snapResizeButton.click() + waitAndVerifySnapResize(wmHelper, context, toLeft) + } + + fun snapResizeWithKeyboard( + wmHelper: WindowManagerStateHelper, + context: Context, + keyEventHelper: KeyEventHelper, + toLeft: Boolean, + ) { + val bracketKey = if (toLeft) KEYCODE_LEFT_BRACKET else KEYCODE_RIGHT_BRACKET + keyEventHelper.press(bracketKey, META_META_ON) + waitAndVerifySnapResize(wmHelper, context, toLeft) + } + + private fun waitAndVerifySnapResize( + wmHelper: WindowManagerStateHelper, + context: Context, + toLeft: Boolean + ) { val displayRect = getDisplayRect(wmHelper) val insets = getWindowInsets( context, WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars() diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt new file mode 100644 index 000000000000..55ed09154aee --- /dev/null +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.os.SystemClock +import android.view.KeyEvent.ACTION_DOWN +import android.view.KeyEvent.ACTION_UP +import android.view.KeyEvent + +/** + * Helper class for injecting a custom key event. This is used for instrumenting keyboard shortcut + * actions. + */ +class KeyEventHelper( + private val instr: Instrumentation, +) { + fun press(keyCode: Int, metaState: Int = 0) { + actionDown(keyCode, metaState) + actionUp(keyCode, metaState) + } + + fun actionDown(keyCode: Int, metaState: Int = 0, time: Long = SystemClock.uptimeMillis()) { + injectKeyEvent(ACTION_DOWN, keyCode, metaState, downTime = time, eventTime = time) + } + + fun actionUp(keyCode: Int, metaState: Int = 0, time: Long = SystemClock.uptimeMillis()) { + injectKeyEvent(ACTION_UP, keyCode, metaState, downTime = time, eventTime = time) + } + + private fun injectKeyEvent( + action: Int, + keyCode: Int, + metaState: Int = 0, + downTime: Long = SystemClock.uptimeMillis(), + eventTime: Long = SystemClock.uptimeMillis() + ): KeyEvent { + val event = KeyEvent(downTime, eventTime, action, keyCode, /* repeat= */ 0, metaState) + injectKeyEvent(event) + return event + } + + private fun injectKeyEvent(event: KeyEvent) { + instr.uiAutomation.injectInputEvent(event, true) + } +}
\ No newline at end of file diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index fafb0e0f75c8..4959cb3651fd 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -782,6 +782,54 @@ class KeyGestureControllerTests { KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), + TestData( + "META + ALT + 'Down' -> Magnification Pan Down", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_DPAD_DOWN + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_DOWN, + intArrayOf(KeyEvent.KEYCODE_DPAD_DOWN), + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ALT + 'Up' -> Magnification Pan Up", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_DPAD_UP + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_UP, + intArrayOf(KeyEvent.KEYCODE_DPAD_UP), + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ALT + 'Left' -> Magnification Pan Left", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_DPAD_LEFT + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_LEFT, + intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT), + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ALT + 'Right' -> Magnification Pan Right", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_DPAD_RIGHT + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_PAN_RIGHT, + intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT), + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), ) } diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index c64dc7296f0a..928e2326498d 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -19,6 +19,7 @@ package com.android.server; import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS; import static com.google.common.truth.Truth.assertThat; @@ -1933,12 +1934,12 @@ public class PackageWatchdogTest { return mImpact; } - public boolean onExecuteHealthCheckMitigation(VersionedPackage versionedPackage, + public int onExecuteHealthCheckMitigation(VersionedPackage versionedPackage, int failureReason, int mitigationCount) { mMitigatedPackages.add(versionedPackage.getPackageName()); mMitigationCounts.add(mitigationCount); mLastFailureReason = failureReason; - return true; + return MITIGATION_RESULT_SUCCESS; } public String getUniqueIdentifier() { @@ -1957,11 +1958,10 @@ public class PackageWatchdogTest { return mImpact; } - public boolean onExecuteBootLoopMitigation(int level) { - Slog.w("hrm1243", "I'm here " + level); + public int onExecuteBootLoopMitigation(int level) { mMitigatedBootLoop = true; mBootMitigationCounts.add(level); - return true; + return MITIGATION_RESULT_SUCCESS; } public boolean mitigatedBootLoop() { diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp index 44aa4028c916..370c0048d9a9 100644 --- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp +++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp @@ -38,6 +38,9 @@ android_test { ], test_suites: [ "general-tests", + // This is an equivalent of general-tests for automotive. + // It helps manage the build time on automotive branches. + "automotive-general-tests", ], sdk_version: "test_current", diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index 5e0f87f0dcaf..60c4bf5c4131 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -292,13 +292,12 @@ bool processFile(Bundle* bundle, ZipFile* zip, } if (!hasData) { const String8& srcName = file->getSourceFile(); - time_t fileModWhen; - fileModWhen = getFileModDate(srcName.c_str()); - if (fileModWhen == (time_t) -1) { // file existence tested earlier, - return false; // not expecting an error here + auto fileModWhen = getFileModDate(srcName.c_str()); + if (fileModWhen == kInvalidModDate) { // file existence tested earlier, + return false; // not expecting an error here } - - if (fileModWhen > entry->getModWhen()) { + + if (toTimeT(fileModWhen) > entry->getModWhen()) { // mark as deleted so add() will succeed if (bundle->getVerbose()) { printf(" (removing old '%s')\n", storageName.c_str()); |