diff options
546 files changed, 12528 insertions, 7910 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 26fbd270eb46..497619ae0613 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -348,6 +348,7 @@ java_aconfig_library { aconfig_declarations { name: "android.security.flags-aconfig", package: "android.security", + exportable: true, container: "system", srcs: ["core/java/android/security/*.aconfig"], } @@ -365,6 +366,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.security.flags-aconfig-java-export", + aconfig_declarations: "android.security.flags-aconfig", + mode: "exported", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + cc_aconfig_library { name: "android_security_flags_aconfig_c_lib", aconfig_declarations: "android.security.flags-aconfig", diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig index 79aef1e6a19a..47a85498f51b 100644 --- a/apex/jobscheduler/framework/aconfig/job.aconfig +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -45,3 +45,11 @@ flag { description: "Introduce a new getPendingJobReasons() API which returns reasons why a job may not have executed. Also deprecate the existing getPendingJobReason() API." bug: "372031023" } + +flag { + name: "get_pending_job_reasons_history_api" + is_exported: true + namespace: "backstage_power" + description: "Introduce a new getPendingJobReasonsHistory() API which returns a limited historical view of getPendingJobReasons()." + bug: "372031023" +} diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java index 3cfddc6d8e2b..fb5ef8771c26 100644 --- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java +++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java @@ -173,6 +173,16 @@ public class JobSchedulerImpl extends JobScheduler { } @Override + @NonNull + public int[] getPendingJobReasons(int jobId) { + try { + return mBinder.getPendingJobReasons(mNamespace, jobId); + } catch (RemoteException e) { + return new int[] { PENDING_JOB_REASON_UNDEFINED }; + } + } + + @Override public boolean canRunUserInitiatedJobs() { try { return mBinder.canRunUserInitiatedJobs(mContext.getOpPackageName()); diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl index 416a2d8c0002..21051b520d84 100644 --- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl +++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl @@ -39,6 +39,7 @@ interface IJobScheduler { ParceledListSlice<JobInfo> getAllPendingJobsInNamespace(String namespace); JobInfo getPendingJob(String namespace, int jobId); int getPendingJobReason(String namespace, int jobId); + int[] getPendingJobReasons(String namespace, int jobId); boolean canRunUserInitiatedJobs(String packageName); boolean hasRunUserInitiatedJobsPermission(String packageName, int userId); List<JobInfo> getStartedJobs(); diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index ad54cd397413..bfdd15e9b0cd 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -16,6 +16,7 @@ package android.app.job; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -238,6 +239,13 @@ public abstract class JobScheduler { * to defer this job. */ public static final int PENDING_JOB_REASON_USER = 15; + /** + * The override deadline has not transpired. + * + * @see JobInfo.Builder#setOverrideDeadline(long) + */ + @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_API) + public static final int PENDING_JOB_REASON_CONSTRAINT_DEADLINE = 16; /** @hide */ @IntDef(prefix = {"PENDING_JOB_REASON_"}, value = { @@ -259,6 +267,7 @@ public abstract class JobScheduler { PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION, PENDING_JOB_REASON_QUOTA, PENDING_JOB_REASON_USER, + PENDING_JOB_REASON_CONSTRAINT_DEADLINE, }) @Retention(RetentionPolicy.SOURCE) public @interface PendingJobReason { @@ -458,6 +467,10 @@ public abstract class JobScheduler { /** * Returns a reason why the job is pending and not currently executing. If there are multiple * reasons why a job may be pending, this will only return one of them. + * + * @apiNote + * To know all the potential reasons why the job may be pending, + * use {@link #getPendingJobReasons(int)} instead. */ @PendingJobReason public int getPendingJobReason(int jobId) { @@ -465,6 +478,21 @@ public abstract class JobScheduler { } /** + * Returns potential reasons why the job with the given {@code jobId} may be pending + * and not currently executing. + * + * The returned array will include {@link PendingJobReason reasons} composed of both + * explicitly set constraints on the job and implicit constraints imposed by the system. + * The results can be used to debug why a given job may not be currently executing. + */ + @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_API) + @NonNull + @PendingJobReason + public int[] getPendingJobReasons(int jobId) { + return new int[] { PENDING_JOB_REASON_UNDEFINED }; + } + + /** * Returns {@code true} if the calling app currently holds the * {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission, allowing it to run * user-initiated jobs. diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig index c4d0d1850a18..426031fbeb9c 100644 --- a/apex/jobscheduler/service/aconfig/device_idle.aconfig +++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig @@ -17,3 +17,13 @@ flag { description: "Disable wakelocks for background apps while Light Device Idle is active" bug: "326607666" } + +flag { + name: "use_cpu_time_for_temp_allowlist" + namespace: "backstage_power" + description: "Use CPU time for temporary allowlists" + bug: "376561328" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 3e650da2e66f..41fd4a29cfd1 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -620,8 +620,8 @@ public class DeviceIdleController extends SystemService * the network and acquire wakelocks. Times are in milliseconds. */ @GuardedBy("this") - private final SparseArray<Pair<MutableLong, String>> mTempWhitelistAppIdEndTimes - = new SparseArray<>(); + @VisibleForTesting + final SparseArray<Pair<MutableLong, String>> mTempWhitelistAppIdEndTimes = new SparseArray<>(); private NetworkPolicyManagerInternal mNetworkPolicyManagerInternal; @@ -1941,7 +1941,8 @@ public class DeviceIdleController extends SystemService private static final int MSG_REPORT_IDLE_ON_LIGHT = 3; private static final int MSG_REPORT_IDLE_OFF = 4; private static final int MSG_REPORT_ACTIVE = 5; - private static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6; + @VisibleForTesting + static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6; @VisibleForTesting static final int MSG_REPORT_STATIONARY_STATUS = 7; private static final int MSG_FINISH_IDLE_OP = 8; @@ -2511,6 +2512,11 @@ public class DeviceIdleController extends SystemService return SystemClock.elapsedRealtime(); } + /** Returns the current elapsed realtime in milliseconds. */ + long getUptimeMillis() { + return SystemClock.uptimeMillis(); + } + LocationManager getLocationManager() { if (mLocationManager == null) { mLocationManager = mContext.getSystemService(LocationManager.class); @@ -3264,7 +3270,8 @@ public class DeviceIdleController extends SystemService void addPowerSaveTempWhitelistAppDirectInternal(int callingUid, int uid, long duration, @TempAllowListType int tempAllowListType, boolean sync, @ReasonCode int reasonCode, @Nullable String reason) { - final long timeNow = SystemClock.elapsedRealtime(); + final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis() + : mInjector.getElapsedRealtime(); boolean informWhitelistChanged = false; int appId = UserHandle.getAppId(uid); synchronized (this) { @@ -3350,7 +3357,8 @@ public class DeviceIdleController extends SystemService } void checkTempAppWhitelistTimeout(int uid) { - final long timeNow = SystemClock.elapsedRealtime(); + final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis() + : mInjector.getElapsedRealtime(); final int appId = UserHandle.getAppId(uid); if (DEBUG) { Slog.d(TAG, "checkTempAppWhitelistTimeout: uid=" + uid + ", timeNow=" + timeNow); @@ -5219,6 +5227,17 @@ public class DeviceIdleController extends SystemService } } + pw.println(" Flags:"); + pw.print(" "); + pw.print(Flags.FLAG_USE_CPU_TIME_FOR_TEMP_ALLOWLIST); + pw.print("="); + pw.println(Flags.useCpuTimeForTempAllowlist()); + pw.print(" "); + pw.print(Flags.FLAG_REMOVE_IDLE_LOCATION); + pw.print("="); + pw.println(Flags.removeIdleLocation()); + pw.println(); + synchronized (this) { mConstants.dump(pw); @@ -5449,7 +5468,8 @@ public class DeviceIdleController extends SystemService pw.println(" Temp whitelist schedule:"); prefix = " "; } - final long timeNow = SystemClock.elapsedRealtime(); + final long timeNow = Flags.useCpuTimeForTempAllowlist() ? mInjector.getUptimeMillis() + : mInjector.getElapsedRealtime(); for (int i = 0; i < size; i++) { pw.print(prefix); pw.print("UID="); 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 ba8e3e8b48fc..8f44698ffd8d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1550,7 +1550,7 @@ class JobConcurrencyManager { mActivePkgStats.add( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats); - mService.resetPendingJobReasonCache(jobStatus); + mService.resetPendingJobReasonsCache(jobStatus); } if (mService.getPendingJobQueue().remove(jobStatus)) { mService.mJobPackageTracker.noteNonpending(jobStatus); 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 4c1951ae9d39..f569388ef3c1 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -460,10 +460,10 @@ public class JobSchedulerService extends com.android.server.SystemService private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>(); /** - * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reason. + * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reasons. */ - @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing - private final SparseArrayMap<String, SparseIntArray> mPendingJobReasonCache = + @GuardedBy("mPendingJobReasonsCache") // Use its own lock to avoid blocking JS processing + private final SparseArrayMap<String, SparseArray<int[]>> mPendingJobReasonsCache = new SparseArrayMap<>(); /** @@ -2021,139 +2021,123 @@ public class JobSchedulerService extends com.android.server.SystemService } } - @JobScheduler.PendingJobReason - private int getPendingJobReason(int uid, String namespace, int jobId) { - int reason; + @NonNull + private int[] getPendingJobReasons(int uid, String namespace, int jobId) { + int[] reasons; // Some apps may attempt to query this frequently, so cache the reason under a separate lock // so that the rest of JS processing isn't negatively impacted. - synchronized (mPendingJobReasonCache) { - SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace); - if (jobIdToReason != null) { - reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED); - if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) { - return reason; + synchronized (mPendingJobReasonsCache) { + SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace); + if (jobIdToReasons != null) { + reasons = jobIdToReasons.get(jobId); + if (reasons != null) { + return reasons; } } } synchronized (mLock) { - reason = getPendingJobReasonLocked(uid, namespace, jobId); + reasons = getPendingJobReasonsLocked(uid, namespace, jobId); if (DEBUG) { - Slog.v(TAG, "getPendingJobReason(" - + uid + "," + namespace + "," + jobId + ")=" + reason); + Slog.v(TAG, "getPendingJobReasons(" + + uid + "," + namespace + "," + jobId + ")=" + Arrays.toString(reasons)); } } - synchronized (mPendingJobReasonCache) { - SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace); - if (jobIdToReason == null) { - jobIdToReason = new SparseIntArray(); - mPendingJobReasonCache.add(uid, namespace, jobIdToReason); + synchronized (mPendingJobReasonsCache) { + SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace); + if (jobIdToReasons == null) { + jobIdToReasons = new SparseArray<>(); + mPendingJobReasonsCache.add(uid, namespace, jobIdToReasons); } - jobIdToReason.put(jobId, reason); + jobIdToReasons.put(jobId, reasons); } - return reason; + return reasons; } @VisibleForTesting @JobScheduler.PendingJobReason int getPendingJobReason(JobStatus job) { - return getPendingJobReason(job.getUid(), job.getNamespace(), job.getJobId()); + // keep original method to enable unit testing with flags + return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId())[0]; + } + + @VisibleForTesting + @NonNull + int[] getPendingJobReasons(JobStatus job) { + return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId()); } - @JobScheduler.PendingJobReason @GuardedBy("mLock") - private int getPendingJobReasonLocked(int uid, String namespace, int jobId) { + @NonNull + private int[] getPendingJobReasonsLocked(int uid, String namespace, int jobId) { // Very similar code to isReadyToBeExecutedLocked. - - JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId); + final JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId); if (job == null) { // Job doesn't exist. - return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID; + return new int[] { JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID }; } - if (isCurrentlyRunningLocked(job)) { - return JobScheduler.PENDING_JOB_REASON_EXECUTING; + return new int[] { JobScheduler.PENDING_JOB_REASON_EXECUTING }; } + final String debugPrefix = "getPendingJobReasonsLocked: " + job.toShortString(); final boolean jobReady = job.isReady(); - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " ready=" + jobReady); + Slog.v(TAG, debugPrefix + " ready=" + jobReady); } - if (!jobReady) { - return job.getPendingJobReason(); + return job.getPendingJobReasons(); } final boolean userStarted = areUsersStartedLocked(job); - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " userStarted=" + userStarted); + Slog.v(TAG, debugPrefix + " userStarted=" + userStarted); } if (!userStarted) { - return JobScheduler.PENDING_JOB_REASON_USER; + return new int[] { JobScheduler.PENDING_JOB_REASON_USER }; } final boolean backingUp = mBackingUpUids.get(job.getSourceUid()); if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " backingUp=" + backingUp); + Slog.v(TAG, debugPrefix + " backingUp=" + backingUp); } - if (backingUp) { // TODO: Should we make a special reason for this? - return JobScheduler.PENDING_JOB_REASON_APP; + return new int[] { JobScheduler.PENDING_JOB_REASON_APP }; } - JobRestriction restriction = checkIfRestricted(job); + final JobRestriction restriction = checkIfRestricted(job); if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " restriction=" + restriction); + Slog.v(TAG, debugPrefix + " restriction=" + restriction); } if (restriction != null) { - return restriction.getPendingReason(); + // Currently this will return _DEVICE_STATE because of thermal reasons. + // TODO (b/372031023): does it make sense to move this along with the + // pendingJobReasons() call above and also get the pending reasons from + // all of the restriction controllers? + return new int[] { restriction.getPendingReason() }; } - // The following can be a little more expensive (especially jobActive, since we need to - // go through the array of all potentially active jobs), so we are doing them - // later... but still before checking with the package manager! + // The following can be a little more expensive, so we are doing it later, + // but still before checking with the package manager! final boolean jobPending = mPendingJobQueue.contains(job); - - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " pending=" + jobPending); + Slog.v(TAG, debugPrefix + " pending=" + jobPending); } - if (jobPending) { - // We haven't started the job for some reason. Presumably, there are too many jobs - // running. - return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE; - } - - final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job); - - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " active=" + jobActive); - } - if (jobActive) { - return JobScheduler.PENDING_JOB_REASON_UNDEFINED; + // We haven't started the job - presumably, there are too many jobs running. + return new int[] { JobScheduler.PENDING_JOB_REASON_DEVICE_STATE }; } // Validate that the defined package+service is still present & viable. final boolean componentUsable = isComponentUsable(job); - if (DEBUG) { - Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString() - + " componentUsable=" + componentUsable); + Slog.v(TAG, debugPrefix + " componentUsable=" + componentUsable); } if (!componentUsable) { - return JobScheduler.PENDING_JOB_REASON_APP; + return new int[] { JobScheduler.PENDING_JOB_REASON_APP }; } - return JobScheduler.PENDING_JOB_REASON_UNDEFINED; + return new int[] { JobScheduler.PENDING_JOB_REASON_UNDEFINED }; } private JobInfo getPendingJob(int uid, @Nullable String namespace, int jobId) { @@ -2195,15 +2179,16 @@ public class JobSchedulerService extends com.android.server.SystemService // The app process will be killed soon. There's no point keeping its jobs in // the pending queue to try and start them. if (mPendingJobQueue.remove(job)) { - synchronized (mPendingJobReasonCache) { - SparseIntArray jobIdToReason = mPendingJobReasonCache.get( + synchronized (mPendingJobReasonsCache) { + SparseArray<int[]> jobIdToReason = mPendingJobReasonsCache.get( job.getUid(), job.getNamespace()); if (jobIdToReason == null) { - jobIdToReason = new SparseIntArray(); - mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), + jobIdToReason = new SparseArray<>(); + mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(), jobIdToReason); } - jobIdToReason.put(job.getJobId(), JobScheduler.PENDING_JOB_REASON_USER); + jobIdToReason.put(job.getJobId(), + new int[] { JobScheduler.PENDING_JOB_REASON_USER }); } } } @@ -2229,8 +2214,8 @@ public class JobSchedulerService extends com.android.server.SystemService synchronized (mLock) { mJobs.removeJobsOfUnlistedUsers(umi.getUserIds()); } - synchronized (mPendingJobReasonCache) { - mPendingJobReasonCache.clear(); + synchronized (mPendingJobReasonsCache) { + mPendingJobReasonsCache.clear(); } } @@ -2875,7 +2860,7 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean update = lastJob != null; mJobs.add(jobStatus); // Clear potentially cached INVALID_JOB_ID reason. - resetPendingJobReasonCache(jobStatus); + resetPendingJobReasonsCache(jobStatus); if (mReadyToRock) { for (int i = 0; i < mControllers.size(); i++) { StateController controller = mControllers.get(i); @@ -2897,9 +2882,9 @@ public class JobSchedulerService extends com.android.server.SystemService // Deal with any remaining work items in the old job. jobStatus.stopTrackingJobLocked(incomingJob); - synchronized (mPendingJobReasonCache) { - SparseIntArray reasonCache = - mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace()); + synchronized (mPendingJobReasonsCache) { + SparseArray<int[]> reasonCache = + mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace()); if (reasonCache != null) { reasonCache.delete(jobStatus.getJobId()); } @@ -2927,11 +2912,11 @@ public class JobSchedulerService extends com.android.server.SystemService return removed; } - /** Remove the pending job reason for this job from the cache. */ - void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) { - synchronized (mPendingJobReasonCache) { - final SparseIntArray reasons = - mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace()); + /** Remove the pending job reasons for this job from the cache. */ + void resetPendingJobReasonsCache(@NonNull JobStatus jobStatus) { + synchronized (mPendingJobReasonsCache) { + final SparseArray<int[]> reasons = + mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace()); if (reasons != null) { reasons.delete(jobStatus.getJobId()); } @@ -3313,18 +3298,18 @@ public class JobSchedulerService extends com.android.server.SystemService public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) { if (changedJobs == null) { mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); - synchronized (mPendingJobReasonCache) { - mPendingJobReasonCache.clear(); + synchronized (mPendingJobReasonsCache) { + mPendingJobReasonsCache.clear(); } } else if (changedJobs.size() > 0) { synchronized (mLock) { mChangedJobList.addAll(changedJobs); } mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget(); - synchronized (mPendingJobReasonCache) { + synchronized (mPendingJobReasonsCache) { for (int i = changedJobs.size() - 1; i >= 0; --i) { final JobStatus job = changedJobs.valueAt(i); - resetPendingJobReasonCache(job); + resetPendingJobReasonsCache(job); } } } @@ -3893,23 +3878,21 @@ public class JobSchedulerService extends com.android.server.SystemService // Update the pending reason for any jobs that aren't going to be run. final int numRunnableJobs = runnableJobs.size(); if (numRunnableJobs > 0 && numRunnableJobs != jobsToRun.size()) { - synchronized (mPendingJobReasonCache) { + synchronized (mPendingJobReasonsCache) { for (int i = 0; i < numRunnableJobs; ++i) { final JobStatus job = runnableJobs.get(i); if (jobsToRun.contains(job)) { - // We're running this job. Skip updating the pending reason. - continue; + continue; // we're running this job - skip updating the pending reason. } - SparseIntArray reasons = - mPendingJobReasonCache.get(job.getUid(), job.getNamespace()); + SparseArray<int[]> reasons = + mPendingJobReasonsCache.get(job.getUid(), job.getNamespace()); if (reasons == null) { - reasons = new SparseIntArray(); - mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), reasons); + reasons = new SparseArray<>(); + mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(), reasons); } - // We're force batching these jobs, so consider it an optimization - // policy reason. - reasons.put(job.getJobId(), - JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION); + // we're force batching these jobs - note it as optimization. + reasons.put(job.getJobId(), new int[] + { JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION }); } } } @@ -5123,12 +5106,16 @@ public class JobSchedulerService extends com.android.server.SystemService @Override public int getPendingJobReason(String namespace, int jobId) throws RemoteException { - final int uid = Binder.getCallingUid(); + return getPendingJobReasons(validateNamespace(namespace), jobId)[0]; + } + @Override + public int[] getPendingJobReasons(String namespace, int jobId) throws RemoteException { + final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - return JobSchedulerService.this.getPendingJobReason( - uid, validateNamespace(namespace), jobId); + return JobSchedulerService.this.getPendingJobReasons( + uid, validateNamespace(namespace), jobId); } finally { Binder.restoreCallingIdentity(ident); } @@ -5867,6 +5854,9 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND, android.app.job.Flags.ignoreImportantWhileForeground()); pw.println(); + pw.print(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API, + android.app.job.Flags.getPendingJobReasonsApi()); + pw.println(); pw.decreaseIndent(); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index 68303e86055c..a4a302450849 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -439,6 +439,9 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { case android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND: pw.println(android.app.job.Flags.ignoreImportantWhileForeground()); break; + case android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API: + pw.println(android.app.job.Flags.getPendingJobReasonsApi()); + break; default: pw.println("Unknown flag: " + flagName); break; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 1dc5a714337c..58579eb0db47 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -2067,12 +2067,11 @@ public final class JobStatus { } /** - * If {@link #isReady()} returns false, this will return a single reason why the job isn't - * ready. If {@link #isReady()} returns true, this will return - * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}. + * This will return all potential reasons why the job is pending. */ - @JobScheduler.PendingJobReason - public int getPendingJobReason() { + @NonNull + public int[] getPendingJobReasons() { + final ArrayList<Integer> reasons = new ArrayList<>(); final int unsatisfiedConstraints = ~satisfiedConstraints & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS); if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) { @@ -2084,78 +2083,99 @@ public final class JobStatus { // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of // battery saver state. if (mIsUserBgRestricted) { - return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION); + } else { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE); + } + } + if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) { + if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE)) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE); } - return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE; } + if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) { if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) { // The developer requested this constraint, so it makes sense to return the // explicit constraint reason. - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW); + } else { + // Hard-coding right now since the current dynamic constraint sets don't overlap + // TODO: return based on active dynamic constraint sets when they start overlapping + reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY); } - // Hard-coding right now since the current dynamic constraint sets don't overlap - // TODO: return based on active dynamic constraint sets when they start overlapping - return JobScheduler.PENDING_JOB_REASON_APP_STANDBY; } if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) { if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) { // The developer requested this constraint, so it makes sense to return the // explicit constraint reason. - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING); + } else { + // Hard-coding right now since the current dynamic constraint sets don't overlap + // TODO: return based on active dynamic constraint sets when they start overlapping + if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY); + } } - // Hard-coding right now since the current dynamic constraint sets don't overlap - // TODO: return based on active dynamic constraint sets when they start overlapping - return JobScheduler.PENDING_JOB_REASON_APP_STANDBY; - } - if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY; - } - if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER; - } - if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE; - } - if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION; } if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) { if ((CONSTRAINT_IDLE & requiredConstraints) != 0) { // The developer requested this constraint, so it makes sense to return the // explicit constraint reason. - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE); + } else { + // Hard-coding right now since the current dynamic constraint sets don't overlap + // TODO: return based on active dynamic constraint sets when they start overlapping + if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY); + } } - // Hard-coding right now since the current dynamic constraint sets don't overlap - // TODO: return based on active dynamic constraint sets when they start overlapping - return JobScheduler.PENDING_JOB_REASON_APP_STANDBY; + } + + if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY); + } + if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER); + } + if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION); } if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH); } if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW); } if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY); } if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) { - return JobScheduler.PENDING_JOB_REASON_QUOTA; + reasons.addLast(JobScheduler.PENDING_JOB_REASON_QUOTA); } - - if (getEffectiveStandbyBucket() == NEVER_INDEX) { - Slog.wtf(TAG, "App in NEVER bucket querying pending job reason"); - // The user hasn't officially launched this app. - return JobScheduler.PENDING_JOB_REASON_USER; + if (android.app.job.Flags.getPendingJobReasonsApi()) { + if ((CONSTRAINT_DEADLINE & unsatisfiedConstraints) != 0) { + reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEADLINE); + } } - if (serviceProcessName != null) { - return JobScheduler.PENDING_JOB_REASON_APP; + + if (reasons.isEmpty()) { + if (getEffectiveStandbyBucket() == NEVER_INDEX) { + Slog.wtf(TAG, "App in NEVER bucket querying pending job reason"); + // The user hasn't officially launched this app. + reasons.add(JobScheduler.PENDING_JOB_REASON_USER); + } else if (serviceProcessName != null) { + reasons.add(JobScheduler.PENDING_JOB_REASON_APP); + } else { + reasons.add(JobScheduler.PENDING_JOB_REASON_UNDEFINED); + } } - if (!isReady()) { - Slog.wtf(TAG, "Unknown reason job isn't ready"); + final int[] reasonsArr = new int[reasons.size()]; + for (int i = 0; i < reasonsArr.length; i++) { + reasonsArr[i] = reasons.get(i); } - return JobScheduler.PENDING_JOB_REASON_UNDEFINED; + return reasonsArr; } /** @return whether or not the @param constraint is satisfied */ diff --git a/api/Android.bp b/api/Android.bp index ff674c7f8bd3..0ac85e28de1a 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -73,6 +73,7 @@ combined_apis { "framework-bluetooth", "framework-configinfrastructure", "framework-connectivity", + "framework-connectivity-b", "framework-connectivity-t", "framework-devicelock", "framework-graphics", diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist index a413bbd68f60..16f069394639 100644 --- a/config/preloaded-classes-denylist +++ b/config/preloaded-classes-denylist @@ -1,13 +1,16 @@ android.content.AsyncTaskLoader$LoadTask +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 android.widget.Magnifier +com.android.internal.jank.InteractionJankMonitor$InstanceHolder +com.android.internal.util.LatencyTracker$SLatencyTrackerHolder gov.nist.core.net.DefaultNetworkLayer -android.net.rtp.AudioGroup -android.net.rtp.AudioStream -android.net.rtp.RtpStream java.util.concurrent.ThreadLocalRandom java.util.ImmutableCollections -com.android.internal.jank.InteractionJankMonitor$InstanceHolder +sun.nio.fs.UnixChannelFactory diff --git a/core/api/current.txt b/core/api/current.txt index d9ab273888db..15eebbe2be0e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -241,6 +241,7 @@ package android { field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS"; field @FlaggedApi("android.security.aapm_api") public static final String QUERY_ADVANCED_PROTECTION_MODE = "android.permission.QUERY_ADVANCED_PROTECTION_MODE"; field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES"; + field @FlaggedApi("android.permission.flags.ranging_permission_enabled") public static final String RANGING = "android.permission.RANGING"; field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA"; field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE"; field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR"; @@ -1074,6 +1075,7 @@ package android { field public static final int layout = 16842994; // 0x10100f2 field public static final int layoutAnimation = 16842988; // 0x10100ec field public static final int layoutDirection = 16843698; // 0x10103b2 + field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel; field public static final int layoutMode = 16843738; // 0x10103da field public static final int layout_above = 16843140; // 0x1010184 field public static final int layout_alignBaseline = 16843142; // 0x1010186 @@ -8023,6 +8025,7 @@ package android.app.admin { field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String CONTENT_PROTECTION_POLICY = "contentProtection"; field public static final String KEYGUARD_DISABLED_FEATURES_POLICY = "keyguardDisabledFeatures"; field public static final String LOCK_TASK_POLICY = "lockTask"; + field @FlaggedApi("android.app.admin.flags.set_mte_policy_coexistence") public static final String MEMORY_TAGGING_POLICY = "memoryTagging"; field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended"; field public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked"; field public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity"; @@ -9245,6 +9248,7 @@ package android.app.job { method @Nullable public String getNamespace(); method @Nullable public abstract android.app.job.JobInfo getPendingJob(int); method public int getPendingJobReason(int); + method @FlaggedApi("android.app.job.get_pending_job_reasons_api") @NonNull public int[] getPendingJobReasons(int); method @NonNull public java.util.Map<java.lang.String,java.util.List<android.app.job.JobInfo>> getPendingJobsInAllNamespaces(); method public abstract int schedule(@NonNull android.app.job.JobInfo); field public static final int PENDING_JOB_REASON_APP = 1; // 0x1 @@ -9254,6 +9258,7 @@ package android.app.job { field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5 field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6 field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7 + field @FlaggedApi("android.app.job.get_pending_job_reasons_api") public static final int PENDING_JOB_REASON_CONSTRAINT_DEADLINE = 16; // 0x10 field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8 field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9 field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa @@ -9687,10 +9692,8 @@ package android.app.wallpaper { method @Nullable public String getId(); method @Nullable public android.net.Uri getThumbnail(); method @Nullable public CharSequence getTitle(); - method @NonNull public static android.app.wallpaper.WallpaperDescription readFromStream(@NonNull java.io.InputStream) throws java.io.IOException; method @NonNull public android.app.wallpaper.WallpaperDescription.Builder toBuilder(); method public void writeToParcel(@NonNull android.os.Parcel, int); - method public void writeToStream(@NonNull java.io.OutputStream) throws java.io.IOException; field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperDescription> CREATOR; } @@ -12732,6 +12735,7 @@ package android.content.pm { method public abstract void onPackagesUnavailable(String[], android.os.UserHandle, boolean); method public void onPackagesUnsuspended(String[], android.os.UserHandle); method public void onShortcutsChanged(@NonNull String, @NonNull java.util.List<android.content.pm.ShortcutInfo>, @NonNull android.os.UserHandle); + method @FlaggedApi("android.multiuser.add_launcher_user_config") public void onUserConfigChanged(@NonNull android.content.pm.LauncherUserInfo); } public static final class LauncherApps.PinItemRequest implements android.os.Parcelable { @@ -12767,10 +12771,12 @@ package android.content.pm { @FlaggedApi("android.os.allow_private_profile") public final class LauncherUserInfo implements android.os.Parcelable { method @FlaggedApi("android.os.allow_private_profile") public int describeContents(); + method @FlaggedApi("android.multiuser.add_launcher_user_config") @NonNull public android.os.Bundle getUserConfig(); method @FlaggedApi("android.os.allow_private_profile") public int getUserSerialNumber(); method @FlaggedApi("android.os.allow_private_profile") @NonNull public String getUserType(); method @FlaggedApi("android.os.allow_private_profile") public void writeToParcel(@NonNull android.os.Parcel, int); field @FlaggedApi("android.os.allow_private_profile") @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherUserInfo> CREATOR; + field @FlaggedApi("android.multiuser.add_launcher_user_config") public static final String PRIVATE_SPACE_ENTRYPOINT_HIDDEN = "private_space_entrypoint_hidden"; } public final class ModuleInfo implements android.os.Parcelable { @@ -13701,7 +13707,7 @@ package android.content.pm { field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1 field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8 field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40 - field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10 + field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING, android.Manifest.permission.RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10 field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1 field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100 field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8 @@ -16418,6 +16424,7 @@ package android.graphics { field public static final int FLEX_RGBA_8888 = 42; // 0x2a field public static final int FLEX_RGB_888 = 41; // 0x29 field public static final int HEIC = 1212500294; // 0x48454946 + field @FlaggedApi("com.android.internal.camera.flags.camera_heif_gainmap") public static final int HEIC_ULTRAHDR = 4102; // 0x1006 field public static final int JPEG = 256; // 0x100 field public static final int JPEG_R = 4101; // 0x1005 field public static final int NV16 = 16; // 0x10 @@ -16844,6 +16851,7 @@ package android.graphics { field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000 field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000 field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8 + field @FlaggedApi("com.android.text.flags.vertical_text_layout") public static final int VERTICAL_TEXT_FLAG = 4096; // 0x1000 } public enum Paint.Align { @@ -18708,6 +18716,7 @@ package android.hardware { field public static final int DATASPACE_DISPLAY_P3 = 143261696; // 0x88a0000 field public static final int DATASPACE_DYNAMIC_DEPTH = 4098; // 0x1002 field public static final int DATASPACE_HEIF = 4100; // 0x1004 + field @FlaggedApi("com.android.internal.camera.flags.camera_heif_gainmap") public static final int DATASPACE_HEIF_ULTRAHDR = 4102; // 0x1006 field public static final int DATASPACE_JFIF = 146931712; // 0x8c20000 field public static final int DATASPACE_JPEG_R = 4101; // 0x1005 field public static final int DATASPACE_SCRGB = 411107328; // 0x18810000 @@ -55573,6 +55582,7 @@ package android.view.accessibility { field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH"; field public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000; // 0x4e20 field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX"; + field @FlaggedApi("android.view.accessibility.a11y_character_in_window_api") public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY"; field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY"; field public static final int FLAG_PREFETCH_ANCESTORS = 1; // 0x1 field public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 16; // 0x10 @@ -56975,6 +56985,9 @@ package android.view.inputmethod { method public String getExtraValueOf(String); method public int getIconResId(); method @NonNull public String getLanguageTag(); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutDisplayName(@NonNull android.content.Context, @NonNull android.content.pm.ApplicationInfo); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutLabelNonLocalized(); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @StringRes public int getLayoutLabelResource(); method @Deprecated @NonNull public String getLocale(); method public String getMode(); method @NonNull public CharSequence getNameOverride(); @@ -56994,6 +57007,8 @@ package android.view.inputmethod { method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(String); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelNonLocalized(@NonNull CharSequence); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelResource(@StringRes int); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean); method @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable android.icu.util.ULocale, @NonNull String); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(String); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 3fde7497a090..2a01ca082832 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -696,6 +696,7 @@ package android.app { field public static final String OPSTR_PLAY_AUDIO = "android:play_audio"; field public static final String OPSTR_POST_NOTIFICATION = "android:post_notification"; field public static final String OPSTR_PROJECT_MEDIA = "android:project_media"; + field @FlaggedApi("android.permission.flags.ranging_permission_enabled") public static final String OPSTR_RANGING = "android:ranging"; field @FlaggedApi("android.view.contentprotection.flags.rapid_clear_notifications_by_listener_app_op_enabled") public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = "android:rapid_clear_notifications_by_listener"; field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard"; field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final String OPSTR_READ_HEART_RATE = "android:read_heart_rate"; @@ -5293,13 +5294,19 @@ package android.hardware.display { field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400 } + public abstract static class VirtualDisplay.Callback { + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void onRequestedBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float); + } + public final class VirtualDisplayConfig implements android.os.Parcelable { + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness(); method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @Nullable public android.view.DisplayCutout getDisplayCutout(); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") public boolean isHomeSupported(); method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") public boolean isIgnoreActivitySizeRestrictions(); } public static final class VirtualDisplayConfig.Builder { + method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float); method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCutout(@Nullable android.view.DisplayCutout); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean); method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setIgnoreActivitySizeRestrictions(boolean); @@ -7027,7 +7034,7 @@ package android.hardware.soundtrigger { method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean); method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int); method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean); - method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@Nullable byte[]); + method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@NonNull byte[]); method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 98d6f58e6bcf..117351943587 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -402,6 +402,7 @@ package android.app { method @FlaggedApi("android.service.notification.notification_classification") @NonNull public java.util.Set<java.lang.String> getUnsupportedAdjustmentTypes(); method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String); method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean); + method @FlaggedApi("android.service.notification.notification_classification") public void setAssistantAdjustmentKeyTypeState(int, boolean); method @FlaggedApi("android.app.api_rich_ongoing") public void setCanPostPromotedNotifications(@NonNull String, int, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 36fc65a76d53..b447897733e1 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2764,14 +2764,19 @@ public class ActivityManager { /** * Information of organized child tasks. * + * @deprecated No longer used * @hide */ + @Deprecated public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>(); /** * Information about the last snapshot taken for this task. + * + * @deprecated No longer used * @hide */ + @Deprecated public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData(); public RecentTaskInfo() { @@ -2793,7 +2798,7 @@ public class ActivityManager { lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR); lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR); lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR); - super.readFromParcel(source); + super.readTaskFromParcel(source); } @Override @@ -2804,7 +2809,7 @@ public class ActivityManager { dest.writeTypedObject(lastSnapshotData.taskSize, flags); dest.writeTypedObject(lastSnapshotData.contentInsets, flags); dest.writeTypedObject(lastSnapshotData.bufferSize, flags); - super.writeToParcel(dest, flags); + super.writeTaskToParcel(dest, flags); } public static final @android.annotation.NonNull Creator<RecentTaskInfo> CREATOR @@ -2988,13 +2993,13 @@ public class ActivityManager { public void readFromParcel(Parcel source) { id = source.readInt(); - super.readFromParcel(source); + super.readTaskFromParcel(source); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); - super.writeToParcel(dest, flags); + super.writeTaskToParcel(dest, flags); } public static final @android.annotation.NonNull Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() { diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 799df1f9227a..16dcf2ad7e45 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -534,10 +534,9 @@ public class ActivityTaskManager { dest.writeIntArray(childTaskUserIds); dest.writeInt(visible ? 1 : 0); dest.writeInt(position); - super.writeToParcel(dest, flags); + super.writeTaskToParcel(dest, flags); } - @Override void readFromParcel(Parcel source) { bounds = source.readTypedObject(Rect.CREATOR); childTaskIds = source.createIntArray(); @@ -546,7 +545,7 @@ public class ActivityTaskManager { childTaskUserIds = source.createIntArray(); visible = source.readInt() > 0; position = source.readInt(); - super.readFromParcel(source); + super.readTaskFromParcel(source); } public static final @NonNull Creator<RootTaskInfo> CREATOR = new Creator<>() { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index ca98da76b78f..60b8f80d8f2d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3100,6 +3100,19 @@ public final class ActivityThread extends ClientTransactionHandler mResourcesManager = ResourcesManager.getInstance(); } + /** + * Creates and initialize a new system activity thread, to be used for testing. This does not + * call {@link #attach}, so it does not modify static state. + */ + @VisibleForTesting + @NonNull + public static ActivityThread createSystemActivityThreadForTesting() { + final var thread = new ActivityThread(); + thread.mSystemThread = true; + initializeSystemThread(thread); + return thread; + } + @UnsupportedAppUsage public ApplicationThread getApplicationThread() { @@ -6806,6 +6819,16 @@ public final class ActivityThread extends ClientTransactionHandler LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths); resApk.updateApplicationInfo(ai, oldPaths); } + if (android.content.res.Flags.systemContextHandleAppInfoChanged() && mSystemThread) { + final var systemContext = getSystemContext(); + if (systemContext.getPackageName().equals(ai.packageName)) { + // The system package is not tracked directly, but still needs to receive updates to + // its application info. + final ArrayList<String> oldPaths = new ArrayList<>(); + LoadedApk.makePaths(this, systemContext.getApplicationInfo(), oldPaths); + systemContext.mPackageInfo.updateApplicationInfo(ai, oldPaths); + } + } ResourcesImpl beforeImpl = getApplication().getResources().getImpl(); @@ -8560,17 +8583,7 @@ public final class ActivityThread extends ClientTransactionHandler // we can't display an alert, we just want to die die die. android.ddm.DdmHandleAppName.setAppName("system_process", UserHandle.myUserId()); - try { - mInstrumentation = new Instrumentation(); - mInstrumentation.basicInit(this); - ContextImpl context = ContextImpl.createAppContext( - this, getSystemContext().mPackageInfo); - mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null); - mInitialApplication.onCreate(); - } catch (Exception e) { - throw new RuntimeException( - "Unable to instantiate Application():" + e.toString(), e); - } + initializeSystemThread(this); } ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> { @@ -8595,6 +8608,28 @@ public final class ActivityThread extends ClientTransactionHandler ViewRootImpl.addConfigCallback(configChangedCallback); } + /** + * Initializes the given system activity thread, setting up its instrumentation and initial + * application. This only has an effect if the given thread is a {@link #mSystemThread}. + * + * @param thread the given system activity thread to initialize. + */ + private static void initializeSystemThread(@NonNull ActivityThread thread) { + if (!thread.mSystemThread) { + return; + } + try { + thread.mInstrumentation = new Instrumentation(); + thread.mInstrumentation.basicInit(thread); + ContextImpl context = ContextImpl.createAppContext( + thread, thread.getSystemContext().mPackageInfo); + thread.mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null); + thread.mInitialApplication.onCreate(); + } catch (Exception e) { + throw new RuntimeException("Unable to instantiate Application():" + e, e); + } + } + @UnsupportedAppUsage public static ActivityThread systemMain() { ThreadedRenderer.initForSystemProcess(); diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java index 4bfa3b340ec9..cf01f50c8026 100644 --- a/core/java/android/app/AppCompatCallbacks.java +++ b/core/java/android/app/AppCompatCallbacks.java @@ -28,6 +28,7 @@ import java.util.Arrays; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class AppCompatCallbacks implements Compatibility.BehaviorChangeDelegate { private final long[] mDisabledChanges; private final long[] mLoggableChanges; diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 8370c2e522f3..68794588afc5 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -167,10 +167,11 @@ public class AppCompatTaskInfo implements Parcelable { } /** - * @return {@code true} if top activity is pillarboxed. + * @return {@code true} if the top activity bounds are letterboxed with width <= height. */ - public boolean isTopActivityPillarboxed() { - return topActivityLetterboxWidth < topActivityLetterboxHeight; + public boolean isTopActivityPillarboxShaped() { + return isTopActivityLetterboxed() + && topActivityLetterboxWidth <= topActivityLetterboxHeight; } /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 0629b8a58b42..38c8583dd024 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -63,6 +63,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; +import android.os.IpcDataCache; import android.os.Looper; import android.os.PackageTagsList; import android.os.Parcel; @@ -78,12 +79,14 @@ import android.permission.PermissionGroupUsage; import android.permission.PermissionUsageHelper; import android.permission.flags.Flags; import android.provider.DeviceConfig; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LongSparseArray; import android.util.LongSparseLongArray; import android.util.Pools; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.Immutable; @@ -1611,9 +1614,16 @@ public class AppOpsManager { /** @hide Access to read skin temperature. */ public static final int OP_READ_SKIN_TEMPERATURE = AppOpEnums.APP_OP_READ_SKIN_TEMPERATURE; + /** + * Allows an app to range with nearby devices using any ranging technology available. + * + * @hide + */ + public static final int OP_RANGING = AppOpEnums.APP_OP_RANGING; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 151; + public static final int _NUM_OP = 152; /** * All app ops represented as strings. @@ -1768,6 +1778,7 @@ public class AppOpsManager { OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS, OPSTR_READ_HEART_RATE, OPSTR_READ_SKIN_TEMPERATURE, + OPSTR_RANGING, }) public @interface AppOpString {} @@ -2515,6 +2526,11 @@ public class AppOpsManager { @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED) public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature"; + /** @hide Access to ranging */ + @SystemApi + @FlaggedApi(Flags.FLAG_RANGING_PERMISSION_ENABLED) + public static final String OPSTR_RANGING = "android:ranging"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2586,6 +2602,7 @@ public class AppOpsManager { OP_BLUETOOTH_ADVERTISE, OP_UWB_RANGING, OP_NEARBY_WIFI_DEVICES, + Flags.rangingPermissionEnabled() ? OP_RANGING : OP_NONE, // Notifications OP_POST_NOTIFICATION, // Health @@ -3108,6 +3125,10 @@ public class AppOpsManager { Flags.platformSkinTemperatureEnabled() ? HealthPermissions.READ_SKIN_TEMPERATURE : null) .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_RANGING, OPSTR_RANGING, "RANGING") + .setPermission(Flags.rangingPermissionEnabled()? + Manifest.permission.RANGING : null) + .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), }; // The number of longs needed to form a full bitmask of app ops @@ -7797,6 +7818,116 @@ public class AppOpsManager { } } + private static final String APP_OP_MODE_CACHING_API = "getAppOpMode"; + private static final String APP_OP_MODE_CACHING_NAME = "appOpModeCache"; + private static final int APP_OP_MODE_CACHING_SIZE = 2048; + + private static final IpcDataCache.QueryHandler<AppOpModeQuery, Integer> sGetAppOpModeQuery = + new IpcDataCache.QueryHandler<>() { + @Override + public Integer apply(AppOpModeQuery query) { + IAppOpsService service = getService(); + try { + return service.checkOperationRawForDevice(query.op, query.uid, + query.packageName, query.attributionTag, query.virtualDeviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean shouldBypassCache(@NonNull AppOpModeQuery query) { + // If the flag to enable the new caching behavior is off, bypass the cache. + return !Flags.appopModeCachingEnabled(); + } + }; + + // A LRU cache on binder clients that caches AppOp mode by uid, packageName, virtualDeviceId + // and attributionTag. + private static final IpcDataCache<AppOpModeQuery, Integer> sAppOpModeCache = + new IpcDataCache<>(APP_OP_MODE_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + APP_OP_MODE_CACHING_API, APP_OP_MODE_CACHING_NAME, sGetAppOpModeQuery); + + // Ops that we don't want to cache due to: + // 1) Discrepancy of attributionTag support in checkOp and noteOp that determines if a package + // can bypass user restriction of an op: b/240617242. COARSE_LOCATION and FINE_LOCATION are + // the only two ops that are impacted. + private static final SparseBooleanArray OPS_WITHOUT_CACHING = new SparseBooleanArray(); + static { + OPS_WITHOUT_CACHING.put(OP_COARSE_LOCATION, true); + OPS_WITHOUT_CACHING.put(OP_FINE_LOCATION, true); + } + + private static boolean isAppOpModeCachingEnabled(int opCode) { + if (!Flags.appopModeCachingEnabled()) { + return false; + } + return !OPS_WITHOUT_CACHING.get(opCode, false); + } + + /** + * @hide + */ + public static void invalidateAppOpModeCache() { + if (Flags.appopModeCachingEnabled()) { + IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, APP_OP_MODE_CACHING_API); + } + } + + /** + * Bypass AppOpModeCache in the local process + * + * @hide + */ + public static void disableAppOpModeCache() { + if (Flags.appopModeCachingEnabled()) { + sAppOpModeCache.disableLocal(); + } + } + + private static final class AppOpModeQuery { + final int op; + final int uid; + final String packageName; + final int virtualDeviceId; + final String attributionTag; + final String methodName; + + AppOpModeQuery(int op, int uid, @Nullable String packageName, int virtualDeviceId, + @Nullable String attributionTag, @Nullable String methodName) { + this.op = op; + this.uid = uid; + this.packageName = packageName; + this.virtualDeviceId = virtualDeviceId; + this.attributionTag = attributionTag; + this.methodName = methodName; + } + + @Override + public String toString() { + return TextUtils.formatSimple("AppOpModeQuery(op=%d, uid=%d, packageName=%s, " + + "virtualDeviceId=%d, attributionTag=%s, methodName=%s", op, uid, + packageName, virtualDeviceId, attributionTag, methodName); + } + + @Override + public int hashCode() { + return Objects.hash(op, uid, packageName, virtualDeviceId, attributionTag); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null) return false; + if (this.getClass() != o.getClass()) return false; + + AppOpModeQuery other = (AppOpModeQuery) o; + return op == other.op && uid == other.uid && Objects.equals(packageName, + other.packageName) && virtualDeviceId == other.virtualDeviceId + && Objects.equals(attributionTag, other.attributionTag); + } + } + AppOpsManager(Context context, IAppOpsService service) { mContext = context; mService = service; @@ -8851,12 +8982,16 @@ public class AppOpsManager { private int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName, int virtualDeviceId) { try { - if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { - return mService.checkOperationRaw(op, uid, packageName, null); + int mode; + if (isAppOpModeCachingEnabled(op)) { + mode = sAppOpModeCache.query( + new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null, + "unsafeCheckOpRawNoThrow")); } else { - return mService.checkOperationRawForDevice( + mode = mService.checkOperationRawForDevice( op, uid, packageName, null, virtualDeviceId); } + return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -9041,7 +9176,7 @@ public class AppOpsManager { SyncNotedAppOp syncOp; if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { syncOp = mService.noteOperation(op, uid, packageName, attributionTag, - collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); + collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); } else { syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag, virtualDeviceId, collectionMode == COLLECT_ASYNC, message, @@ -9284,8 +9419,21 @@ public class AppOpsManager { @UnsupportedAppUsage public int checkOp(int op, int uid, String packageName) { try { - int mode = mService.checkOperationForDevice(op, uid, packageName, - Context.DEVICE_ID_DEFAULT); + int mode; + if (isAppOpModeCachingEnabled(op)) { + mode = sAppOpModeCache.query( + new AppOpModeQuery(op, uid, packageName, Context.DEVICE_ID_DEFAULT, null, + "checkOp")); + if (mode == MODE_FOREGROUND) { + // We only cache raw mode. If the mode is FOREGROUND, we need another binder + // call to fetch translated value based on the process state. + mode = mService.checkOperationForDevice(op, uid, packageName, + Context.DEVICE_ID_DEFAULT); + } + } else { + mode = mService.checkOperationForDevice(op, uid, packageName, + Context.DEVICE_ID_DEFAULT); + } if (mode == MODE_ERRORED) { throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } @@ -9324,13 +9472,19 @@ public class AppOpsManager { private int checkOpNoThrow(int op, int uid, String packageName, int virtualDeviceId) { try { int mode; - if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { - mode = mService.checkOperation(op, uid, packageName); + if (isAppOpModeCachingEnabled(op)) { + mode = sAppOpModeCache.query( + new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null, + "checkOpNoThrow")); + if (mode == MODE_FOREGROUND) { + // We only cache raw mode. If the mode is FOREGROUND, we need another binder + // call to fetch translated value based on the process state. + mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId); + } } else { mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId); } - - return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode; + return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index d1e517bbd03c..16444dc5adde 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -398,6 +398,7 @@ public abstract class ForegroundServiceTypePolicy { new RegularPermission(Manifest.permission.NFC), new RegularPermission(Manifest.permission.TRANSMIT_IR), new RegularPermission(Manifest.permission.UWB_RANGING), + new RegularPermission(Manifest.permission.RANGING), new UsbDevicePermission(), new UsbAccessoryPermission(), }, false), diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index a97fa18a3599..0654ac2f33ce 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -267,4 +267,7 @@ interface INotificationManager void setAdjustmentTypeSupportedState(in INotificationListener token, String key, boolean supported); List<String> getUnsupportedAdjustmentTypes(); + + int[] getAllowedAdjustmentKeyTypes(); + void setAssistantAdjustmentKeyTypeState(int type, boolean enabled); } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 768b70c28632..c49b02210dd4 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1848,6 +1848,20 @@ public class NotificationManager { /** * @hide */ + @TestApi + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public void setAssistantAdjustmentKeyTypeState(@Adjustment.Types int type, boolean enabled) { + INotificationManager service = getService(); + try { + service.setAssistantAdjustmentKeyTypeState(type, enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ public List<String> getEnabledNotificationListenerPackages() { INotificationManager service = getService(); try { diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 038dcdb7efc9..f432a22b14ac 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -717,12 +717,10 @@ public class PropertyInvalidatedCache<Query, Result> { // The shared memory. private volatile NonceStore mStore; - // The index of the nonce in shared memory. + // The index of the nonce in shared memory. This changes from INVALID only when the local + // object is completely initialized. private volatile int mHandle = NonceStore.INVALID_NONCE_INDEX; - // True if the string has been stored, ever. - private volatile boolean mRecorded = false; - // A short name that is saved in shared memory. This is the portion of the property name // that follows the prefix. private final String mShortName; @@ -736,48 +734,63 @@ public class PropertyInvalidatedCache<Query, Result> { } } + // Initialize the mStore and mHandle variables. This function does nothing if the + // variables are already initialized. Synchronization ensures that initialization happens + // no more than once. The function returns the new value of mHandle. + // + // If the "update" boolean is true, then the property is registered with the nonce store + // before the associated handle is fetched. + private int initialize(boolean update) { + synchronized (mLock) { + int handle = mHandle; + if (handle == NonceStore.INVALID_NONCE_INDEX) { + if (mStore == null) { + mStore = NonceStore.getInstance(); + if (mStore == null) { + return NonceStore.INVALID_NONCE_INDEX; + } + } + if (update) { + mStore.storeName(mShortName); + } + handle = mStore.getHandleForName(mShortName); + if (handle == NonceStore.INVALID_NONCE_INDEX) { + return NonceStore.INVALID_NONCE_INDEX; + } + // The handle must be valid. + mHandle = handle; + } + return handle; + } + } + // Fetch the nonce from shared memory. If the shared memory is not available, return // UNSET. If the shared memory is available but the nonce name is not known (it may not // have been invalidated by the server yet), return UNSET. @Override long getNonceInternal() { - if (mHandle == NonceStore.INVALID_NONCE_INDEX) { - if (mStore == null) { - mStore = NonceStore.getInstance(); - if (mStore == null) { - return NONCE_UNSET; - } - } - mHandle = mStore.getHandleForName(mShortName); - if (mHandle == NonceStore.INVALID_NONCE_INDEX) { + int handle = mHandle; + if (handle == NonceStore.INVALID_NONCE_INDEX) { + handle = initialize(false); + if (handle == NonceStore.INVALID_NONCE_INDEX) { return NONCE_UNSET; } } - return mStore.getNonce(mHandle); + return mStore.getNonce(handle); } - // Set the nonce in shared mmory. If the shared memory is not available, throw an - // exception. Otherwise, if the nonce name has never been recorded, record it now and - // fetch the handle for the name. If the handle cannot be created, throw an exception. + // Set the nonce in shared memory. If the shared memory is not available or if the nonce + // cannot be registered in shared memory, throw an exception. @Override void setNonceInternal(long value) { - if (mHandle == NonceStore.INVALID_NONCE_INDEX || !mRecorded) { - if (mStore == null) { - mStore = NonceStore.getInstance(); - if (mStore == null) { - throw new IllegalStateException("setNonce: shared memory not ready"); - } - } - // Always store the name before fetching the handle. storeName() is idempotent - // but does take a little time, so this code calls it just once. - mStore.storeName(mShortName); - mRecorded = true; - mHandle = mStore.getHandleForName(mShortName); - if (mHandle == NonceStore.INVALID_NONCE_INDEX) { - throw new IllegalStateException("setNonce: shared memory store failed"); + int handle = mHandle; + if (handle == NonceStore.INVALID_NONCE_INDEX) { + handle = initialize(true); + if (handle == NonceStore.INVALID_NONCE_INDEX) { + throw new IllegalStateException("unable to assign nonce handle: " + mName); } } - mStore.setNonce(mHandle, value); + mStore.setNonce(handle, value); } } diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index c4a6decd982f..aac963ade726 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -364,8 +364,9 @@ public class TaskInfo { // Do nothing } - private TaskInfo(Parcel source) { - readFromParcel(source); + /** @hide */ + public TaskInfo(Parcel source) { + readTaskFromParcel(source); } /** @@ -524,7 +525,7 @@ public class TaskInfo { /** * Reads the TaskInfo from a parcel. */ - void readFromParcel(Parcel source) { + void readTaskFromParcel(Parcel source) { userId = source.readInt(); taskId = source.readInt(); effectiveUid = source.readInt(); @@ -577,8 +578,9 @@ public class TaskInfo { /** * Writes the TaskInfo to a parcel. + * @hide */ - void writeToParcel(Parcel dest, int flags) { + public void writeTaskToParcel(Parcel dest, int flags) { dest.writeInt(userId); dest.writeInt(taskId); dest.writeInt(effectiveUid); diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index c0e435c04d3c..35149b5a3135 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -191,6 +191,12 @@ public final class DevicePolicyIdentifiers { public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity"; /** + * String identifier for {@link DevicePolicyManager#setMtePolicy(int)}. + */ + @FlaggedApi(android.app.admin.flags.Flags.FLAG_SET_MTE_POLICY_COEXISTENCE) + public static final String MEMORY_TAGGING_POLICY = "memoryTagging"; + + /** * @hide */ public static final String USER_RESTRICTION_PREFIX = "userRestriction_"; diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 3aaca25eca15..04a9d13420ee 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -276,6 +276,16 @@ flag { } flag { + name: "suspend_packages_coexistence" + namespace: "enterprise" + description: "Migrate setPackagesSuspended for unmanaged mode" + bug: "335624297" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "backup_connected_apps_settings" namespace: "enterprise" description: "backup and restore connected work and personal apps user settings across devices" diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java index db663f8ed4c4..7d21cbf955d9 100644 --- a/core/java/android/app/compat/ChangeIdStateCache.java +++ b/core/java/android/app/compat/ChangeIdStateCache.java @@ -31,13 +31,24 @@ import com.android.internal.compat.IPlatformCompat; * Handles caching of calls to {@link com.android.internal.compat.IPlatformCompat} * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class ChangeIdStateCache extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> { private static final String CACHE_KEY = createSystemCacheKey("is_compat_change_enabled"); private static final int MAX_ENTRIES = 2048; - private static boolean sDisabled = false; + private static boolean sDisabled = getDefaultDisabled(); private volatile IPlatformCompat mPlatformCompat; + + @android.ravenwood.annotation.RavenwoodReplace + private static boolean getDefaultDisabled() { + return false; + } + + private static boolean getDefaultDisabled$ravenwood() { + return true; // TODO(b/376676753) Disable the cache for now. + } + /** @hide */ public ChangeIdStateCache() { super(MAX_ENTRIES, CACHE_KEY); diff --git a/core/java/android/app/compat/ChangeIdStateQuery.java b/core/java/android/app/compat/ChangeIdStateQuery.java index 7598d6c90d3d..26d9ab65417e 100644 --- a/core/java/android/app/compat/ChangeIdStateQuery.java +++ b/core/java/android/app/compat/ChangeIdStateQuery.java @@ -35,6 +35,7 @@ import java.util.Objects; * @hide */ @Immutable +@android.ravenwood.annotation.RavenwoodKeepWholeClass final class ChangeIdStateQuery { static final int QUERY_BY_PACKAGE_NAME = 0; diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java index d7b2ab4351a4..643d4c96f7b9 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -39,6 +39,7 @@ import java.util.Set; * @hide */ @SystemApi +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class CompatChanges { private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache(); diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java index ebc2945fb1a0..ffc1eec05667 100644 --- a/core/java/android/app/compat/PackageOverride.java +++ b/core/java/android/app/compat/PackageOverride.java @@ -36,6 +36,7 @@ import java.util.Objects; * @hide */ @SystemApi +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class PackageOverride { /** @hide */ diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java index c3d6340be41f..8ffda7242b37 100644 --- a/core/java/android/app/wallpaper/WallpaperDescription.java +++ b/core/java/android/app/wallpaper/WallpaperDescription.java @@ -18,8 +18,6 @@ package android.app.wallpaper; import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING; -import static java.nio.charset.StandardCharsets.UTF_8; - import android.annotation.FlaggedApi; import android.app.WallpaperInfo; import android.content.ComponentName; @@ -31,7 +29,6 @@ import android.text.Html; import android.text.Spanned; import android.text.SpannedString; import android.util.Log; -import android.util.Xml; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -43,8 +40,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -154,46 +149,6 @@ public final class WallpaperDescription implements Parcelable { return Objects.hash(mComponent, mId); } - ////// Stream read/write - - /** - * Writes the content of the {@link WallpaperDescription} to a {@link OutputStream}. - * - * <p>The content can be read by {@link #readFromStream}. This method is intended for use by - * trusted apps only, and the format is not guaranteed to be stable.</p> - */ - public void writeToStream(@NonNull OutputStream outputStream) throws IOException { - TypedXmlSerializer serializer = Xml.newFastSerializer(); - serializer.setOutput(outputStream, UTF_8.name()); - serializer.startTag(null, "description"); - try { - saveToXml(serializer); - } catch (XmlPullParserException e) { - throw new IOException(e); - } - serializer.endTag(null, "description"); - serializer.flush(); - } - - /** - * Reads a {@link PersistableBundle} from an {@link InputStream}. - * - * <p>The stream must be generated by {@link #writeToStream}. This method is intended for use by - * trusted apps only, and the format is not guaranteed to be stable.</p> - */ - @NonNull - public static WallpaperDescription readFromStream(@NonNull InputStream inputStream) - throws IOException { - try { - TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setInput(inputStream, UTF_8.name()); - parser.next(); - return WallpaperDescription.restoreFromXml(parser); - } catch (XmlPullParserException e) { - throw new IOException(e); - } - } - ////// XML storage /** @hide */ diff --git a/core/java/android/content/pm/IOnAppsChangedListener.aidl b/core/java/android/content/pm/IOnAppsChangedListener.aidl index 830cbe0e0dd0..ade58c45b024 100644 --- a/core/java/android/content/pm/IOnAppsChangedListener.aidl +++ b/core/java/android/content/pm/IOnAppsChangedListener.aidl @@ -16,6 +16,7 @@ package android.content.pm; +import android.content.pm.LauncherUserInfo; import android.content.pm.ParceledListSlice; import android.os.Bundle; import android.os.UserHandle; @@ -34,4 +35,5 @@ oneway interface IOnAppsChangedListener { void onPackagesUnsuspended(in UserHandle user, in String[] packageNames); void onShortcutChanged(in UserHandle user, String packageName, in ParceledListSlice shortcuts); void onPackageLoadingProgressChanged(in UserHandle user, String packageName, float progress); + void onUserConfigChanged(in LauncherUserInfo launcherUserInfo); } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 26f919f99ee9..26b835689b67 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -182,6 +182,8 @@ public class LauncherApps { */ public static final int FLAG_CACHE_PEOPLE_TILE_SHORTCUTS = 2; + private static final String LAUNCHER_USER_INFO_EXTRA_KEY = "launcher_user_info"; + /** @hide */ @IntDef(flag = false, prefix = { "FLAG_CACHE_" }, value = { FLAG_CACHE_NOTIFICATION_SHORTCUTS, @@ -349,6 +351,19 @@ public class LauncherApps { */ public void onPackageLoadingProgressChanged(@NonNull String packageName, @NonNull UserHandle user, float progress) {} + + /** + * Indicates {@link LauncherUserInfo} configs for a user have changed. The new + * {@link LauncherUserInfo} is given as a parameter. + * + * {@link LauncherUserInfo#getUserConfig} to get the updated user configs. + * + * @param launcherUserInfo The LauncherUserInfo of the user/profile whose configs have + * changed. + */ + @FlaggedApi(android.multiuser.Flags.FLAG_ADD_LAUNCHER_USER_CONFIG) + public void onUserConfigChanged(@NonNull LauncherUserInfo launcherUserInfo) { + } } /** @@ -2168,6 +2183,21 @@ public class LauncherApps { } } } + + public void onUserConfigChanged(LauncherUserInfo launcherUserInfo) { + if (DEBUG) { + if (Flags.allowPrivateProfile() + && android.multiuser.Flags.addLauncherUserConfig()) { + Log.d(TAG, "OnUserConfigChanged for user type " + launcherUserInfo.getUserType() + + ", new userConfig: " + launcherUserInfo.getUserConfig()); + } + } + synchronized (LauncherApps.this) { + for (CallbackMessageHandler callback : mCallbacks) { + callback.postOnUserConfigChanged(launcherUserInfo); + } + } + } }; /** @@ -2224,6 +2254,7 @@ public class LauncherApps { private static final int MSG_UNSUSPENDED = 7; private static final int MSG_SHORTCUT_CHANGED = 8; private static final int MSG_LOADING_PROGRESS_CHANGED = 9; + private static final int MSG_USER_CONFIG_CHANGED = 10; private final LauncherApps.Callback mCallback; @@ -2278,6 +2309,14 @@ public class LauncherApps { mCallback.onPackageLoadingProgressChanged(info.packageName, info.user, info.mLoadingProgress); break; + case MSG_USER_CONFIG_CHANGED: + if (Flags.allowPrivateProfile() + && android.multiuser.Flags.addLauncherUserConfig()) { + mCallback.onUserConfigChanged(Objects.requireNonNull( + info.launcherExtras.getParcelable(LAUNCHER_USER_INFO_EXTRA_KEY, + LauncherUserInfo.class))); + } + break; } } @@ -2353,6 +2392,13 @@ public class LauncherApps { info.mLoadingProgress = progress; obtainMessage(MSG_LOADING_PROGRESS_CHANGED, info).sendToTarget(); } + + public void postOnUserConfigChanged(LauncherUserInfo launcherUserInfo) { + CallbackInfo info = new CallbackInfo(); + info.launcherExtras = new Bundle(); + info.launcherExtras.putParcelable(LAUNCHER_USER_INFO_EXTRA_KEY, launcherUserInfo); + obtainMessage(MSG_USER_CONFIG_CHANGED, info).sendToTarget(); + } } /** diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java index 8426f54d4754..574af5902e89 100644 --- a/core/java/android/content/pm/LauncherUserInfo.java +++ b/core/java/android/content/pm/LauncherUserInfo.java @@ -18,6 +18,7 @@ package android.content.pm; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.os.Bundle; import android.os.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -31,11 +32,25 @@ import android.os.UserManager; @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) public final class LauncherUserInfo implements Parcelable { + /** + * A boolean extra indicating whether the private space entrypoint should be hidden when locked. + * + * @see #getUserConfig + */ + @FlaggedApi(android.multiuser.Flags.FLAG_ADD_LAUNCHER_USER_CONFIG) + public static final String PRIVATE_SPACE_ENTRYPOINT_HIDDEN = + "private_space_entrypoint_hidden"; + private final String mUserType; // Serial number for the user, should be same as in the {@link UserInfo} object. private final int mUserSerialNumber; + // Additional configs for the user, e.g., whether to hide the private space entrypoint when + // locked. + private final Bundle mUserConfig; + + /** * Returns type of the user as defined in {@link UserManager}. e.g., * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE} @@ -50,6 +65,17 @@ public final class LauncherUserInfo implements Parcelable { } /** + * Returns additional configs for this launcher user + * + * @see #PRIVATE_SPACE_ENTRYPOINT_HIDDEN + */ + @FlaggedApi(android.multiuser.Flags.FLAG_ADD_LAUNCHER_USER_CONFIG) + @NonNull + public Bundle getUserConfig() { + return mUserConfig; + } + + /** * Returns serial number of user as returned by * {@link UserManager#getSerialNumberForUser(UserHandle)} * @@ -63,6 +89,7 @@ public final class LauncherUserInfo implements Parcelable { private LauncherUserInfo(@NonNull Parcel in) { mUserType = in.readString16NoHelper(); mUserSerialNumber = in.readInt(); + mUserConfig = in.readBundle(Bundle.class.getClassLoader()); } @Override @@ -70,6 +97,7 @@ public final class LauncherUserInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString16NoHelper(mUserType); dest.writeInt(mUserSerialNumber); + dest.writeBundle(mUserConfig); } @Override @@ -99,23 +127,36 @@ public final class LauncherUserInfo implements Parcelable { private final String mUserType; private final int mUserSerialNumber; + private final Bundle mUserConfig; + + + @FlaggedApi(android.multiuser.Flags.FLAG_ADD_LAUNCHER_USER_CONFIG) + public Builder(@NonNull String userType, int userSerialNumber, @NonNull Bundle config) { + this.mUserType = userType; + this.mUserSerialNumber = userSerialNumber; + this.mUserConfig = config; + } public Builder(@NonNull String userType, int userSerialNumber) { this.mUserType = userType; this.mUserSerialNumber = userSerialNumber; + this.mUserConfig = new Bundle(); } /** * Builds the LauncherUserInfo object */ - @NonNull public LauncherUserInfo build() { - return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber); + @NonNull + public LauncherUserInfo build() { + return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber, this.mUserConfig); } } // End builder - private LauncherUserInfo(@NonNull String userType, int userSerialNumber) { + private LauncherUserInfo(@NonNull String userType, int userSerialNumber, + @NonNull Bundle config) { this.mUserType = userType; this.mUserSerialNumber = userSerialNumber; + this.mUserConfig = config; } } diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 5b0cee75e591..4285b0a2b91a 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -251,6 +251,7 @@ public class ServiceInfo extends ComponentInfo * {@link android.Manifest.permission#NFC}, * {@link android.Manifest.permission#TRANSMIT_IR}, * {@link android.Manifest.permission#UWB_RANGING}, + * {@link android.Manifest.permission#RANGING}, * or has been granted the access to one of the attached USB devices/accessories. */ @RequiresPermission( @@ -267,6 +268,7 @@ public class ServiceInfo extends ComponentInfo Manifest.permission.NFC, Manifest.permission.TRANSMIT_IR, Manifest.permission.UWB_RANGING, + Manifest.permission.RANGING, }, conditional = true ) diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 6f70586881be..fff980fa8585 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -349,3 +349,12 @@ flag { bug: "364760703" is_fixed_read_only: true } + +flag { + name: "cloud_compilation_pm" + is_exported: true + namespace: "package_manager_service" + description: "Feature flag to enable the Cloud Compilation support on the package manager side." + bug: "377474232" + is_fixed_read_only: true +} diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 528bde80cd3d..3d89ce12dec4 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -543,3 +543,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "add_launcher_user_config" + namespace: "profile_experiences" + description: "Add support for LauncherUserInfo configs" + bug: "346553745" +} diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index e98fc0c9d02c..26ecbd1982d5 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -83,3 +83,15 @@ flag { bug: "364035303" } +flag { + name: "system_context_handle_app_info_changed" + is_exported: true + namespace: "resource_manager" + description: "Feature flag for allowing system context to handle application info changes" + bug: "362420029" + # This flag is read at boot time. + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java index 611738435f7e..1cd9244333c5 100644 --- a/core/java/android/hardware/DataSpace.java +++ b/core/java/android/hardware/DataSpace.java @@ -420,18 +420,38 @@ public final class DataSpace { public static final int DATASPACE_HEIF = 4100; /** - * ISO/IEC TBD + * Ultra HDR * - * JPEG image with embedded recovery map following the Jpeg/R specification. + * JPEG image with embedded HDR gain map following the Ultra HDR specification and + * starting with Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM V} + * ISO/CD 21496‐1 * - * <p>This value must always remain aligned with the public ImageFormat Jpeg/R definition and is - * valid with formats: - * HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to ISO/IEC TBD. - * The image contains a standard SDR JPEG and a recovery map. Jpeg/R decoders can use the - * map to recover the input image.</p> + * <p>This value is valid with formats:</p> + * <ul> + * <li>HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to + * ISO/CD 21496‐1</li> + * </ul> + * <p> + * The image contains a standard SDR JPEG and a gain map. Ultra HDR decoders can use the + * gain map to boost the brightness of the rendered image.</p> */ public static final int DATASPACE_JPEG_R = 4101; + /** + * ISO/IEC 23008-12:2024 + * + * High Efficiency Image File Format (HEIF) with embedded HDR gain map + * + * <p>This value is valid with formats:</p> + * <ul> + * <li>HAL_PIXEL_FORMAT_BLOB: A HEIC image encoded by HEVC encoder + * according to ISO/IEC 23008-12:2024 that includes an HDR gain map and + * metadata according to ISO/CD 21496‐1.</li> + * </ul> + */ + @FlaggedApi(com.android.internal.camera.flags.Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final int DATASPACE_HEIF_ULTRAHDR = 4102; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { @@ -660,6 +680,7 @@ public final class DataSpace { DATASPACE_DEPTH, DATASPACE_DYNAMIC_DEPTH, DATASPACE_HEIF, + DATASPACE_HEIF_ULTRAHDR, DATASPACE_JPEG_R, DATASPACE_UNKNOWN, DATASPACE_SCRGB_LINEAR, diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 16d82caa6c1a..a37648f7e45d 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -5775,6 +5775,122 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); /** + * <p>The available HEIC (ISO/IEC 23008-12/24) UltraHDR stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream).</p> + * <p>The configurations are listed as <code>(format, width, height, input?)</code> tuples.</p> + * <p>All the static, control, and dynamic metadata tags related to JPEG apply to HEIC formats. + * Configuring JPEG and HEIC streams at the same time is not supported.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicUltraHdrStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination for HEIC UltraHDR output formats.</p> + * <p>This should correspond to the frame duration when only that + * stream is active, with all processing (typically in android.*.mode) + * set to either OFF or FAST.</p> + * <p>When multiple streams are used in a request, the minimum frame + * duration will be max(individual stream min durations).</p> + * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and + * android.scaler.availableStallDurations for more details about + * calculating the max frame rate.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for HEIC UltraHDR streams.</p> + * <p>A stall duration is how much extra time would get added + * to the normal minimum frame duration for a repeating request + * that has streams with non-zero stall.</p> + * <p>This functions similarly to + * android.scaler.availableStallDurations for HEIC UltraHDR + * streams.</p> + * <p>All HEIC output stream formats may have a nonzero stall + * duration.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>The available HEIC (ISO/IEC 23008-12/24) UltraHDR stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream) for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Refer to android.heic.availableHeicStreamConfigurations for details.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicUltraHdrStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination for HEIC UltraHDR output formats for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Refer to android.heic.availableHeicMinFrameDurations for details.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for HEIC UltraHDR streams for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Refer to android.heic.availableHeicStallDurations for details.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** * <p>The direction of the camera faces relative to the vehicle body frame and the * passenger seats.</p> * <p>This enum defines the lens facing characteristic of the cameras on the automotive diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index ef7f3f8ab58b..e22c263e893d 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -1385,6 +1385,9 @@ public class CameraMetadataNative implements Parcelable { /*jpegRconfiguration*/ null, /*jpegRminduration*/ null, /*jpegRstallduration*/ null, + /*heicUltraHDRconfiguration*/ null, + /*heicUltraHDRminduration*/ null, + /*heicUltraHDRstallduration*/ null, /*highspeedvideoconfigurations*/ null, /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]); break; @@ -1402,6 +1405,9 @@ public class CameraMetadataNative implements Parcelable { /*jpegRconfiguration*/ null, /*jpegRminduration*/ null, /*jpegRstallduration*/ null, + /*heicUltraHDRconfiguration*/ null, + /*heicUltraHDRminduration*/ null, + /*heicUltraHDRstallduration*/ null, highSpeedVideoConfigurations, /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]); break; @@ -1419,6 +1425,9 @@ public class CameraMetadataNative implements Parcelable { /*jpegRconfiguration*/ null, /*jpegRminduration*/ null, /*jpegRstallduration*/ null, + /*heicUltraHDRcconfiguration*/ null, + /*heicUltraHDRminduration*/ null, + /*heicUltraHDRstallduration*/ null, /*highSpeedVideoConfigurations*/ null, inputOutputFormatsMap, listHighResolution, supportsPrivate[i]); break; @@ -1436,6 +1445,9 @@ public class CameraMetadataNative implements Parcelable { /*jpegRconfiguration*/ null, /*jpegRminduration*/ null, /*jpegRstallduration*/ null, + /*heicUltraHDRcconfiguration*/ null, + /*heicUltraHDRminduration*/ null, + /*heicUltraHDRstallduration*/ null, /*highSpeedVideoConfigurations*/ null, /*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]); } @@ -1607,6 +1619,17 @@ public class CameraMetadataNative implements Parcelable { CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS); StreamConfigurationDuration[] heicStallDurations = getBase( CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS); + StreamConfiguration[] heicUltraHDRConfigurations = null; + StreamConfigurationDuration[] heicUltraHDRMinFrameDurations = null; + StreamConfigurationDuration[] heicUltraHDRStallDurations = null; + if (Flags.cameraHeifGainmap()) { + heicUltraHDRConfigurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS); + heicUltraHDRMinFrameDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS); + heicUltraHDRStallDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS); + } StreamConfiguration[] jpegRConfigurations = getBase( CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS); StreamConfigurationDuration[] jpegRMinFrameDurations = getBase( @@ -1625,7 +1648,8 @@ public class CameraMetadataNative implements Parcelable { dynamicDepthStallDurations, heicConfigurations, heicMinFrameDurations, heicStallDurations, jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations, - highSpeedVideoConfigurations, inputOutputFormatsMap, + heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations, + heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution); } @@ -1662,6 +1686,17 @@ public class CameraMetadataNative implements Parcelable { CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); StreamConfigurationDuration[] heicStallDurations = getBase( CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfiguration[] heicUltraHDRConfigurations = null; + StreamConfigurationDuration[] heicUltraHDRMinFrameDurations = null; + StreamConfigurationDuration[] heicUltraHDRStallDurations = null; + if (Flags.cameraHeifGainmap()) { + heicUltraHDRConfigurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); + heicUltraHDRMinFrameDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); + heicUltraHDRStallDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS_MAXIMUM_RESOLUTION); + } StreamConfiguration[] jpegRConfigurations = getBase( CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); StreamConfigurationDuration[] jpegRMinFrameDurations = getBase( @@ -1681,7 +1716,8 @@ public class CameraMetadataNative implements Parcelable { dynamicDepthStallDurations, heicConfigurations, heicMinFrameDurations, heicStallDurations, jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations, - highSpeedVideoConfigurations, inputOutputFormatsMap, + heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations, + heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution, false); } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index e3dbb2bbbf90..ec028bf6dcbb 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkArrayElementsNotNull; import android.graphics.ImageFormat; import android.graphics.PixelFormat; +import android.hardware.DataSpace; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraMetadata; @@ -31,6 +32,8 @@ import android.util.Size; import android.util.SparseIntArray; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import java.util.Arrays; import java.util.HashMap; import java.util.Objects; @@ -100,6 +103,12 @@ public final class StreamConfigurationMap { * {@link StreamConfigurationDuration} * @param jpegRStallDurations a non-{@code null} array of Jpeg/R * {@link StreamConfigurationDuration} + * @param heicUltraHDRConfigurations a non-{@code null} array of Heic UltraHDR + * {@link StreamConfiguration} + * @param heicUltraHDRMinFrameDurations a non-{@code null} array of Heic UltraHDR + * {@link StreamConfigurationDuration} + * @param heicUltraHDRStallDurations a non-{@code null} array of Heic UltraHDR + * {@link StreamConfigurationDuration} * @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if * camera device does not support high speed video recording * @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE @@ -125,6 +134,9 @@ public final class StreamConfigurationMap { StreamConfiguration[] jpegRConfigurations, StreamConfigurationDuration[] jpegRMinFrameDurations, StreamConfigurationDuration[] jpegRStallDurations, + StreamConfiguration[] heicUltraHDRConfigurations, + StreamConfigurationDuration[] heicUltraHDRMinFrameDurations, + StreamConfigurationDuration[] heicUltraHDRStallDurations, HighSpeedVideoConfiguration[] highSpeedVideoConfigurations, ReprocessFormatsMap inputOutputFormatsMap, boolean listHighResolution) { @@ -134,8 +146,9 @@ public final class StreamConfigurationMap { dynamicDepthStallDurations, heicConfigurations, heicMinFrameDurations, heicStallDurations, jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations, - highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution, - /*enforceImplementationDefined*/ true); + heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations, + heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, + listHighResolution, /*enforceImplementationDefined*/ true); } /** @@ -168,6 +181,12 @@ public final class StreamConfigurationMap { * {@link StreamConfigurationDuration} * @param jpegRStallDurations a non-{@code null} array of Jpeg/R * {@link StreamConfigurationDuration} + * @param heicUltraHDRConfigurations an array of Heic UltraHDR + * {@link StreamConfiguration}, {@code null} if camera doesn't support the format + * @param heicUltraHDRMinFrameDurations an array of Heic UltraHDR + * {@link StreamConfigurationDuration}, {@code null} if camera doesn't support the format + * @param heicUltraHDRStallDurations an array of Heic UltraHDR + * {@link StreamConfigurationDuration}, {@code null} if camera doesn't support the format * @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if * camera device does not support high speed video recording * @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE @@ -195,6 +214,9 @@ public final class StreamConfigurationMap { StreamConfiguration[] jpegRConfigurations, StreamConfigurationDuration[] jpegRMinFrameDurations, StreamConfigurationDuration[] jpegRStallDurations, + StreamConfiguration[] heicUltraHDRConfigurations, + StreamConfigurationDuration[] heicUltraHDRMinFrameDurations, + StreamConfigurationDuration[] heicUltraHDRStallDurations, HighSpeedVideoConfiguration[] highSpeedVideoConfigurations, ReprocessFormatsMap inputOutputFormatsMap, boolean listHighResolution, @@ -259,6 +281,18 @@ public final class StreamConfigurationMap { "heicStallDurations"); } + if (heicUltraHDRConfigurations == null || (!Flags.cameraHeifGainmap())) { + mHeicUltraHDRConfigurations = new StreamConfiguration[0]; + mHeicUltraHDRMinFrameDurations = new StreamConfigurationDuration[0]; + mHeicUltraHDRStallDurations = new StreamConfigurationDuration[0]; + } else { + mHeicUltraHDRConfigurations = checkArrayElementsNotNull(heicUltraHDRConfigurations, + "heicUltraHDRConfigurations"); + mHeicUltraHDRMinFrameDurations = checkArrayElementsNotNull( + heicUltraHDRMinFrameDurations, "heicUltraHDRMinFrameDurations"); + mHeicUltraHDRStallDurations = checkArrayElementsNotNull(heicUltraHDRStallDurations, + "heicUltraHDRStallDurations"); + } if (jpegRConfigurations == null) { mJpegRConfigurations = new StreamConfiguration[0]; @@ -336,6 +370,19 @@ public final class StreamConfigurationMap { mHeicOutputFormats.get(config.getFormat()) + 1); } + if (Flags.cameraHeifGainmap()) { + // For each Heic UlrtaHDR format, track how many sizes there are available to configure + for (StreamConfiguration config : mHeicUltraHDRConfigurations) { + if (!config.isOutput()) { + // Ignoring input Heic UltraHDR configs + continue; + } + + mHeicUltraHDROutputFormats.put(config.getFormat(), + mHeicUltraHDROutputFormats.get(config.getFormat()) + 1); + } + } + // For each Jpeg/R format, track how many sizes there are available to configure for (StreamConfiguration config : mJpegRConfigurations) { if (!config.isOutput()) { @@ -483,6 +530,11 @@ public final class StreamConfigurationMap { int internalFormat = imageFormatToInternal(format); int dataspace = imageFormatToDataspace(format); + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + return mHeicUltraHDROutputFormats.indexOfKey(internalFormat) >= 0; + } + } if (dataspace == HAL_DATASPACE_DEPTH) { return mDepthOutputFormats.indexOfKey(internalFormat) >= 0; } else if (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) { @@ -607,6 +659,11 @@ public final class StreamConfigurationMap { surfaceDataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations : surfaceDataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations : mConfigurations; + if (Flags.cameraHeifGainmap()) { + if (surfaceDataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + configs = mHeicUltraHDRConfigurations; + } + } for (StreamConfiguration config : configs) { if (config.getFormat() == surfaceFormat && config.isOutput()) { // Matching format, either need exact size match, or a flexible consumer @@ -646,6 +703,11 @@ public final class StreamConfigurationMap { dataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations : dataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations : mConfigurations; + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR ) { + configs = mHeicUltraHDRConfigurations; + } + } for (StreamConfiguration config : configs) { if ((config.getFormat() == internalFormat) && config.isOutput() && config.getSize().equals(size)) { @@ -1176,6 +1238,10 @@ public final class StreamConfigurationMap { Arrays.equals(mHeicConfigurations, other.mHeicConfigurations) && Arrays.equals(mHeicMinFrameDurations, other.mHeicMinFrameDurations) && Arrays.equals(mHeicStallDurations, other.mHeicStallDurations) && + Arrays.equals(mHeicUltraHDRConfigurations, other.mHeicUltraHDRConfigurations) && + Arrays.equals(mHeicUltraHDRMinFrameDurations, + other.mHeicUltraHDRMinFrameDurations) && + Arrays.equals(mHeicUltraHDRStallDurations, other.mHeicUltraHDRStallDurations) && Arrays.equals(mJpegRConfigurations, other.mJpegRConfigurations) && Arrays.equals(mJpegRMinFrameDurations, other.mJpegRMinFrameDurations) && Arrays.equals(mJpegRStallDurations, other.mJpegRStallDurations) && @@ -1197,8 +1263,9 @@ public final class StreamConfigurationMap { mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations, mDynamicDepthStallDurations, mHeicConfigurations, mHeicMinFrameDurations, mHeicStallDurations, - mJpegRConfigurations, mJpegRMinFrameDurations, mJpegRStallDurations, - mHighSpeedVideoConfigurations); + mHeicUltraHDRConfigurations, mHeicUltraHDRMinFrameDurations, + mHeicUltraHDRStallDurations, mJpegRConfigurations, mJpegRMinFrameDurations, + mJpegRStallDurations, mHighSpeedVideoConfigurations); } // Check that the argument is supported by #getOutputFormats or #getInputFormats @@ -1209,6 +1276,13 @@ public final class StreamConfigurationMap { int internalDataspace = imageFormatToDataspace(format); if (output) { + if (Flags.cameraHeifGainmap()) { + if (internalDataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + if (mHeicUltraHDROutputFormats.indexOfKey(internalFormat) >= 0) { + return format; + } + } + } if (internalDataspace == HAL_DATASPACE_DEPTH) { if (mDepthOutputFormats.indexOfKey(internalFormat) >= 0) { return format; @@ -1429,6 +1503,7 @@ public final class StreamConfigurationMap { * <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB * <li>ImageFormat.DEPTH_JPEG => HAL_PIXEL_FORMAT_BLOB * <li>ImageFormat.HEIC => HAL_PIXEL_FORMAT_BLOB + * <li>ImageFormat.HEIC_ULTRAHDR => HAL_PIXEL_FORMAT_BLOB * <li>ImageFormat.JPEG_R => HAL_PIXEL_FORMAT_BLOB * <li>ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16 * </ul> @@ -1451,6 +1526,11 @@ public final class StreamConfigurationMap { * if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED} */ static int imageFormatToInternal(int format) { + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + return HAL_PIXEL_FORMAT_BLOB; + } + } switch (format) { case ImageFormat.JPEG: case ImageFormat.DEPTH_POINT_CLOUD: @@ -1480,6 +1560,7 @@ public final class StreamConfigurationMap { * <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH * <li>ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH * <li>ImageFormat.HEIC => HAL_DATASPACE_HEIF + * <li>ImageFormat.HEIC_ULTRAHDR => DATASPACE_HEIF_ULTRAHDR * <li>ImageFormat.JPEG_R => HAL_DATASPACE_JPEG_R * <li>ImageFormat.YUV_420_888 => HAL_DATASPACE_JFIF * <li>ImageFormat.RAW_SENSOR => HAL_DATASPACE_ARBITRARY @@ -1508,6 +1589,11 @@ public final class StreamConfigurationMap { * if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED} */ static int imageFormatToDataspace(int format) { + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + return DataSpace.DATASPACE_HEIF_ULTRAHDR; + } + } switch (format) { case ImageFormat.JPEG: return HAL_DATASPACE_V0_JFIF; @@ -1584,13 +1670,21 @@ public final class StreamConfigurationMap { dataspace == HAL_DATASPACE_JPEG_R ? mJpegROutputFormats : highRes ? mHighResOutputFormats : mOutputFormats; - + boolean isDataSpaceHeifUltraHDR = false; + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + formatsMap = mHeicUltraHDROutputFormats; + isDataSpaceHeifUltraHDR = true; + } + } int sizesCount = formatsMap.get(format); if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || dataspace == HAL_DATASPACE_JPEG_R || dataspace == HAL_DATASPACE_DYNAMIC_DEPTH || - dataspace == HAL_DATASPACE_HEIF)) && sizesCount == 0) || + dataspace == HAL_DATASPACE_HEIF || + isDataSpaceHeifUltraHDR)) && sizesCount == 0) || (output && (dataspace != HAL_DATASPACE_DEPTH && dataspace != HAL_DATASPACE_JPEG_R && dataspace != HAL_DATASPACE_DYNAMIC_DEPTH && + !isDataSpaceHeifUltraHDR && dataspace != HAL_DATASPACE_HEIF) && mAllOutputFormats.get(format) == 0)) { return null; @@ -1604,12 +1698,14 @@ public final class StreamConfigurationMap { (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations : + (isDataSpaceHeifUltraHDR) ? mHeicUltraHDRConfigurations : mConfigurations; StreamConfigurationDuration[] minFrameDurations = (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations : + (isDataSpaceHeifUltraHDR) ? mHeicUltraHDRMinFrameDurations : mMinFrameDurations; for (StreamConfiguration config : configurations) { @@ -1639,7 +1735,8 @@ public final class StreamConfigurationMap { // Dynamic depth streams can have both fast and also high res modes. if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH || - dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R)) { + dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R) || + isDataSpaceHeifUltraHDR) { if (sizeIndex > sizesCount) { throw new AssertionError( @@ -1682,6 +1779,11 @@ public final class StreamConfigurationMap { if (mHeicOutputFormats.size() > 0) { formats[i++] = ImageFormat.HEIC; } + if (Flags.cameraHeifGainmap()) { + if (mHeicUltraHDROutputFormats.size() > 0) { + formats[i++] = ImageFormat.HEIC_ULTRAHDR; + } + } if (mJpegROutputFormats.size() > 0) { formats[i++] = ImageFormat.JPEG_R; } @@ -1725,12 +1827,19 @@ public final class StreamConfigurationMap { * @see #DURATION_STALL * */ private StreamConfigurationDuration[] getDurations(int duration, int dataspace) { + boolean isDataSpaceHeifUltraHDR = false; + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + isDataSpaceHeifUltraHDR = true; + } + } switch (duration) { case DURATION_MIN_FRAME: return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations : + isDataSpaceHeifUltraHDR ? mHeicUltraHDRMinFrameDurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations : mMinFrameDurations; @@ -1738,6 +1847,7 @@ public final class StreamConfigurationMap { return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicStallDurations : + isDataSpaceHeifUltraHDR ? mHeicUltraHDRStallDurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRStallDurations : mStallDurations; default: @@ -1754,6 +1864,7 @@ public final class StreamConfigurationMap { size += mDynamicDepthOutputFormats.size(); size += mHeicOutputFormats.size(); size += mJpegROutputFormats.size(); + size += mHeicUltraHDROutputFormats.size(); } return size; @@ -1774,11 +1885,18 @@ public final class StreamConfigurationMap { } private boolean isSupportedInternalConfiguration(int format, int dataspace, Size size) { + boolean isDataSpaceHeifUltraHDR = false; + if (Flags.cameraHeifGainmap()) { + if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) { + isDataSpaceHeifUltraHDR = true; + } + } StreamConfiguration[] configurations = (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations : (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations : + isDataSpaceHeifUltraHDR ? mHeicUltraHDRConfigurations : mConfigurations; for (int i = 0; i < configurations.length; i++) { @@ -1954,6 +2072,11 @@ public final class StreamConfigurationMap { * @hide */ public static String formatToString(int format) { + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + return "HEIC_ULTRAHDR"; + } + } switch (format) { case ImageFormat.YV12: return "YV12"; @@ -2078,6 +2201,10 @@ public final class StreamConfigurationMap { private final StreamConfigurationDuration[] mHeicMinFrameDurations; private final StreamConfigurationDuration[] mHeicStallDurations; + private final StreamConfiguration[] mHeicUltraHDRConfigurations; + private final StreamConfigurationDuration[] mHeicUltraHDRMinFrameDurations; + private final StreamConfigurationDuration[] mHeicUltraHDRStallDurations; + private final StreamConfiguration[] mJpegRConfigurations; private final StreamConfigurationDuration[] mJpegRMinFrameDurations; private final StreamConfigurationDuration[] mJpegRStallDurations; @@ -2103,6 +2230,8 @@ public final class StreamConfigurationMap { private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray(); /** internal format -> num heic output sizes mapping, for HAL_DATASPACE_HEIF */ private final SparseIntArray mHeicOutputFormats = new SparseIntArray(); + /** internal format -> num heic output sizes mapping, for DATASPACE_HEIF_GAINMAP */ + private final SparseIntArray mHeicUltraHDROutputFormats = new SparseIntArray(); /** internal format -> num Jpeg/R output sizes mapping, for HAL_DATASPACE_JPEG_R */ private final SparseIntArray mJpegROutputFormats = new SparseIntArray(); diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 3c6841cd8aeb..56307ae53a0c 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -1432,6 +1432,13 @@ public final class DisplayManagerGlobal { mExecutor.execute(mCallback::onStopped); } } + + @Override // Binder call + public void onRequestedBrightnessChanged(float brightness) { + if (mCallback != null) { + mExecutor.execute(() -> mCallback.onRequestedBrightnessChanged(brightness)); + } + } } /** diff --git a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl index c3490d177be2..9cc0364f2729 100644 --- a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl +++ b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl @@ -38,4 +38,9 @@ oneway interface IVirtualDisplayCallback { * of the application to release() the virtual display. */ void onStopped(); + + /** + * Called when the virtual display's requested brightness has changed. + */ + void onRequestedBrightnessChanged(float brightness); } diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java index 32b640583734..3b573ea98c27 100644 --- a/core/java/android/hardware/display/VirtualDisplay.java +++ b/core/java/android/hardware/display/VirtualDisplay.java @@ -16,6 +16,8 @@ package android.hardware.display; import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.SystemApi; import android.view.Display; import android.view.Surface; @@ -164,5 +166,25 @@ public final class VirtualDisplay { * of the application to release() the virtual display. */ public void onStopped() { } + + /** + * Called when the requested brightness of the display has changed. + * + * <p>The system may adjust the display's brightness based on user or app activity. This + * callback will only be invoked if the display has an explicitly specified default + * brightness value.</p> + * + * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of + * {@code 1.0} indicates the maximum supported brightness.</p> + * + * @see android.view.View#setKeepScreenOn(boolean) + * @see android.view.WindowManager.LayoutParams#screenBrightness + * @see VirtualDisplayConfig.Builder#setDefaultBrightness(float) + * @hide + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @SystemApi + public void onRequestedBrightnessChanged( + @FloatRange(from = 0.0f, to = 1.0f) float brightness) {} } } diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java index 49944c76eb99..57d9d28a9d47 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -29,6 +29,7 @@ import android.media.projection.MediaProjection; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; +import android.os.PowerManager; import android.util.ArraySet; import android.view.Display; import android.view.DisplayCutout; @@ -61,6 +62,7 @@ public final class VirtualDisplayConfig implements Parcelable { private final boolean mIsHomeSupported; private final DisplayCutout mDisplayCutout; private final boolean mIgnoreActivitySizeRestrictions; + private final float mDefaultBrightness; private VirtualDisplayConfig( @NonNull String name, @@ -76,7 +78,8 @@ public final class VirtualDisplayConfig implements Parcelable { float requestedRefreshRate, boolean isHomeSupported, @Nullable DisplayCutout displayCutout, - boolean ignoreActivitySizeRestrictions) { + boolean ignoreActivitySizeRestrictions, + @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness) { mName = name; mWidth = width; mHeight = height; @@ -91,6 +94,7 @@ public final class VirtualDisplayConfig implements Parcelable { mIsHomeSupported = isHomeSupported; mDisplayCutout = displayCutout; mIgnoreActivitySizeRestrictions = ignoreActivitySizeRestrictions; + mDefaultBrightness = defaultBrightness; } /** @@ -157,6 +161,22 @@ public final class VirtualDisplayConfig implements Parcelable { } /** + * Returns the default brightness of the display. + * + * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of {@code 1.0} + * indicates the maximum supported brightness.</p> + * + * @see Builder#setDefaultBrightness(float) + * @hide + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @SystemApi + public @FloatRange(from = 0.0f, to = 1.0f) float getDefaultBrightness() { + return mDefaultBrightness; + } + + + /** * Returns the unique identifier for the display. Shouldn't be displayed to the user. * @hide */ @@ -245,6 +265,7 @@ public final class VirtualDisplayConfig implements Parcelable { dest.writeBoolean(mIsHomeSupported); DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags); dest.writeBoolean(mIgnoreActivitySizeRestrictions); + dest.writeFloat(mDefaultBrightness); } @Override @@ -272,7 +293,8 @@ public final class VirtualDisplayConfig implements Parcelable { && mRequestedRefreshRate == that.mRequestedRefreshRate && mIsHomeSupported == that.mIsHomeSupported && mIgnoreActivitySizeRestrictions == that.mIgnoreActivitySizeRestrictions - && Objects.equals(mDisplayCutout, that.mDisplayCutout); + && Objects.equals(mDisplayCutout, that.mDisplayCutout) + && mDefaultBrightness == that.mDefaultBrightness; } @Override @@ -281,7 +303,7 @@ public final class VirtualDisplayConfig implements Parcelable { mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId, mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories, mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout, - mIgnoreActivitySizeRestrictions); + mIgnoreActivitySizeRestrictions, mDefaultBrightness); return hashCode; } @@ -303,6 +325,7 @@ public final class VirtualDisplayConfig implements Parcelable { + " mIsHomeSupported=" + mIsHomeSupported + " mDisplayCutout=" + mDisplayCutout + " mIgnoreActivitySizeRestrictions=" + mIgnoreActivitySizeRestrictions + + " mDefaultBrightness=" + mDefaultBrightness + ")"; } @@ -321,6 +344,7 @@ public final class VirtualDisplayConfig implements Parcelable { mIsHomeSupported = in.readBoolean(); mDisplayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(in); mIgnoreActivitySizeRestrictions = in.readBoolean(); + mDefaultBrightness = in.readFloat(); } @NonNull @@ -355,6 +379,7 @@ public final class VirtualDisplayConfig implements Parcelable { private boolean mIsHomeSupported = false; private DisplayCutout mDisplayCutout = null; private boolean mIgnoreActivitySizeRestrictions = false; + private float mDefaultBrightness = 0.0f; /** * Creates a new Builder. @@ -547,6 +572,35 @@ public final class VirtualDisplayConfig implements Parcelable { } /** + * Sets the default brightness of the display. + * + * <p>The system will use this brightness value whenever the display should be bright, i.e. + * it is powered on and not dimmed due to user activity or app activity.</p> + * + * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of + * {@code 1.0} indicates the maximum supported brightness.</p> + * + * <p>If unset, defaults to {@code 0.0}</p> + * + * @see android.view.View#setKeepScreenOn(boolean) + * @see Builder#setDefaultBrightness(float) + * @see VirtualDisplay.Callback#onRequestedBrightnessChanged(float) + * @hide + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER) + @SystemApi + @NonNull + public Builder setDefaultBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) { + if (brightness < PowerManager.BRIGHTNESS_MIN + || brightness > PowerManager.BRIGHTNESS_MAX) { + throw new IllegalArgumentException( + "Virtual display default brightness must be in range [0.0, 1.0]"); + } + mDefaultBrightness = brightness; + return this; + } + + /** * Builds the {@link VirtualDisplayConfig} instance. */ @NonNull @@ -565,7 +619,8 @@ public final class VirtualDisplayConfig implements Parcelable { mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout, - mIgnoreActivitySizeRestrictions); + mIgnoreActivitySizeRestrictions, + mDefaultBrightness); } } } diff --git a/core/java/android/hardware/input/AidlInputGestureData.aidl b/core/java/android/hardware/input/AidlInputGestureData.aidl index 137f672bf59c..e33ec53dd208 100644 --- a/core/java/android/hardware/input/AidlInputGestureData.aidl +++ b/core/java/android/hardware/input/AidlInputGestureData.aidl @@ -19,13 +19,26 @@ package android.hardware.input; /** @hide */ @JavaDerive(equals=true) parcelable AidlInputGestureData { - int keycode; - int modifierState; - int gestureType; + Trigger trigger; - // App launch parameters: Only set if gestureType is KEY_GESTURE_TYPE_LAUNCH_APPLICATION + int gestureType; + // App launch parameters (Only set if gestureType is LAUNCH_APPLICATION) String appLaunchCategory; String appLaunchRole; String appLaunchPackageName; String appLaunchClassName; + + parcelable KeyTrigger { + int keycode; + int modifierState; + } + + parcelable TouchpadGestureTrigger { + int gestureType; + } + + union Trigger { + KeyTrigger key; + TouchpadGestureTrigger touchpadGesture; + } } diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 39dddb723cb9..1b96224f03da 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -266,19 +266,19 @@ interface IInputManager { @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") - int addCustomInputGesture(in AidlInputGestureData data); + int addCustomInputGesture(int userId, in AidlInputGestureData data); @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") - int removeCustomInputGesture(in AidlInputGestureData data); + int removeCustomInputGesture(int userId, in AidlInputGestureData data); @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") - void removeAllCustomInputGestures(); + void removeAllCustomInputGestures(int userId); - AidlInputGestureData[] getCustomInputGestures(); + AidlInputGestureData[] getCustomInputGestures(int userId); AidlInputGestureData[] getAppLaunchBookmarks(); } diff --git a/core/java/android/hardware/input/InputGestureData.java b/core/java/android/hardware/input/InputGestureData.java index 5ab73cee9641..ee0a2a9cf88c 100644 --- a/core/java/android/hardware/input/InputGestureData.java +++ b/core/java/android/hardware/input/InputGestureData.java @@ -35,20 +35,40 @@ import java.util.Objects; */ public final class InputGestureData { + public static final int TOUCHPAD_GESTURE_TYPE_UNKNOWN = 0; + public static final int TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP = 1; + @NonNull private final AidlInputGestureData mInputGestureData; - public InputGestureData(AidlInputGestureData inputGestureData) { + public InputGestureData(@NonNull AidlInputGestureData inputGestureData) { this.mInputGestureData = inputGestureData; validate(); } /** Returns the trigger information for this input gesture */ public Trigger getTrigger() { - if (mInputGestureData.keycode != KeyEvent.KEYCODE_UNKNOWN) { - return new KeyTrigger(mInputGestureData.keycode, mInputGestureData.modifierState); + switch (mInputGestureData.trigger.getTag()) { + case AidlInputGestureData.Trigger.Tag.key: { + AidlInputGestureData.KeyTrigger trigger = mInputGestureData.trigger.getKey(); + if (trigger == null) { + throw new RuntimeException("InputGestureData is corrupted, null key trigger!"); + } + return createKeyTrigger(trigger.keycode, trigger.modifierState); + } + case AidlInputGestureData.Trigger.Tag.touchpadGesture: { + AidlInputGestureData.TouchpadGestureTrigger trigger = + mInputGestureData.trigger.getTouchpadGesture(); + if (trigger == null) { + throw new RuntimeException( + "InputGestureData is corrupted, null touchpad trigger!"); + } + return createTouchpadTrigger(trigger.gestureType); + } + default: + throw new RuntimeException("InputGestureData is corrupted, invalid trigger type!"); + } - throw new RuntimeException("InputGestureData is corrupted, invalid trigger type!"); } /** Returns the action to perform for this input gesture */ @@ -127,9 +147,15 @@ public final class InputGestureData { "No app launch data for system action launch application"); } AidlInputGestureData data = new AidlInputGestureData(); + data.trigger = new AidlInputGestureData.Trigger(); if (mTrigger instanceof KeyTrigger keyTrigger) { - data.keycode = keyTrigger.getKeycode(); - data.modifierState = keyTrigger.getModifierState(); + data.trigger.setKey(new AidlInputGestureData.KeyTrigger()); + data.trigger.getKey().keycode = keyTrigger.getKeycode(); + data.trigger.getKey().modifierState = keyTrigger.getModifierState(); + } else if (mTrigger instanceof TouchpadTrigger touchpadTrigger) { + data.trigger.setTouchpadGesture(new AidlInputGestureData.TouchpadGestureTrigger()); + data.trigger.getTouchpadGesture().gestureType = + touchpadTrigger.getTouchpadGestureType(); } else { throw new IllegalArgumentException("Invalid trigger type!"); } @@ -163,30 +189,12 @@ public final class InputGestureData { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InputGestureData that = (InputGestureData) o; - return mInputGestureData.keycode == that.mInputGestureData.keycode - && mInputGestureData.modifierState == that.mInputGestureData.modifierState - && mInputGestureData.gestureType == that.mInputGestureData.gestureType - && Objects.equals(mInputGestureData.appLaunchCategory, that.mInputGestureData.appLaunchCategory) - && Objects.equals(mInputGestureData.appLaunchRole, that.mInputGestureData.appLaunchRole) - && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName) - && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName); + return Objects.equals(mInputGestureData, that.mInputGestureData); } @Override public int hashCode() { - int _hash = 1; - _hash = 31 * _hash + mInputGestureData.keycode; - _hash = 31 * _hash + mInputGestureData.modifierState; - _hash = 31 * _hash + mInputGestureData.gestureType; - _hash = 31 * _hash + (mInputGestureData.appLaunchCategory != null - ? mInputGestureData.appLaunchCategory.hashCode() : 0); - _hash = 31 * _hash + (mInputGestureData.appLaunchRole != null - ? mInputGestureData.appLaunchRole.hashCode() : 0); - _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null - ? mInputGestureData.appLaunchPackageName.hashCode() : 0); - _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null - ? mInputGestureData.appLaunchPackageName.hashCode() : 0); - return _hash; + return mInputGestureData.hashCode(); } public interface Trigger { @@ -197,6 +205,11 @@ public final class InputGestureData { return new KeyTrigger(keycode, modifierState); } + /** Creates a input gesture trigger based on a touchpad gesture */ + public static Trigger createTouchpadTrigger(int touchpadGestureType) { + return new TouchpadTrigger(touchpadGestureType); + } + /** Key based input gesture trigger */ public static class KeyTrigger implements Trigger { private static final int SHORTCUT_META_MASK = @@ -242,6 +255,43 @@ public final class InputGestureData { } } + /** Touchpad based input gesture trigger */ + public static class TouchpadTrigger implements Trigger { + private final int mTouchpadGestureType; + + private TouchpadTrigger(int touchpadGestureType) { + if (touchpadGestureType != TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP) { + throw new IllegalArgumentException( + "Invalid touchpadGestureType = " + touchpadGestureType); + } + mTouchpadGestureType = touchpadGestureType; + } + + public int getTouchpadGestureType() { + return mTouchpadGestureType; + } + + @Override + public String toString() { + return "TouchpadTrigger{" + + "mTouchpadGestureType=" + mTouchpadGestureType + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TouchpadTrigger that = (TouchpadTrigger) o; + return mTouchpadGestureType == that.mTouchpadGestureType; + } + + @Override + public int hashCode() { + return Objects.hashCode(mTouchpadGestureType); + } + } + /** Data for action to perform when input gesture is triggered */ public record Action(@KeyGestureEvent.KeyGestureType int keyGestureType, @Nullable AppLaunchData appLaunchData) { diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 2051dbe7fb2e..9050ae235ce7 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -34,6 +34,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserHandleAware; import android.annotation.UserIdInt; import android.app.ActivityThread; import android.compat.annotation.ChangeId; @@ -1487,12 +1488,13 @@ public final class InputManager { */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) @CustomInputGestureResult + @UserHandleAware public int addCustomInputGesture(@NonNull InputGestureData inputGestureData) { if (!enableCustomizableInputGestures()) { return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } try { - return mIm.addCustomInputGesture(inputGestureData.getAidlData()); + return mIm.addCustomInputGesture(mContext.getUserId(), inputGestureData.getAidlData()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1509,12 +1511,14 @@ public final class InputManager { */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) @CustomInputGestureResult + @UserHandleAware public int removeCustomInputGesture(@NonNull InputGestureData inputGestureData) { if (!enableCustomizableInputGestures()) { return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER; } try { - return mIm.removeCustomInputGesture(inputGestureData.getAidlData()); + return mIm.removeCustomInputGesture(mContext.getUserId(), + inputGestureData.getAidlData()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1525,12 +1529,13 @@ public final class InputManager { * @hide */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) + @UserHandleAware public void removeAllCustomInputGestures() { if (!enableCustomizableInputGestures()) { return; } try { - mIm.removeAllCustomInputGestures(); + mIm.removeAllCustomInputGestures(mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1540,13 +1545,14 @@ public final class InputManager { * * @hide */ + @UserHandleAware public List<InputGestureData> getCustomInputGestures() { List<InputGestureData> result = new ArrayList<>(); if (!enableCustomizableInputGestures()) { return result; } try { - for (AidlInputGestureData data : mIm.getCustomInputGestures()) { + for (AidlInputGestureData data : mIm.getCustomInputGestures(mContext.getUserId())) { result.add(new InputGestureData(data)); } } catch (RemoteException e) { diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index c4566981d3b5..96f6ad117035 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -30,6 +30,7 @@ import static com.android.hardware.input.Flags.mouseSwapPrimaryButton; import static com.android.hardware.input.Flags.touchpadTapDragging; import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut; import static com.android.hardware.input.Flags.touchpadVisualizer; +import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; import static com.android.input.flags.Flags.enableInputFilterRustImpl; import static com.android.input.flags.Flags.keyboardRepeatKeys; @@ -386,7 +387,7 @@ public class InputSettings { * @hide */ public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() { - return enableCustomizableInputGestures() && touchpadThreeFingerTapShortcut(); + return isCustomizableInputGesturesFeatureFlagEnabled() && touchpadThreeFingerTapShortcut(); } /** @@ -1132,4 +1133,18 @@ public class InputSettings { Settings.Secure.KEY_REPEAT_DELAY_MS, delayTimeMillis, UserHandle.USER_CURRENT); } + + /** + * Whether "Customizable key gestures" feature flag is enabled. + * + * <p> + * ‘Customizable key gestures’ is a feature which allows users to customize key based + * shortcuts on the physical keyboard. + * </p> + * + * @hide + */ + public static boolean isCustomizableInputGesturesFeatureFlagEnabled() { + return enableCustomizableInputGestures() && useKeyGestureEventHandler(); + } } diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index f9cb94aca54e..38e32c61c99e 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -141,7 +141,7 @@ flag { flag { name: "keyboard_a11y_shortcut_control" namespace: "input" - description: "Adds shortcuts to toggle and control a11y features" + description: "Adds shortcuts to toggle and control a11y keyboard features" bug: "373458181" } @@ -165,3 +165,10 @@ flag { description: "Turns three-finger touchpad taps into a customizable shortcut." bug: "365063048" } + +flag { + name: "enable_talkback_and_magnifier_key_gestures" + namespace: "input" + description: "Adds key gestures for talkback and magnifier" + bug: "375277034" +}
\ No newline at end of file diff --git a/core/java/android/hardware/location/OWNERS b/core/java/android/hardware/location/OWNERS index 747f90947b9c..340d6f2eb08c 100644 --- a/core/java/android/hardware/location/OWNERS +++ b/core/java/android/hardware/location/OWNERS @@ -9,4 +9,4 @@ wyattriley@google.com yuhany@google.com # ContextHub team -per-file *ContextHub*,*NanoApp* = file:platform/system/chre:/OWNERS +per-file Android.bp,*Hub*,*NanoApp* = file:platform/system/chre:/OWNERS diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index c6fd0ee3c80d..85cf9491287c 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -1750,8 +1750,8 @@ public class SoundTrigger { * internals, typically during enrollment. * @return the same Builder instance. */ - public @NonNull Builder setData(@Nullable byte[] data) { - mData = data; + public @NonNull Builder setData(@NonNull byte[] data) { + mData = requireNonNull(data, "Data must not be null"); return this; } diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java index c7f8878f104e..f0e12ca644dd 100644 --- a/core/java/android/os/AggregateBatteryConsumer.java +++ b/core/java/android/os/AggregateBatteryConsumer.java @@ -85,7 +85,7 @@ public final class AggregateBatteryConsumer extends BatteryConsumer { throw new XmlPullParserException("Invalid XML parser state"); } - consumerBuilder.setConsumedPower( + consumerBuilder.addConsumedPower( parser.getAttributeDouble(null, BatteryUsageStats.XML_ATTR_POWER)); while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals( @@ -132,11 +132,19 @@ public final class AggregateBatteryConsumer extends BatteryConsumer { } /** + * Adds the total power included in this aggregate. + */ + public Builder addConsumedPower(double consumedPowerMah) { + mData.putDouble(COLUMN_INDEX_CONSUMED_POWER, + mData.getDouble(COLUMN_INDEX_CONSUMED_POWER) + consumedPowerMah); + return this; + } + + /** * Adds power and usage duration from the supplied AggregateBatteryConsumer. */ public void add(AggregateBatteryConsumer aggregateBatteryConsumer) { - setConsumedPower(mData.getDouble(COLUMN_INDEX_CONSUMED_POWER) - + aggregateBatteryConsumer.getConsumedPower()); + addConsumedPower(aggregateBatteryConsumer.getConsumedPower()); mPowerComponentsBuilder.addPowerAndDuration(aggregateBatteryConsumer.mPowerComponents); } diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index b0ecca790b80..14b67f64b6da 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -1064,7 +1064,9 @@ public abstract class BatteryConsumer { * @param componentId The ID of the power component, e.g. * {@link BatteryConsumer#POWER_COMPONENT_CPU}. * @param componentPower Amount of consumed power in mAh. + * @deprecated use {@link #addConsumedPower} */ + @Deprecated @NonNull public T setConsumedPower(@PowerComponentId int componentId, double componentPower) { return setConsumedPower(componentId, componentPower, POWER_MODEL_POWER_PROFILE); @@ -1076,7 +1078,9 @@ public abstract class BatteryConsumer { * @param componentId The ID of the power component, e.g. * {@link BatteryConsumer#POWER_COMPONENT_CPU}. * @param componentPower Amount of consumed power in mAh. + * @deprecated use {@link #addConsumedPower} */ + @Deprecated @SuppressWarnings("unchecked") @NonNull public T setConsumedPower(@PowerComponentId int componentId, double componentPower, @@ -1104,6 +1108,21 @@ public abstract class BatteryConsumer { @SuppressWarnings("unchecked") @NonNull + public T addConsumedPower(@PowerComponentId int componentId, double componentPower) { + mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED), + componentPower, POWER_MODEL_UNDEFINED); + return (T) this; + } + + @SuppressWarnings("unchecked") + @NonNull + public T addConsumedPower(Key key, double componentPower) { + mPowerComponentsBuilder.addConsumedPower(key, componentPower, POWER_MODEL_UNDEFINED); + return (T) this; + } + + @SuppressWarnings("unchecked") + @NonNull public T addConsumedPower(Key key, double componentPower, @PowerModel int powerModel) { mPowerComponentsBuilder.addConsumedPower(key, componentPower, powerModel); return (T) this; @@ -1115,7 +1134,9 @@ public abstract class BatteryConsumer { * @param componentId The ID of the power component, e.g. * {@link UidBatteryConsumer#POWER_COMPONENT_CPU}. * @param componentUsageTimeMillis Amount of time in microseconds. + * @deprecated use {@link #addUsageDurationMillis} */ + @Deprecated @SuppressWarnings("unchecked") @NonNull public T setUsageDurationMillis(@PowerComponentId int componentId, @@ -1126,6 +1147,7 @@ public abstract class BatteryConsumer { return (T) this; } + @Deprecated @SuppressWarnings("unchecked") @NonNull public T setUsageDurationMillis(Key key, long componentUsageTimeMillis) { @@ -1133,6 +1155,14 @@ public abstract class BatteryConsumer { return (T) this; } + @NonNull + public T addUsageDurationMillis(@PowerComponentId int componentId, + long componentUsageTimeMillis) { + mPowerComponentsBuilder.addUsageDurationMillis( + getKey(componentId, PROCESS_STATE_UNSPECIFIED), componentUsageTimeMillis); + return (T) this; + } + @SuppressWarnings("unchecked") @NonNull public T addUsageDurationMillis(Key key, long componentUsageTimeMillis) { diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index 5ae425f184c7..72e4cef2f6eb 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -209,7 +209,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { } builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) - .setConsumedPower(totalPowerMah); + .addConsumedPower(totalPowerMah); mAggregateBatteryConsumers = new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT]; diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 66f4198ad31c..6cfbf4ebf661 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -93,9 +93,7 @@ public final class MessageQueue { * system processes and provides a higher level of concurrency and higher enqueue throughput * than the legacy implementation. */ - private static boolean sUseConcurrent; - - private static boolean sUseConcurrentInitialized = false; + private boolean mUseConcurrent; @RavenwoodRedirect private native static long nativeInit(); @@ -112,10 +110,7 @@ public final class MessageQueue { private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { - if (!sUseConcurrentInitialized) { - sUseConcurrent = UserHandle.isCore(Process.myUid()); - sUseConcurrentInitialized = true; - } + mUseConcurrent = UserHandle.isCore(Process.myUid()); mQuitAllowed = quitAllowed; mPtr = nativeInit(); } @@ -158,7 +153,7 @@ public final class MessageQueue { * @return True if the looper is idle. */ public boolean isIdle() { - if (sUseConcurrent) { + if (mUseConcurrent) { final long now = SystemClock.uptimeMillis(); if (stackHasMessages(null, 0, null, null, now, mMatchDeliverableMessages, false)) { @@ -208,7 +203,7 @@ public final class MessageQueue { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mIdleHandlersLock) { mIdleHandlers.add(handler); } @@ -229,7 +224,7 @@ public final class MessageQueue { * @param handler The IdleHandler to be removed. */ public void removeIdleHandler(@NonNull IdleHandler handler) { - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mIdleHandlersLock) { mIdleHandlers.remove(handler); } @@ -252,7 +247,7 @@ public final class MessageQueue { * @hide */ public boolean isPolling() { - if (sUseConcurrent) { + if (mUseConcurrent) { // If the loop is quitting then it must not be idling. // We can assume mPtr != 0 when sQuitting is false. return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr); @@ -303,7 +298,7 @@ public final class MessageQueue { throw new IllegalArgumentException("listener must not be null"); } - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mFileDescriptorRecordsLock) { updateOnFileDescriptorEventListenerLocked(fd, events, listener); } @@ -331,7 +326,7 @@ public final class MessageQueue { if (fd == null) { throw new IllegalArgumentException("fd must not be null"); } - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mFileDescriptorRecordsLock) { updateOnFileDescriptorEventListenerLocked(fd, 0, null); } @@ -388,7 +383,7 @@ public final class MessageQueue { final int oldWatchedEvents; final OnFileDescriptorEventListener listener; final int seq; - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mFileDescriptorRecordsLock) { record = mFileDescriptorRecords.get(fd); if (record == null) { @@ -708,7 +703,7 @@ public final class MessageQueue { @UnsupportedAppUsage Message next() { - if (sUseConcurrent) { + if (mUseConcurrent) { return nextConcurrent(); } @@ -834,7 +829,7 @@ public final class MessageQueue { throw new IllegalStateException("Main thread not allowed to quit."); } - if (sUseConcurrent) { + if (mUseConcurrent) { synchronized (mIdleHandlersLock) { if (sQuitting.compareAndSet(this, false, true)) { if (safe) { @@ -898,7 +893,7 @@ public final class MessageQueue { private int postSyncBarrier(long when) { // Enqueue a new sync barrier token. // We don't need to wake the queue because the purpose of a barrier is to stall it. - if (sUseConcurrent) { + if (mUseConcurrent) { final int token = mNextBarrierTokenAtomic.getAndIncrement(); // b/376573804: apps and tests may expect to be able to use reflection @@ -991,7 +986,7 @@ public final class MessageQueue { public void removeSyncBarrier(int token) { // Remove a sync barrier token from the queue. // If the queue is no longer stalled by a barrier then wake it. - if (sUseConcurrent) { + if (mUseConcurrent) { boolean removed; MessageNode first; final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token); @@ -1058,7 +1053,7 @@ public final class MessageQueue { throw new IllegalArgumentException("Message must have a target."); } - if (sUseConcurrent) { + if (mUseConcurrent) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } @@ -1187,7 +1182,7 @@ public final class MessageQueue { if (h == null) { return false; } - if (sUseConcurrent) { + if (mUseConcurrent) { return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false); } @@ -1219,7 +1214,7 @@ public final class MessageQueue { if (h == null) { return false; } - if (sUseConcurrent) { + if (mUseConcurrent) { return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, false); @@ -1253,7 +1248,7 @@ public final class MessageQueue { if (h == null) { return false; } - if (sUseConcurrent) { + if (mUseConcurrent) { return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false); } @@ -1285,7 +1280,7 @@ public final class MessageQueue { if (h == null) { return false; } - if (sUseConcurrent) { + if (mUseConcurrent) { return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false); } synchronized (this) { @@ -1304,7 +1299,7 @@ public final class MessageQueue { if (h == null) { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true); return; } @@ -1355,7 +1350,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true); return; } @@ -1407,7 +1402,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true); return; } @@ -1470,7 +1465,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true); return; } @@ -1532,7 +1527,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true); return; } @@ -1594,7 +1589,7 @@ public final class MessageQueue { return; } - if (sUseConcurrent) { + if (mUseConcurrent) { findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true); return; } @@ -1742,7 +1737,7 @@ public final class MessageQueue { @NeverCompile void dump(Printer pw, String prefix, Handler h) { - if (sUseConcurrent) { + if (mUseConcurrent) { long now = SystemClock.uptimeMillis(); int n = 0; @@ -1803,7 +1798,7 @@ public final class MessageQueue { @NeverCompile void dumpDebug(ProtoOutputStream proto, long fieldId) { - if (sUseConcurrent) { + if (mUseConcurrent) { final long messageQueueToken = proto.start(fieldId); StackNode node = (StackNode) sState.getVolatile(this); diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java index f4e3f3b6e430..d116e0737c46 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -439,8 +439,8 @@ class PowerComponents { } final BatteryConsumer.Key key = builder.mData.layout.getKey(componentId, processState, screenState, powerState); - builder.setConsumedPower(key, powerMah, model); - builder.setUsageDurationMillis(key, durationMs); + builder.addConsumedPower(key, powerMah, model); + builder.addUsageDurationMillis(key, durationMs); break; } } @@ -468,6 +468,10 @@ class PowerComponents { } } + /** + * @deprecated use {@link #addConsumedPower(BatteryConsumer.Key, double, int)} + */ + @Deprecated @NonNull public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower, int powerModel) { @@ -489,6 +493,10 @@ class PowerComponents { return this; } + /** + * @deprecated use {@link #addUsageDurationMillis(BatteryConsumer.Key, long)} + */ + @Deprecated @NonNull public Builder setUsageDurationMillis(BatteryConsumer.Key key, long componentUsageDurationMillis) { diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index f893739fcf28..976bfe41ba45 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -210,12 +210,6 @@ public final class UidBatteryConsumer extends BatteryConsumer { serializer.attribute(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE, packageWithHighestDrain); } - serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND, - getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND)); - serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND, - getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND)); - serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE, - getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE)); mPowerComponents.writeToXml(serializer); serializer.endTag(null, BatteryUsageStats.XML_TAG_UID); } @@ -235,13 +229,6 @@ public final class UidBatteryConsumer extends BatteryConsumer { consumerBuilder.setPackageWithHighestDrain( parser.getAttributeValue(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE)); - consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND, - parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND)); - consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, - parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND)); - consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE, - parser.getAttributeLong(null, - BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE)); while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) && eventType != XmlPullParser.END_DOCUMENT) { @@ -335,7 +322,11 @@ public final class UidBatteryConsumer extends BatteryConsumer { /** * Sets the duration, in milliseconds, that this UID was active in a particular process * state, such as foreground service. + * + * @deprecated time in process is now derived from the + * {@link BatteryConsumer#POWER_COMPONENT_BASE} duration */ + @Deprecated @NonNull public Builder setTimeInProcessStateMs(@ProcessState int state, long timeInProcessStateMs) { Key key = getKey(POWER_COMPONENT_BASE, state); diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 1ab48a22cbbd..09b96da39a84 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -181,6 +181,5 @@ interface IStorageManager { * device's useful lifetime remains. If no information is available, -1 * is returned. */ - @EnforcePermission("READ_PRIVILEGED_PHONE_STATE") int getInternalStorageRemainingLifetime() = 99; } diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 6a4932211f27..9e0d0e195a96 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -53,6 +53,24 @@ flag { } flag { + name: "enhanced_confirmation_in_call_apis_enabled" + is_exported: true + is_fixed_read_only: true + namespace: "permissions" + description: "enable enhanced confirmation incall apis" + bug: "310220212" +} + +flag { + name: "unknown_call_package_install_blocking_enabled" + is_exported: true + is_fixed_read_only: true + namespace: "permissions" + description: "enable the blocking of certain app installs during an unknown call" + bug: "310220212" +} + +flag { name: "op_enable_mobile_data_by_user" is_exported: true namespace: "permissions" @@ -332,3 +350,38 @@ flag { description: "Enables ExtServices to leverage TextClassifier for OTP detection" bug: "351976749" } + +flag { + name: "health_connect_backup_restore_permission_enabled" + is_fixed_read_only: true + namespace: "health_connect" + description: "This flag protects the permission that is required to call Health Connect backup and restore apis" + bug: "376014879" # android_fr bug +} + +flag { + name: "enable_aiai_proxied_text_classifiers" + is_fixed_read_only: true + is_exported: true + namespace: "permissions" + description: "Enables the AiAi to utilize the default OTP text classifier that is also used by ExtServices" + bug: "377229653" +} + +flag { + name: "enable_sqlite_appops_accesses" + is_fixed_read_only: true + is_exported: true + namespace: "permissions" + description: "Enables SQlite for recording discrete and historical AppOp accesses" + bug: "377584611" +} + +flag { + name: "ranging_permission_enabled" + is_fixed_read_only: true + is_exported: true + namespace: "uwb" + description: "This fixed read-only flag is used to enable new ranging permission for all ranging use cases." + bug: "370977414" +} diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS index 0809de25b45c..ce79f5d0c669 100644 --- a/core/java/android/print/OWNERS +++ b/core/java/android/print/OWNERS @@ -2,3 +2,4 @@ anothermark@google.com kumarashishg@google.com +bmgordon@google.com diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS index 0809de25b45c..ce79f5d0c669 100644 --- a/core/java/android/printservice/OWNERS +++ b/core/java/android/printservice/OWNERS @@ -2,3 +2,4 @@ anothermark@google.com kumarashishg@google.com +bmgordon@google.com diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig index 5995760a41ec..66e1f38621ae 100644 --- a/core/java/android/security/responsible_apis_flags.aconfig +++ b/core/java/android/security/responsible_apis_flags.aconfig @@ -67,6 +67,7 @@ flag { name: "aapm_api" namespace: "responsible_apis" description: "Android Advanced Protection Mode Service and Manager" + is_exported: true bug: "352420507" is_fixed_read_only: true } diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig index 34e311f8c932..d065939bc19d 100644 --- a/core/java/android/service/notification/flags.aconfig +++ b/core/java/android/service/notification/flags.aconfig @@ -65,4 +65,11 @@ flag { namespace: "systemui" description: "Allows the NAS to create and modify conversation notifications" bug: "373599715" -}
\ No newline at end of file +} + +flag { + name: "notification_regroup_on_classification" + namespace: "systemui" + description: "This flag controls regrouping after notification classification" + bug: "372775153" +} diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 2e660fc1f157..7d79fd3d44ea 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -1164,7 +1164,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { public boolean startRecognition(@RecognitionFlags int recognitionFlags) { if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")"); synchronized (mLock) { - return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK; + return startRecognitionLocked(recognitionFlags, /* data= */new byte[0]) == STATUS_OK; } } @@ -1496,8 +1496,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } @GuardedBy("mLock") - private int startRecognitionLocked(int recognitionFlags, - @Nullable byte[] data) { + @SuppressWarnings("FlaggedApi") // RecognitionConfig.Builder is available internally. + private int startRecognitionLocked(int recognitionFlags, @NonNull byte[] data) { if (DBG) { Slog.d(TAG, "startRecognition(" + recognitionFlags diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index e830d89d3116..02923eda308e 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -202,3 +202,10 @@ flag { description: "Deprecate the Paint#elegantTextHeight API and stick it to true" bug: "349519475" } + +flag { + name: "vertical_text_layout" + namespace: "text" + description: "Make Paint class work for vertical layout text." + bug: "355296926" +} diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 8358b9a51adb..1dd9d46fdfb7 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -75,8 +75,7 @@ import java.net.UnknownHostException; @android.ravenwood.annotation.RavenwoodClassLoadHook( "com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded") // Uncomment the following annotation to switch to the Java substitution version. -//@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( -// "com.android.platform.test.ravenwood.nativesubstitution.Log_host") +@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_host") public final class Log { /** @hide */ @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE}) @@ -250,6 +249,7 @@ public final class Log { * tag limit of concern after this API level. */ @FastNative + @android.ravenwood.annotation.RavenwoodRedirect public static native boolean isLoggable(@Nullable String tag, @Level int level); /** @@ -425,6 +425,7 @@ public final class Log { * @hide */ @UnsupportedAppUsage + @android.ravenwood.annotation.RavenwoodRedirect public static native int println_native(int bufID, int priority, String tag, String msg); /** @@ -452,6 +453,7 @@ public final class Log { * Return the maximum payload the log daemon accepts without truncation. * @return LOGGER_ENTRY_MAX_PAYLOAD. */ + @android.ravenwood.annotation.RavenwoodRedirect private static native int logger_entry_max_payload_native(); /** diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java index b80146505a1b..19e0913fbc65 100644 --- a/core/java/android/view/ImeBackAnimationController.java +++ b/core/java/android/view/ImeBackAnimationController.java @@ -33,6 +33,7 @@ import android.util.Log; import android.view.animation.BackGestureInterpolator; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.view.inputmethod.Flags; import android.view.inputmethod.ImeTracker; import android.window.BackEvent; import android.window.OnBackAnimationCallback; @@ -142,9 +143,15 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { // control has been cancelled by the system. This can happen in multi-window mode for // example (i.e. split-screen or activity-embedding) notifyHideIme(); - return; + } else { + startPostCommitAnim(/*hideIme*/ true); + } + if (Flags.refactorInsetsController()) { + // Unregister all IME back callbacks so that back events are sent to the next callback + // even while the hide animation is playing + mInsetsController.getHost().getInputMethodManager().getImeOnBackInvokedDispatcher() + .preliminaryClear(); } - startPostCommitAnim(/*hideIme*/ true); } private void setPreCommitProgress(float progress) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 25d2246424de..26ca813a9caa 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1344,6 +1344,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean fromPredictiveBack) { final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN; + if (Flags.refactorInsetsController() && !fromPredictiveBack && !visible + && (types & ime()) != 0 && (mRequestedVisibleTypes & ime()) != 0) { + // Clear IME back callbacks if a IME hide animation is requested + mHost.getInputMethodManager().getImeOnBackInvokedDispatcher().preliminaryClear(); + } // Basically, we accept the requested visibilities from the upstream callers... setRequestedVisibleTypes(visible ? types : 0, types); @@ -1921,6 +1926,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final @InsetsType int requestedVisibleTypes = (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask); if (mRequestedVisibleTypes != requestedVisibleTypes) { + if (Flags.refactorInsetsController() && (mRequestedVisibleTypes & ime()) == 0 + && (requestedVisibleTypes & ime()) != 0) { + // In case the IME back callbacks have been preliminarily cleared before, let's + // reregister them. This can happen if an IME hide animation was interrupted and the + // IME is requested to be shown again. + getHost().getInputMethodManager().getImeOnBackInvokedDispatcher() + .undoPreliminaryClear(); + } ProtoLog.d(IME_INSETS_CONTROLLER, "Setting requestedVisibleTypes to %d (was %d)", requestedVisibleTypes, mRequestedVisibleTypes); mRequestedVisibleTypes = requestedVisibleTypes; diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java index 59c2598f00f0..5e1eadae0953 100644 --- a/core/java/android/view/RoundScrollbarRenderer.java +++ b/core/java/android/view/RoundScrollbarRenderer.java @@ -35,7 +35,9 @@ import android.view.flags.Flags; * @hide */ public class RoundScrollbarRenderer { - private static final String BLUECHIP_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled"; + /** @hide */ + public static final String BLUECHIP_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled"; + // The range of the scrollbar position represented as an angle in degrees. private static final float SCROLLBAR_ANGLE_RANGE = 28.8f; private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 26.3f; // 90% diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b8b22e283175..df54d310059f 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -312,6 +312,7 @@ public final class SurfaceControl implements Parcelable { private static native void nativeNotifyShutdown(); private static native void nativeSetLuts(long transactionObj, long nativeObject, float[] buffers, int[] slots, int[] dimensions, int[] sizes, int[] samplingKeys); + private static native void nativeEnableDebugLogCallPoints(long transactionObj); /** * Transforms that can be applied to buffers as they are displayed to a window. @@ -4605,7 +4606,6 @@ public final class SurfaceControl implements Parcelable { } /** - * TODO(b/366484871): To be removed once we have some logging in native * This is called when BlastBufferQueue.mergeWithNextTransaction() is called from java, and * for the purposes of logging that path. */ @@ -4616,6 +4616,7 @@ public final class SurfaceControl implements Parcelable { if (mCalls != null) { mCalls.clear(); } + nativeEnableDebugLogCallPoints(mNativeObject); } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 5e5f33ef41f8..fe4f0cd73fff 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -857,8 +857,10 @@ public class AccessibilityNodeInfo implements Parcelable { * <p> * The data can be retrieved from the {@code Bundle} returned by {@link #getExtras()} using this * string as a key for {@link Bundle#getParcelableArray(String, Class)}. The - * {@link android.graphics.RectF} will be null for characters that either do not exist or are - * off the screen. + * {@link android.graphics.RectF} will be {@code null} for characters that either do not exist + * or are off the screen. + * <p> + * Note that character locations returned are modified by changes in display magnification. * * {@see #refreshWithExtraData(String, Bundle)} */ @@ -866,6 +868,36 @@ public class AccessibilityNodeInfo implements Parcelable { "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_KEY"; /** + * Key used to request and locate extra data for text character location in + * window coordinates. This key requests that an array of + * {@link android.graphics.RectF}s be added to the extras. This request is made + * with {@link #refreshWithExtraData(String, Bundle)}. The arguments taken by + * this request are two integers: + * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX} and + * {@link #EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH}. The starting index + * must be valid inside the CharSequence returned by {@link #getText()}, and + * the length must be positive. + * <p> + * Providers may advertise that they support text characters in window coordinates using + * {@link #setAvailableExtraData(List)}. Services may check if an implementation supports text + * characters in window coordinates with {@link #getAvailableExtraData()}. + * <p> + * The data can be retrieved from the {@code Bundle} returned by + * {@link #getExtras()} using this string as a key for + * {@link Bundle#getParcelableArray(String, Class)}. The + * {@link android.graphics.RectF} will be {@code null} for characters that either do + * not exist or are outside of the window bounds. + * <p> + * Note that character locations in window bounds are not modified by + * changes in display magnification. + * + * {@see #refreshWithExtraData(String, Bundle)} + */ + @FlaggedApi(Flags.FLAG_A11Y_CHARACTER_IN_WINDOW_API) + public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY = + "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY"; + + /** * Integer argument specifying the start index of the requested text location data. Must be * valid inside the CharSequence returned by {@link #getText()}. * @@ -5460,26 +5492,6 @@ public class AccessibilityNodeInfo implements Parcelable { } } - private static String getExpandedStateSymbolicName(int state) { - if (Flags.a11yExpansionStateApi()) { - switch (state) { - case EXPANDED_STATE_UNDEFINED: - return "EXPANDED_STATE_UNDEFINED"; - case EXPANDED_STATE_COLLAPSED: - return "EXPANDED_STATE_COLLAPSED"; - case EXPANDED_STATE_PARTIAL: - return "EXPANDED_STATE_PARTIAL"; - case EXPANDED_STATE_FULL: - return "EXPANDED_STATE_FULL"; - default: - throw new IllegalArgumentException("Unknown expanded state: " + state); - } - } else { - // TODO(b/362782158) Remove when flag is removed. - return ""; - } - } - private static boolean canPerformRequestOverConnection(int connectionId, int windowId, long accessibilityNodeId) { final boolean hasWindowId = windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; @@ -5573,20 +5585,12 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; maxTextLength: ").append(mMaxTextLength); builder.append("; stateDescription: ").append(mStateDescription); builder.append("; contentDescription: ").append(mContentDescription); - if (Flags.supplementalDescription()) { - builder.append("; supplementalDescription: ").append(mSupplementalDescription); - } builder.append("; tooltipText: ").append(mTooltipText); builder.append("; containerTitle: ").append(mContainerTitle); builder.append("; viewIdResName: ").append(mViewIdResourceName); builder.append("; uniqueId: ").append(mUniqueId); - builder.append("; expandedState: ").append(getExpandedStateSymbolicName(mExpandedState)); - builder.append("; checkable: ").append(isCheckable()); builder.append("; checked: ").append(isChecked()); - if (Flags.a11yIsRequiredApi()) { - builder.append("; required: ").append(isFieldRequired()); - } builder.append("; focusable: ").append(isFocusable()); builder.append("; focused: ").append(isFocused()); builder.append("; selected: ").append(isSelected()); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 7dc77b175c79..73f9d9fc23dc 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -3667,6 +3667,14 @@ public final class InputMethodManager { } /** + * Returns the ImeOnBackInvokedDispatcher. + * @hide + */ + public ImeOnBackInvokedDispatcher getImeOnBackInvokedDispatcher() { + return mImeDispatcher; + } + + /** * Check the next served view if needs to start input. */ @GuardedBy("mH") diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index be91cfb9ebf8..a67ae7c96a54 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -17,8 +17,10 @@ package android.view.inputmethod; import android.annotation.AnyThread; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringRes; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; @@ -87,8 +89,17 @@ public final class InputMethodSubtype implements Parcelable { private final boolean mIsAsciiCapable; private final int mSubtypeHashCode; private final int mSubtypeIconResId; + /** The subtype name resource identifier. */ private final int mSubtypeNameResId; + /** The untranslatable name of the subtype. */ + @NonNull private final CharSequence mSubtypeNameOverride; + /** The layout label string resource identifier. */ + @StringRes + private final int mLayoutLabelResId; + /** The non-localized layout label. */ + @NonNull + private final CharSequence mLayoutLabelNonLocalized; private final String mPkLanguageTag; private final String mPkLayoutType; private final int mSubtypeId; @@ -176,6 +187,7 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameResId = subtypeNameResId; return this; } + /** The subtype name resource identifier. */ private int mSubtypeNameResId = 0; /** @@ -191,9 +203,56 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameOverride = nameOverride; return this; } + /** The untranslatable name of the subtype. */ + @NonNull private CharSequence mSubtypeNameOverride = ""; /** + * Sets the layout label string resource identifier. + * + * @param layoutLabelResId the layout label string resource identifier. + * + * @see #getLayoutDisplayName + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @NonNull + public InputMethodSubtypeBuilder setLayoutLabelResource( + @StringRes int layoutLabelResId) { + if (!Flags.imeSwitcherRevampApi()) { + return this; + } + mLayoutLabelResId = layoutLabelResId; + return this; + } + /** The layout label string resource identifier. */ + @StringRes + private int mLayoutLabelResId = 0; + + /** + * Sets the non-localized layout label. This is used as the layout display name if the + * {@link #getLayoutLabelResource layoutLabelResource} is not set ({@code 0}). + * + * @param layoutLabelNonLocalized the non-localized layout label. + * + * @see #getLayoutDisplayName + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @NonNull + public InputMethodSubtypeBuilder setLayoutLabelNonLocalized( + @NonNull CharSequence layoutLabelNonLocalized) { + if (!Flags.imeSwitcherRevampApi()) { + return this; + } + Objects.requireNonNull(layoutLabelNonLocalized, + "layoutLabelNonLocalized cannot be null"); + mLayoutLabelNonLocalized = layoutLabelNonLocalized; + return this; + } + /** The non-localized layout label. */ + @NonNull + private CharSequence mLayoutLabelNonLocalized = ""; + + /** * Sets the physical keyboard hint information, such as language and layout. * * The system can use the hint information to automatically configure the physical keyboard @@ -350,6 +409,8 @@ public final class InputMethodSubtype implements Parcelable { private InputMethodSubtype(InputMethodSubtypeBuilder builder) { mSubtypeNameResId = builder.mSubtypeNameResId; mSubtypeNameOverride = builder.mSubtypeNameOverride; + mLayoutLabelResId = builder.mLayoutLabelResId; + mLayoutLabelNonLocalized = builder.mLayoutLabelNonLocalized; mPkLanguageTag = builder.mPkLanguageTag; mPkLayoutType = builder.mPkLayoutType; mSubtypeIconResId = builder.mSubtypeIconResId; @@ -376,6 +437,9 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameResId = source.readInt(); CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); mSubtypeNameOverride = cs != null ? cs : ""; + mLayoutLabelResId = source.readInt(); + cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + mLayoutLabelNonLocalized = cs != null ? cs : ""; s = source.readString8(); mPkLanguageTag = s != null ? s : ""; s = source.readString8(); @@ -412,6 +476,24 @@ public final class InputMethodSubtype implements Parcelable { } /** + * Returns the layout label string resource identifier. + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @StringRes + public int getLayoutLabelResource() { + return mLayoutLabelResId; + } + + /** + * Returns the non-localized layout label. + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @NonNull + public CharSequence getLayoutLabelNonLocalized() { + return mLayoutLabelNonLocalized; + } + + /** * Returns the physical keyboard BCP-47 language tag. * * @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLanguageTag @@ -643,9 +725,47 @@ public final class InputMethodSubtype implements Parcelable { try { return String.format(subtypeNameString, replacementString); } catch (IllegalFormatException e) { - Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e); + Slog.w(TAG, "Found illegal format in subtype name(" + subtypeName + "): " + e); + return ""; + } + } + + /** + * Returns the layout display name. + * + * <p>If {@code layoutLabelResource} is non-zero (specified through + * {@link InputMethodSubtypeBuilder#setLayoutLabelResource setLayoutLabelResource}), the + * text generated from that resource will be returned. The localized string resource of the + * label should be capitalized for inclusion in UI lists. + * + * <p>If {@code layoutLabelResource} is zero, the framework returns the non-localized + * layout label, if specified through + * {@link InputMethodSubtypeBuilder#setLayoutLabelNonLocalized setLayoutLabelNonLocalized}. + * + * @param context The context used for getting the + * {@link android.content.pm.PackageManager PackageManager}. + * @param imeAppInfo The {@link ApplicationInfo} of the input method. + * @return the layout display name. + */ + @NonNull + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + public CharSequence getLayoutDisplayName(@NonNull Context context, + @NonNull ApplicationInfo imeAppInfo) { + if (!Flags.imeSwitcherRevampApi()) { + return ""; + } + Objects.requireNonNull(context, "context cannot be null"); + Objects.requireNonNull(imeAppInfo, "imeAppInfo cannot be null"); + if (mLayoutLabelResId == 0) { + return mLayoutLabelNonLocalized; + } + + final CharSequence subtypeLayoutName = context.getPackageManager().getText( + imeAppInfo.packageName, mLayoutLabelResId, imeAppInfo); + if (TextUtils.isEmpty(subtypeLayoutName)) { return ""; } + return subtypeLayoutName; } @Nullable @@ -778,6 +898,8 @@ public final class InputMethodSubtype implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeInt(mSubtypeNameResId); TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags); + dest.writeInt(mLayoutLabelResId); + TextUtils.writeToParcel(mLayoutLabelNonLocalized, dest, parcelableFlags); dest.writeString8(mPkLanguageTag); dest.writeString8(mPkLayoutType); dest.writeInt(mSubtypeIconResId); @@ -794,6 +916,7 @@ public final class InputMethodSubtype implements Parcelable { void dump(@NonNull Printer pw, @NonNull String prefix) { pw.println(prefix + "mSubtypeNameOverride=" + mSubtypeNameOverride + + " mLayoutLabelNonLocalized=" + mLayoutLabelNonLocalized + " mPkLanguageTag=" + mPkLanguageTag + " mPkLayoutType=" + mPkLayoutType + " mSubtypeId=" + mSubtypeId diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index ef941da0e32d..d7750bd412a3 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -26,6 +26,9 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDER import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; +import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY; +import static android.view.accessibility.Flags.FLAG_A11Y_CHARACTER_IN_WINDOW_API; +import static android.view.accessibility.Flags.a11yCharacterInWindowApi; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY; import static android.view.inputmethod.Flags.initiationWithoutInputConnection; @@ -492,6 +495,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** Accessibility action start id for "smart" actions. @hide */ static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000; + // Stable extra data keys supported by TextView. + private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS = List.of( + EXTRA_DATA_RENDERING_INFO_KEY, + EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY + ); + + // Flagged and stable extra data keys supported by TextView. + @FlaggedApi(FLAG_A11Y_CHARACTER_IN_WINDOW_API) + private static final List<String> ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED = List.of( + EXTRA_DATA_RENDERING_INFO_KEY, + EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, + EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY + ); + /** * @hide */ @@ -14207,10 +14224,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); - info.setAvailableExtraData(Arrays.asList( - EXTRA_DATA_RENDERING_INFO_KEY, - EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY - )); + if (a11yCharacterInWindowApi()) { + info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS_FLAGGED); + } else { + info.setAvailableExtraData(ACCESSIBILITY_EXTRA_DATA_KEYS); + } info.setTextSelectable(isTextSelectable() || isTextEditable()); } else { info.setAvailableExtraData(Arrays.asList( @@ -14275,7 +14293,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public void addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { - if (arguments != null && extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { + boolean isCharacterLocationKey = extraDataKey.equals( + EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY); + boolean isCharacterLocationInWindowKey = (a11yCharacterInWindowApi() && extraDataKey.equals( + EXTRA_DATA_TEXT_CHARACTER_LOCATION_IN_WINDOW_KEY)); + if (arguments != null && (isCharacterLocationKey || isCharacterLocationInWindowKey)) { int positionInfoStartIndex = arguments.getInt( EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); int positionInfoLength = arguments.getInt( @@ -14297,7 +14319,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener RectF bounds = cursorAnchorInfo .getCharacterBounds(positionInfoStartIndex + i); if (bounds != null) { - mapRectFromViewToScreenCoords(bounds, true); + if (isCharacterLocationKey) { + mapRectFromViewToScreenCoords(bounds, true); + } else if (isCharacterLocationInWindowKey) { + mapRectFromViewToWindowCoords(bounds, true); + } boundingRects[i] = bounds; } } diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java index bd01899a649b..c67b9cac250b 100644 --- a/core/java/android/window/ImeOnBackInvokedDispatcher.java +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java @@ -203,6 +203,34 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc mImeCallbacks.remove(callback); } + /** + * Unregisters all callbacks on the receiving dispatcher but keeps a reference of the callbacks + * in case the clearance is reverted in + * {@link ImeOnBackInvokedDispatcher#undoPreliminaryClear()}. + */ + public void preliminaryClear() { + // Unregister previously registered callbacks if there's any. + if (getReceivingDispatcher() != null) { + for (ImeOnBackInvokedCallback callback : mImeCallbacks) { + getReceivingDispatcher().unregisterOnBackInvokedCallback(callback); + } + } + } + + /** + * Reregisters all callbacks on the receiving dispatcher that have previously been cleared by + * calling {@link ImeOnBackInvokedDispatcher#preliminaryClear()}. This can happen if an IME hide + * animation is interrupted causing the IME to reappear. + */ + public void undoPreliminaryClear() { + if (getReceivingDispatcher() != null) { + for (ImeOnBackInvokedCallback callback : mImeCallbacks) { + getReceivingDispatcher().registerOnBackInvokedCallbackUnchecked(callback, + callback.mPriority); + } + } + } + /** Clears all registered callbacks on the instance. */ public void clear() { // Unregister previously registered callbacks if there's any. diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java index 8bb4c526b20d..61fc6226f822 100644 --- a/core/java/android/window/TransitionFilter.java +++ b/core/java/android/window/TransitionFilter.java @@ -17,6 +17,7 @@ package android.window; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManager.TransitionType; import android.annotation.IntDef; @@ -189,6 +190,8 @@ public final class TransitionFilter implements Parcelable { public Boolean mCustomAnimation = null; public IBinder mTaskFragmentToken = null; + public int mWindowingMode = WINDOWING_MODE_UNDEFINED; + public Requirement() { } @@ -206,6 +209,7 @@ public final class TransitionFilter implements Parcelable { final int customAnimRaw = in.readInt(); mCustomAnimation = customAnimRaw == 0 ? null : Boolean.valueOf(customAnimRaw == 2); mTaskFragmentToken = in.readStrongBinder(); + mWindowingMode = in.readInt(); } /** Go through changes and find if at-least one change matches this filter */ @@ -270,6 +274,12 @@ public final class TransitionFilter implements Parcelable { continue; } } + if (mWindowingMode != WINDOWING_MODE_UNDEFINED) { + if (change.getTaskInfo() == null + || change.getTaskInfo().getWindowingMode() != mWindowingMode) { + continue; + } + } return true; } return false; @@ -322,6 +332,7 @@ public final class TransitionFilter implements Parcelable { int customAnimRaw = mCustomAnimation == null ? 0 : (mCustomAnimation ? 2 : 1); dest.writeInt(customAnimRaw); dest.writeStrongBinder(mTaskFragmentToken); + dest.writeInt(mWindowingMode); } @NonNull @@ -369,6 +380,8 @@ public final class TransitionFilter implements Parcelable { if (mTaskFragmentToken != null) { out.append(" taskFragmentToken=").append(mTaskFragmentToken); } + out.append(" windowingMode=" + + WindowConfiguration.windowingModeToString(mWindowingMode)); out.append("}"); return out.toString(); } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 14505f527195..0f2dd10d7f47 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -169,8 +169,11 @@ public final class TransitionInfo implements Parcelable { /** This change represents its start configuration for the duration of the animation. */ public static final int FLAG_CONFIG_AT_END = 1 << 22; + /** This change represents one of a Task Display Area. */ + public static final int FLAG_IS_TASK_DISPLAY_AREA = 1 << 23; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 23; + public static final int FLAG_FIRST_CUSTOM = 1 << 24; /** The change belongs to a window that won't contain activities. */ public static final int FLAGS_IS_NON_APP_WINDOW = @@ -205,6 +208,7 @@ public final class TransitionInfo implements Parcelable { FLAG_MOVED_TO_TOP, FLAG_SYNC, FLAG_CONFIG_AT_END, + FLAG_IS_TASK_DISPLAY_AREA, FLAG_FIRST_CUSTOM }, flag = true) public @interface ChangeFlags {} @@ -553,6 +557,9 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_MOVED_TO_TOP) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP"); } + if ((flags & FLAG_IS_TASK_DISPLAY_AREA) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_TASK_DISPLAY_AREA"); + } return sb.toString(); } diff --git a/core/java/com/android/internal/compat/AndroidBuildClassifier.java b/core/java/com/android/internal/compat/AndroidBuildClassifier.java index 364db06976a0..19f8889996dc 100644 --- a/core/java/com/android/internal/compat/AndroidBuildClassifier.java +++ b/core/java/com/android/internal/compat/AndroidBuildClassifier.java @@ -22,6 +22,7 @@ import android.os.Build; * Platform private class for determining the type of Android build installed. * */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class AndroidBuildClassifier { public boolean isDebuggableBuild() { diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java index f61157175f9d..f714098e8bc4 100644 --- a/core/java/com/android/internal/compat/ChangeReporter.java +++ b/core/java/com/android/internal/compat/ChangeReporter.java @@ -42,6 +42,7 @@ import java.util.function.Function; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ChangeReporter { private static final String TAG = "CompatChangeReporter"; private static final Function<Integer, Set<ChangeReport>> NEW_CHANGE_REPORT_SET = diff --git a/core/java/com/android/internal/compat/CompatibilityChangeConfig.java b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java index 182dba71d0d7..8fd914aecf55 100644 --- a/core/java/com/android/internal/compat/CompatibilityChangeConfig.java +++ b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java @@ -28,6 +28,7 @@ import java.util.Set; * Parcelable containing compat config overrides for a given application. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class CompatibilityChangeConfig implements Parcelable { private final ChangeConfig mChangeConfig; diff --git a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java index 03fe4551c249..505fd2319a6b 100644 --- a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java +++ b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java @@ -25,6 +25,7 @@ import android.os.Parcelable; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class CompatibilityChangeInfo implements Parcelable { private final long mChangeId; private final @Nullable String mName; diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java index 9a02b7b7aae9..32206c9950dd 100644 --- a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java +++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java @@ -28,6 +28,7 @@ import java.util.Map; * Parcelable containing compat config overrides for a given application. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class CompatibilityOverrideConfig implements Parcelable { public final Map<Long, PackageOverride> overrides; diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java index 8652bb6d05e4..998b48a8a76e 100644 --- a/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java +++ b/core/java/com/android/internal/compat/CompatibilityOverridesByPackageConfig.java @@ -26,6 +26,7 @@ import java.util.Map; * Parcelable containing compat config overrides by application. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class CompatibilityOverridesByPackageConfig implements Parcelable { public final Map<String, CompatibilityOverrideConfig> packageNameToOverrides; diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java index b408d6440070..c0e2217d509e 100644 --- a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java +++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveByPackageConfig.java @@ -29,6 +29,7 @@ import java.util.Map; * IDs. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class CompatibilityOverridesToRemoveByPackageConfig implements Parcelable { public final Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove; diff --git a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java index e85afefdc39a..10461ec0b4c6 100644 --- a/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java +++ b/core/java/com/android/internal/compat/CompatibilityOverridesToRemoveConfig.java @@ -30,6 +30,7 @@ import java.util.Set; * <p>This class is separate from CompatibilityOverrideConfig since we only need change IDs. * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class CompatibilityOverridesToRemoveConfig implements Parcelable { public final Set<Long> changeIds; diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java index e408be2ab471..f018c3a830bd 100644 --- a/core/java/com/android/internal/compat/OverrideAllowedState.java +++ b/core/java/com/android/internal/compat/OverrideAllowedState.java @@ -27,6 +27,7 @@ import java.lang.annotation.RetentionPolicy; /** * This class contains all the possible override allowed states. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class OverrideAllowedState implements Parcelable { @IntDef({ ALLOWED, diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java index a69d2e4f4dca..3303d875c427 100644 --- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java +++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java @@ -15,6 +15,12 @@ */ package com.android.internal.ravenwood; +import static android.os.Build.VERSION_CODES.S; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + +import android.compat.annotation.ChangeId; +import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledAfter; import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.ravenwood.annotation.RavenwoodRedirect; import android.ravenwood.annotation.RavenwoodRedirectionClass; @@ -78,4 +84,20 @@ public final class RavenwoodEnvironment { public String getRavenwoodRuntimePath() { throw notSupportedOnDevice(); } + + /** @hide */ + public static class CompatIdsForTest { + // Enabled by default + @ChangeId + public static final long TEST_COMPAT_ID_1 = 368131859L; + + @Disabled + @ChangeId public static final long TEST_COMPAT_ID_2 = 368131701L; + + @EnabledAfter(targetSdkVersion = S) + @ChangeId public static final long TEST_COMPAT_ID_3 = 368131659L; + + @EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE) + @ChangeId public static final long TEST_COMPAT_ID_4 = 368132057L; + } } diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java index b6383d9f0754..38685b652c50 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java @@ -530,8 +530,26 @@ public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup { int rootViewTopOnWindow = mTmpCoords[1]; int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow; int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow; - mCoordsOnWindow.set( - Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen)); + // In some cases, app can have specific Window for Android UI components such as EditText. + // In this case, Window bounds != App bounds. Hence, instead of ensuring non-negative + // PopupWindow coords, app bounds should be used to limit the coords. For instance, + // ____ <- | + // | | |W1 & App bounds + // |___| | + // |W2 | | W2 has smaller bounds and contain EditText where PopupWindow will be opened. + // ---- <-| + // Here, we'll open PopupWindow upwards, but as PopupWindow is anchored based on W2, it + // will have negative Y coords. This negative Y is safe to use because it's still within app + // bounds. However, if it gets out of app bounds, we should clamp it to 0. + Rect appBounds = mContext + .getResources().getConfiguration().windowConfiguration.getAppBounds(); + mCoordsOnWindow.set(x - windowLeftOnScreen, y - windowTopOnScreen); + if (rootViewLeftOnScreen + mCoordsOnWindow.x < appBounds.left) { + mCoordsOnWindow.x = 0; + } + if (rootViewTopOnScreen + mCoordsOnWindow.y < appBounds.top) { + mCoordsOnWindow.y = 0; + } } /** diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index df87a69f02ce..56292c3d0fb2 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -2403,6 +2403,11 @@ SurfaceComposerClient::Transaction* android_view_SurfaceTransaction_getNativeSur } } +static void nativeEnableDebugLogCallPoints(JNIEnv* env, jclass clazz, jlong transactionObj) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + transaction->enableDebugLogCallPoints(); +} + static const JNINativeMethod sSurfaceControlMethods[] = { // clang-format off {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", @@ -2649,6 +2654,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { {"nativeNotifyShutdown", "()V", (void*)nativeNotifyShutdown }, {"nativeSetLuts", "(JJ[F[I[I[I[I)V", (void*)nativeSetLuts }, + {"nativeEnableDebugLogCallPoints", "(J)V", (void*)nativeEnableDebugLogCallPoints }, // clang-format on }; diff --git a/core/res/Android.bp b/core/res/Android.bp index f6ca8218926c..66c2e12f7cdf 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -171,6 +171,7 @@ android_app { "android.security.flags-aconfig", "com.android.hardware.input.input-aconfig", "aconfig_trade_in_mode_flags", + "ranging_aconfig_flags", ], } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0479318ad9ed..5913992004b8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2412,6 +2412,16 @@ android:label="@string/permlab_nearby_wifi_devices" android:protectionLevel="dangerous" /> + <!-- Required to be able to range to devices using generic ranging module. + @FlaggedApi("android.permission.flags.ranging_permission_enabled") + <p>Protection level: dangerous --> + <permission android:name="android.permission.RANGING" + android:permissionGroup="android.permission-group.UNDEFINED" + android:description="@string/permdesc_ranging" + android:label="@string/permlab_ranging" + android:protectionLevel="dangerous" + android:featureFlag="android.permission.flags.ranging_permission_enabled"/> + <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the user from using them until they are unsuspended. @hide diff --git a/core/res/res/drawable/ic_zen_mode_type_unknown.xml b/core/res/res/drawable/ic_zen_mode_icon_star_badge.xml index 04df5f91fd68..04df5f91fd68 100644 --- a/core/res/res/drawable/ic_zen_mode_type_unknown.xml +++ b/core/res/res/drawable/ic_zen_mode_icon_star_badge.xml diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml index f8710cc45358..7b241aff3fb1 100644 --- a/core/res/res/layout/input_method_switch_item_new.xml +++ b/core/res/res/layout/input_method_switch_item_new.xml @@ -18,18 +18,20 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list_item" android:layout_width="match_parent" - android:layout_height="72dp" + android:layout_height="wrap_content" + android:minHeight="72dp" android:background="@drawable/input_method_switch_item_background" android:gravity="center_vertical" android:orientation="horizontal" android:layout_marginHorizontal="16dp" android:layout_marginBottom="8dp" android:paddingStart="20dp" - android:paddingEnd="24dp"> + android:paddingEnd="24dp" + android:paddingVertical="8dp"> <LinearLayout android:layout_width="0dp" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_weight="1" android:gravity="start|center_vertical" android:orientation="vertical"> @@ -39,11 +41,26 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="marquee" + android:marqueeRepeatLimit="1" android:singleLine="true" android:fontFamily="google-sans-text" android:textColor="@color/input_method_switch_on_item" android:textAppearance="?attr/textAppearanceListItem"/> + <TextView + android:id="@+id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ellipsize="marquee" + android:marqueeRepeatLimit="1" + android:singleLine="true" + android:fontFamily="google-sans-text" + android:textColor="?attr/materialColorOnSurfaceVariant" + android:textAppearance="?attr/textAppearanceListItemSecondary" + android:textAllCaps="true" + android:visibility="gone"/> + </LinearLayout> <ImageView diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 092d2a72580a..e6dedce8feaf 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4418,6 +4418,10 @@ <declare-styleable name="InputMethod_Subtype"> <!-- The name of the subtype. --> <attr name="label" /> + <!-- The layout label of the subtype. + {@link android.view.inputmethod.InputMethodSubtype#getLayoutDisplayName} returns the + value specified in this attribute. --> + <attr name="layoutLabel" format="reference" /> <!-- The icon of the subtype. --> <attr name="icon" /> <!-- The locale of the subtype. This string should be a locale (for example en_US and fr_FR) diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 0c28ea406aa2..70cc5f14391d 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -125,6 +125,8 @@ <public name="supplementalDescription"/> <!-- @FlaggedApi("android.security.enable_intent_matching_flags") --> <public name="intentMatchingFlags"/> + <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> + <public name="layoutLabel"/> </staging-public-group> <staging-public-group type="id" first-id="0x01b60000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e23e665e7335..c13fdb17dfe3 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1766,6 +1766,11 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=140]--> <string name="permdesc_nearby_wifi_devices">Allows the app to advertise, connect, and determine the relative position of nearby Wi\u2011Fi devices</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]--> + <string name="permlab_ranging">determine relative position between nearby devices</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]--> + <string name="permdesc_ranging">Allow the app to determine relative position between nearby devices</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_preferredPaymentInfo">Preferred NFC Payment Service Information</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index fec8bbbfeb83..aa08d5e2313e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5647,7 +5647,6 @@ <java-symbol type="drawable" name="ic_zen_mode_type_schedule_calendar" /> <java-symbol type="drawable" name="ic_zen_mode_type_schedule_time" /> <java-symbol type="drawable" name="ic_zen_mode_type_theater" /> - <java-symbol type="drawable" name="ic_zen_mode_type_unknown" /> <java-symbol type="drawable" name="ic_zen_mode_type_special_dnd" /> <!-- System notification for background user sound --> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 24f6ceaf786c..8d045f87063b 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -26,6 +26,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; 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; @@ -59,6 +60,7 @@ import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StopActivityItem; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -67,7 +69,11 @@ import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.os.Bundle; import android.os.IBinder; +import android.os.Looper; import android.platform.test.annotations.Presubmit; +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.DisplayMetrics; import android.util.Log; @@ -129,6 +135,9 @@ public class ActivityThreadTest { @Rule(order = 1) public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private ActivityWindowInfoListener mActivityWindowInfoListener; private WindowTokenClientController mOriginalWindowTokenClientController; private Configuration mOriginalAppConfig; @@ -912,6 +921,32 @@ public class ActivityThreadTest { } /** + * Verifies that {@link ActivityThread#handleApplicationInfoChanged} does send updates to the + * system context, when given the system application info. + */ + @RequiresFlagsEnabled(android.content.res.Flags.FLAG_SYSTEM_CONTEXT_HANDLE_APP_INFO_CHANGED) + @Test + public void testHandleApplicationInfoChanged_systemContext() { + Looper.prepare(); + final var systemThread = ActivityThread.createSystemActivityThreadForTesting(); + + final Context systemContext = systemThread.getSystemContext(); + final var appInfo = systemContext.getApplicationInfo(); + // sourceDir must not be null, and contain at least a '/', for handleApplicationInfoChanged. + appInfo.sourceDir = "/"; + + // Create a copy of the application info. + final var newAppInfo = new ApplicationInfo(appInfo); + newAppInfo.sourceDir = "/"; + assertWithMessage("New application info is a separate instance") + .that(systemContext.getApplicationInfo()).isNotSameInstanceAs(newAppInfo); + + systemThread.handleApplicationInfoChanged(newAppInfo); + assertWithMessage("Application info was updated successfully") + .that(systemContext.getApplicationInfo()).isSameInstanceAs(newAppInfo); + } + + /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the * activity for the given sequence number. diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java index 00ffda867d6a..a47a3e0e6c2a 100644 --- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java +++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java @@ -161,7 +161,7 @@ public class ImeBackAnimationControllerTest { mBackAnimationController.onBackInvoked(); // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever // getInputMethodManager is called from ImeBackAnimationController) - verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager(); + verify(mViewRootInsetsControllerHost, times(2)).getInputMethodManager(); // verify that ImeBackAnimationController does not take control over IME insets verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(), anyBoolean(), anyLong(), any(), anyInt(), anyBoolean()); @@ -180,7 +180,7 @@ public class ImeBackAnimationControllerTest { mBackAnimationController.onBackInvoked(); // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever // getInputMethodManager is called from ImeBackAnimationController) - verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager(); + verify(mViewRootInsetsControllerHost, times(2)).getInputMethodManager(); // verify that ImeBackAnimationController does not take control over IME insets verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(), anyBoolean(), anyLong(), any(), anyInt(), anyBoolean()); @@ -300,7 +300,7 @@ public class ImeBackAnimationControllerTest { mBackAnimationController.onBackInvoked(); // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever // getInputMethodManager is called from ImeBackAnimationController) - verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager(); + verify(mViewRootInsetsControllerHost, times(2)).getInputMethodManager(); }); } diff --git a/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java b/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java index 262bd5cd6c01..0f17f9cdddc8 100644 --- a/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java +++ b/core/tests/coretests/src/android/view/RoundScrollbarRendererTest.java @@ -16,7 +16,11 @@ package android.view; +import static android.view.RoundScrollbarRenderer.BLUECHIP_ENABLED_SYSPROP; + import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -30,11 +34,8 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.view.flags.Flags; import androidx.test.core.app.ApplicationProvider; @@ -42,7 +43,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -66,9 +66,6 @@ public class RoundScrollbarRendererTest { private static final float DEFAULT_ALPHA = 0.5f; private static final Rect BOUNDS = new Rect(0, 0, 200, 200); - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - @Mock private Canvas mCanvas; @Captor private ArgumentCaptor<Paint> mPaintCaptor; private RoundScrollbarRenderer mScrollbar; @@ -88,8 +85,8 @@ public class RoundScrollbarRendererTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_USE_REFACTORED_ROUND_SCROLLBAR) public void testScrollbarDrawn_legacy() { + assumeFalse(usingRefactoredScrollbar()); mScrollbar.drawRoundScrollbars(mCanvas, DEFAULT_ALPHA, BOUNDS, /* drawToLeft= */ false); // The arc will be drawn twice, i.e. once for track and once for thumb @@ -105,8 +102,8 @@ public class RoundScrollbarRendererTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_USE_REFACTORED_ROUND_SCROLLBAR) public void testScrollbarDrawn() { + assumeTrue(usingRefactoredScrollbar()); mScrollbar.drawRoundScrollbars(mCanvas, DEFAULT_ALPHA, BOUNDS, /* drawToLeft= */ false); // The arc will be drawn thrice, i.e. twice for track and once for thumb @@ -143,4 +140,9 @@ public class RoundScrollbarRendererTest { return super.computeVerticalScrollExtent(); } } + + private static boolean usingRefactoredScrollbar() { + return Flags.useRefactoredRoundScrollbar() + && SystemProperties.getBoolean(BLUECHIP_ENABLED_SYSPROP, false); + } } diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index 93d94c9cd7eb..b4899f975f43 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -19,6 +19,8 @@ package android.graphics; import android.annotation.FlaggedApi; import android.annotation.IntDef; +import com.android.internal.camera.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -63,6 +65,7 @@ public class ImageFormat { RAW_DEPTH10, PRIVATE, HEIC, + HEIC_ULTRAHDR, JPEG_R }) public @interface Format { @@ -832,6 +835,16 @@ public class ImageFormat { public static final int HEIC = 0x48454946; /** + * High Efficiency Image File Format (HEIF) with embedded HDR gain map + * + * <p>This format defines the HEIC brand of High Efficiency Image File + * Format as described in ISO/IEC 23008-12:2024 with HDR gain map according + * to ISO/CD 21496‐1.</p> + */ + @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP) + public static final int HEIC_ULTRAHDR = 0x1006; + + /** * Use this function to retrieve the number of bits per pixel of an * ImageFormat. * @@ -926,6 +939,11 @@ public class ImageFormat { if (android.media.codec.Flags.p210FormatSupport() && format == YCBCR_P210) { return true; } + if (Flags.cameraHeifGainmap()){ + if (format == HEIC_ULTRAHDR) { + return true; + } + } return false; } } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index b7a1c13c75c7..8bb32568ec5a 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -19,7 +19,7 @@ package android.graphics; import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION; import static com.android.text.flags.Flags.FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API; - +import static com.android.text.flags.Flags.FLAG_VERTICAL_TEXT_LAYOUT; import android.annotation.ColorInt; import android.annotation.ColorLong; @@ -35,6 +35,7 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; +import android.graphics.text.TextRunShaper; import android.os.Build; import android.os.LocaleList; import android.text.GraphicsOperations; @@ -269,7 +270,24 @@ public class Paint { public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400; /** @hide bit mask for the flag forcing freetype's autohinter on for text */ public static final int AUTO_HINTING_TEXT_FLAG = 0x800; - /** @hide bit mask for the flag enabling vertical rendering for text */ + + /** + * A flat that controls text to be written in vertical orientation + * + * <p> + * This flag is used for telling the underlying text layout engine that the text is for vertical + * direction. By enabling this flag, text measurement, drawing and shaping APIs works for + * vertical text layout. For example, {@link Canvas#drawText(String, float, float, Paint)} draws + * text from top to bottom. {@link Paint#measureText(String)} returns vertical advances instead + * of horizontal advances. {@link TextRunShaper} shapes text vertically and report glyph IDs for + * vertical layout. + * + * <p> + * Do not set this flag for making {@link android.text.Layout}. The {@link android.text.Layout} + * class and its subclasses are designed for horizontal text only and does not work for vertical + * text. + */ + @FlaggedApi(FLAG_VERTICAL_TEXT_LAYOUT) public static final int VERTICAL_TEXT_FLAG = 0x1000; /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index 438532725686..4d7be39ca5a4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -16,6 +16,7 @@ package androidx.window.extensions.area; +import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY; import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT; import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY; import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY; @@ -569,7 +570,8 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, private boolean isDeviceFolded() { if (Flags.deviceStatePropertyApi()) { return mCurrentDeviceState.hasProperty( - PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY); + PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY) + && !mCurrentDeviceState.hasProperty(PROPERTY_EMULATED_ONLY); } else { return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState.getIdentifier()); } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt index 2fbf089d99d6..00d9a931cebe 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt @@ -19,12 +19,15 @@ package com.android.wm.shell.bubbles.bar import android.app.ActivityManager import android.content.Context import android.content.pm.LauncherApps +import android.graphics.PointF import android.os.Handler import android.os.UserManager import android.view.IWindowManager import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.WindowManager +import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -48,6 +51,7 @@ import com.android.wm.shell.bubbles.BubbleTaskViewFactory import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.FakeBubbleFactory import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat +import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix import com.android.wm.shell.bubbles.properties.BubbleProperties import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.common.DisplayController @@ -57,6 +61,7 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl import com.android.wm.shell.shared.TransactionPool +import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -66,8 +71,10 @@ import com.android.wm.shell.taskview.TaskViewTaskController import com.android.wm.shell.taskview.TaskViewTransitions import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat +import org.junit.After import java.util.Collections import org.junit.Before +import org.junit.ClassRule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock @@ -79,18 +86,28 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class BubbleBarLayerViewTest { + companion object { + @JvmField @ClassRule + val animatorTestRule: AnimatorTestRule = AnimatorTestRule() + } + private val context = ApplicationProvider.getApplicationContext<Context>() private lateinit var bubbleBarLayerView: BubbleBarLayerView private lateinit var uiEventLoggerFake: UiEventLoggerFake + private lateinit var bubbleController: BubbleController + + private lateinit var bubblePositioner: BubblePositioner + private lateinit var bubble: Bubble @Before fun setUp() { ProtoLog.REQUIRE_PROTOLOGTOOL = false ProtoLog.init() + PhysicsAnimatorTestUtils.prepareForTest() uiEventLoggerFake = UiEventLoggerFake() val bubbleLogger = BubbleLogger(uiEventLoggerFake) @@ -100,7 +117,7 @@ class BubbleBarLayerViewTest { val windowManager = context.getSystemService(WindowManager::class.java) - val bubblePositioner = BubblePositioner(context, windowManager) + bubblePositioner = BubblePositioner(context, windowManager) bubblePositioner.setShowingInBubbleBar(true) val bubbleData = @@ -113,7 +130,7 @@ class BubbleBarLayerViewTest { bgExecutor, ) - val bubbleController = + bubbleController = createBubbleController( bubbleData, windowManager, @@ -151,6 +168,12 @@ class BubbleBarLayerViewTest { bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo) } + @After + fun tearDown() { + PhysicsAnimatorTestUtils.tearDown() + getInstrumentation().waitForIdleSync() + } + private fun createBubbleController( bubbleData: BubbleData, windowManager: WindowManager?, @@ -224,6 +247,70 @@ class BubbleBarLayerViewTest { assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) } + @Test + fun testEventLogging_dragExpandedViewLeft() { + bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + // Drag from right to left + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightEdge()) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftEdge()) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftEdge()) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + @Test + fun testEventLogging_dragExpandedViewRight() { + bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT + + getInstrumentation().runOnMainSync { + bubbleBarLayerView.showExpandedView(bubble) + } + waitForExpandedViewAnimation() + + val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view) + assertThat(handleView).isNotNull() + + // Drag from left to right + handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftEdge()) + handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightEdge()) + handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightEdge()) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.logs[0].eventId) + .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id) + assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble) + } + + private fun leftEdge(): PointF { + val screenSize = bubblePositioner.availableRect + return PointF(screenSize.left.toFloat(), screenSize.height() / 2f) + } + + private fun rightEdge(): PointF { + val screenSize = bubblePositioner.availableRect + return PointF(screenSize.right.toFloat(), screenSize.height() / 2f) + } + + private fun waitForExpandedViewAnimation() { + // wait for idle to allow the animation to start + getInstrumentation().waitForIdleSync() + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(200) } + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( + AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y) + } + private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) : BubbleTaskViewFactory { override fun create(): BubbleTaskView { @@ -290,4 +377,9 @@ class BubbleBarLayerViewTest { } } } + + private fun View.dispatchTouchEvent(eventTime: Long, action: Int, point: PointF) { + val event = MotionEvent.obtain(0L, eventTime, action, point.x, point.y, 0) + getInstrumentation().runOnMainSync { dispatchTouchEvent(event) } + } } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt index ecb2b25a02f1..d4cbe6e10971 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt @@ -74,6 +74,7 @@ class BubbleExpandedViewPinControllerTest { @Before fun setUp() { ProtoLog.REQUIRE_PROTOLOGTOOL = false + ProtoLog.init() container = FrameLayout(context) val windowManager = context.getSystemService(WindowManager::class.java) positioner = BubblePositioner(context, windowManager) @@ -85,7 +86,7 @@ class BubbleExpandedViewPinControllerTest { isSmallTablet = false, isLandscape = true, isRtl = false, - insets = Insets.of(10, 20, 30, 40) + insets = Insets.of(10, 20, 30, 40), ) positioner.update(deviceConfig) positioner.bubbleBarTopOnScreen = @@ -407,12 +408,26 @@ class BubbleExpandedViewPinControllerTest { assertThat(testListener.locationReleases).containsExactly(RIGHT) } + /** Send drag start event when on left */ + @Test + fun start_onLeft_sendStartEventOnLeft() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = true) } + assertThat(testListener.locationStart).containsExactly(LEFT) + } + + /** Send drag start event when on right */ + @Test + fun start_onRight_sendStartEventOnRight() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) } + assertThat(testListener.locationStart).containsExactly(RIGHT) + } + private fun getExpectedDropTargetBoundsOnLeft(): Rect = Rect().also { positioner.getBubbleBarExpandedViewBounds( true /* onLeft */, false /* isOverflowExpanded */, - it + it, ) } @@ -421,7 +436,7 @@ class BubbleExpandedViewPinControllerTest { positioner.getBubbleBarExpandedViewBounds( false /* onLeft */, false /* isOverflowExpanded */, - it + it, ) } @@ -446,8 +461,14 @@ class BubbleExpandedViewPinControllerTest { } internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener { + val locationStart = mutableListOf<BubbleBarLocation>() val locationChanges = mutableListOf<BubbleBarLocation>() val locationReleases = mutableListOf<BubbleBarLocation>() + + override fun onStart(location: BubbleBarLocation) { + locationStart.add(location) + } + override fun onChange(location: BubbleBarLocation) { locationChanges.add(location) } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl index e21bf8fb723c..93e635dd937c 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl @@ -16,4 +16,4 @@ package com.android.wm.shell.shared; -parcelable GroupedRecentTaskInfo;
\ No newline at end of file +parcelable GroupedTaskInfo;
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 65e079ef4f72..03e0ab0591a1 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -17,7 +17,8 @@ package com.android.wm.shell.shared; import android.annotation.IntDef; -import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; +import android.app.TaskInfo; import android.app.WindowConfiguration; import android.os.Parcel; import android.os.Parcelable; @@ -27,69 +28,91 @@ import androidx.annotation.Nullable; import com.android.wm.shell.shared.split.SplitBounds; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; /** - * Simple container for recent tasks. May contain either a single or pair of tasks. + * Simple container for recent tasks which should be presented as a single task within the + * Overview UI. */ -public class GroupedRecentTaskInfo implements Parcelable { +public class GroupedTaskInfo implements Parcelable { - public static final int TYPE_SINGLE = 1; + public static final int TYPE_FULLSCREEN = 1; public static final int TYPE_SPLIT = 2; public static final int TYPE_FREEFORM = 3; @IntDef(prefix = {"TYPE_"}, value = { - TYPE_SINGLE, + TYPE_FULLSCREEN, TYPE_SPLIT, TYPE_FREEFORM }) public @interface GroupType {} + /** + * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or + * TYPE_FREEFORM. + */ + @GroupType + protected final int mType; + + /** + * The list of tasks associated with this single recent task info. + * TYPE_FULLSCREEN: Contains the stack of tasks associated with a single "task" in overview + * TYPE_SPLIT: Contains the two split roots of each side + * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode + */ @NonNull - private final ActivityManager.RecentTaskInfo[] mTasks; + protected final List<TaskInfo> mTasks; + + /** + * Only set for TYPE_SPLIT. + * + * Information about the split bounds. + */ @Nullable - private final SplitBounds mSplitBounds; - @GroupType - private final int mType; - // TODO(b/348332802): move isMinimized inside each Task object instead once we have a - // replacement for RecentTaskInfo - private final int[] mMinimizedTaskIds; + protected final SplitBounds mSplitBounds; /** - * Create new for a single task + * Only set for TYPE_FREEFORM. + * + * TODO(b/348332802): move isMinimized inside each Task object instead once we have a + * replacement for RecentTaskInfo */ - public static GroupedRecentTaskInfo forSingleTask( - @NonNull ActivityManager.RecentTaskInfo task) { - return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null, - TYPE_SINGLE, null /* minimizedFreeformTasks */); + @Nullable + protected final int[] mMinimizedTaskIds; + + /** + * Create new for a stack of fullscreen tasks + */ + public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) { + return new GroupedTaskInfo(List.of(task), null, TYPE_FULLSCREEN, + null /* minimizedFreeformTasks */); } /** * Create new for a pair of tasks in split screen */ - public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1, - @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) { - return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2}, - splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */); + public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1, + @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) { + return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT, + null /* minimizedFreeformTasks */); } /** * Create new for a group of freeform tasks */ - public static GroupedRecentTaskInfo forFreeformTasks( - @NonNull ActivityManager.RecentTaskInfo[] tasks, - @NonNull Set<Integer> minimizedFreeformTasks) { - return new GroupedRecentTaskInfo( - tasks, - null /* splitBounds */, - TYPE_FREEFORM, + public static GroupedTaskInfo forFreeformTasks( + @NonNull List<TaskInfo> tasks, + @NonNull Set<Integer> minimizedFreeformTasks) { + return new GroupedTaskInfo(tasks, null /* splitBounds */, TYPE_FREEFORM, minimizedFreeformTasks.stream().mapToInt(i -> i).toArray()); } - private GroupedRecentTaskInfo( - @NonNull ActivityManager.RecentTaskInfo[] tasks, + private GroupedTaskInfo( + @NonNull List<TaskInfo> tasks, @Nullable SplitBounds splitBounds, @GroupType int type, @Nullable int[] minimizedFreeformTaskIds) { @@ -100,52 +123,56 @@ public class GroupedRecentTaskInfo implements Parcelable { ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds); } - private static void ensureAllMinimizedIdsPresent( - @NonNull ActivityManager.RecentTaskInfo[] tasks, + private void ensureAllMinimizedIdsPresent( + @NonNull List<TaskInfo> tasks, @Nullable int[] minimizedFreeformTaskIds) { if (minimizedFreeformTaskIds == null) { return; } if (!Arrays.stream(minimizedFreeformTaskIds).allMatch( - taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) { + taskId -> tasks.stream().anyMatch(task -> task.taskId == taskId))) { throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID."); } } - GroupedRecentTaskInfo(Parcel parcel) { - mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR); + protected GroupedTaskInfo(@NonNull Parcel parcel) { + mTasks = new ArrayList(); + final int numTasks = parcel.readInt(); + for (int i = 0; i < numTasks; i++) { + mTasks.add(new TaskInfo(parcel)); + } mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR); mType = parcel.readInt(); mMinimizedTaskIds = parcel.createIntArray(); } /** - * Get primary {@link ActivityManager.RecentTaskInfo} + * Get primary {@link RecentTaskInfo} */ @NonNull - public ActivityManager.RecentTaskInfo getTaskInfo1() { - return mTasks[0]; + public TaskInfo getTaskInfo1() { + return mTasks.getFirst(); } /** - * Get secondary {@link ActivityManager.RecentTaskInfo}. + * Get secondary {@link RecentTaskInfo}. * * Used in split screen. */ @Nullable - public ActivityManager.RecentTaskInfo getTaskInfo2() { - if (mTasks.length > 1) { - return mTasks[1]; + public TaskInfo getTaskInfo2() { + if (mTasks.size() > 1) { + return mTasks.get(1); } return null; } /** - * Get all {@link ActivityManager.RecentTaskInfo}s grouped together. + * Get all {@link RecentTaskInfo}s grouped together. */ @NonNull - public List<ActivityManager.RecentTaskInfo> getTaskInfoList() { - return Arrays.asList(mTasks); + public List<TaskInfo> getTaskInfoList() { + return mTasks; } /** @@ -164,28 +191,46 @@ public class GroupedRecentTaskInfo implements Parcelable { return mType; } + @Nullable public int[] getMinimizedTaskIds() { return mMinimizedTaskIds; } @Override + public boolean equals(Object obj) { + if (!(obj instanceof GroupedTaskInfo)) { + return false; + } + GroupedTaskInfo other = (GroupedTaskInfo) obj; + return mType == other.mType + && Objects.equals(mTasks, other.mTasks) + && Objects.equals(mSplitBounds, other.mSplitBounds) + && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds); + } + + @Override + public int hashCode() { + return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds)); + } + + @Override public String toString() { StringBuilder taskString = new StringBuilder(); - for (int i = 0; i < mTasks.length; i++) { + for (int i = 0; i < mTasks.size(); i++) { if (i == 0) { taskString.append("Task"); } else { taskString.append(", Task"); } - taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks[i])); + taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i))); } if (mSplitBounds != null) { taskString.append(", SplitBounds: ").append(mSplitBounds); } taskString.append(", Type="); switch (mType) { - case TYPE_SINGLE: - taskString.append("TYPE_SINGLE"); + case TYPE_FULLSCREEN: + taskString.append("TYPE_FULLSCREEN"); break; case TYPE_SPLIT: taskString.append("TYPE_SPLIT"); @@ -199,7 +244,7 @@ public class GroupedRecentTaskInfo implements Parcelable { return taskString.toString(); } - private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { + private String getTaskInfo(TaskInfo taskInfo) { if (taskInfo == null) { return null; } @@ -213,7 +258,12 @@ public class GroupedRecentTaskInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedArray(mTasks, flags); + // We don't use the parcel list methods because we want to only write the TaskInfo state + // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated + parcel.writeInt(mTasks.size()); + for (int i = 0; i < mTasks.size(); i++) { + mTasks.get(i).writeTaskToParcel(parcel, flags); + } parcel.writeTypedObject(mSplitBounds, flags); parcel.writeInt(mType); parcel.writeIntArray(mMinimizedTaskIds); @@ -224,13 +274,15 @@ public class GroupedRecentTaskInfo implements Parcelable { return 0; } - public static final @android.annotation.NonNull Creator<GroupedRecentTaskInfo> CREATOR = - new Creator<GroupedRecentTaskInfo>() { - public GroupedRecentTaskInfo createFromParcel(Parcel source) { - return new GroupedRecentTaskInfo(source); + public static final Creator<GroupedTaskInfo> CREATOR = new Creator() { + @Override + public GroupedTaskInfo createFromParcel(Parcel in) { + return new GroupedTaskInfo(in); } - public GroupedRecentTaskInfo[] newArray(int size) { - return new GroupedRecentTaskInfo[size]; + + @Override + public GroupedTaskInfo[] newArray(int size) { + return new GroupedTaskInfo[size]; } }; -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt index fc3dc1465dff..f93b35e868f6 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt @@ -20,7 +20,7 @@ import android.os.Looper import android.util.ArrayMap import androidx.dynamicanimation.animation.FloatPropertyCompat import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.prepareForTest -import java.util.* +import java.util.ArrayDeque import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.collections.ArrayList @@ -74,14 +74,17 @@ object PhysicsAnimatorTestUtils { @JvmStatic fun tearDown() { - val latch = CountDownLatch(1) - animationThreadHandler.post { + if (Looper.myLooper() == animationThreadHandler.looper) { animatorTestHelpers.keys.forEach { it.cancel() } - latch.countDown() + } else { + val latch = CountDownLatch(1) + animationThreadHandler.post { + animatorTestHelpers.keys.forEach { it.cancel() } + latch.countDown() + } + latch.await(5, TimeUnit.SECONDS) } - latch.await() - animatorTestHelpers.clear() animators.clear() allAnimatedObjects.clear() @@ -348,8 +351,9 @@ object PhysicsAnimatorTestUtils { * Returns all of the values that have ever been reported to update listeners, per property. */ @Suppress("UNCHECKED_CAST") - fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>): - UpdateFramesPerProperty<T> { + fun <T : Any> getAnimationUpdateFrames( + animator: PhysicsAnimator<T> + ): UpdateFramesPerProperty<T> { return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T> } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt index 7086691e7431..bd129a28f049 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt @@ -56,6 +56,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi onLeft = initialLocationOnLeft screenCenterX = screenSizeProvider.invoke().x / 2 dismissZone = getExclusionRect() + listener?.onStart(if (initialLocationOnLeft) LEFT else RIGHT) } /** View has moved to [x] and [y] screen coordinates */ @@ -109,6 +110,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi /** Get width for exclusion rect where dismiss takes over drag */ protected abstract fun getExclusionRectWidth(): Float + /** Get height for exclusion rect where dismiss takes over drag */ protected abstract fun getExclusionRectHeight(): Float @@ -184,6 +186,9 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi /** Receive updates on location changes */ interface LocationChangeListener { + /** Bubble bar dragging has started. Includes the initial location of the bar */ + fun onStart(location: BubbleBarLocation) {} + /** * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in * progress. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 402818c80b01..999ce17905ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -124,18 +124,7 @@ public class BubbleBarLayerView extends FrameLayout mBubbleExpandedViewPinController = new BubbleExpandedViewPinController( context, this, mPositioner); - mBubbleExpandedViewPinController.setListener( - new BaseBubblePinController.LocationChangeListener() { - @Override - public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) { - mBubbleController.animateBubbleBarLocation(bubbleBarLocation); - } - - @Override - public void onRelease(@NonNull BubbleBarLocation location) { - mBubbleController.setBubbleBarLocation(location); - } - }); + mBubbleExpandedViewPinController.setListener(new LocationChangeListener()); setOnClickListener(view -> hideModalOrCollapse()); } @@ -238,11 +227,7 @@ public class BubbleBarLayerView extends FrameLayout DragListener dragListener = inDismiss -> { if (inDismiss && mExpandedBubble != null) { mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE); - if (mExpandedBubble instanceof Bubble) { - // Only a bubble can be dragged to dismiss - mBubbleLogger.log((Bubble) mExpandedBubble, - BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); - } + logBubbleEvent(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW); } }; mDragController = new BubbleBarExpandedViewDragController( @@ -423,10 +408,47 @@ public class BubbleBarLayerView extends FrameLayout } } + /** + * Log the event only if {@link #mExpandedBubble} is a {@link Bubble}. + * <p> + * Skips logging if it is {@link BubbleOverflow}. + */ + private void logBubbleEvent(BubbleLogger.Event event) { + if (mExpandedBubble != null && mExpandedBubble instanceof Bubble bubble) { + mBubbleLogger.log(bubble, event); + } + } + @Nullable @VisibleForTesting public BubbleBarExpandedViewDragController getDragController() { return mDragController; } + private class LocationChangeListener implements + BaseBubblePinController.LocationChangeListener { + + private BubbleBarLocation mInitialLocation; + + @Override + public void onStart(@NonNull BubbleBarLocation location) { + mInitialLocation = location; + } + + @Override + public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) { + mBubbleController.animateBubbleBarLocation(bubbleBarLocation); + } + + @Override + public void onRelease(@NonNull BubbleBarLocation location) { + mBubbleController.setBubbleBarLocation(location); + if (location != mInitialLocation) { + BubbleLogger.Event event = location.isOnLeft(isLayoutRtl()) + ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW + : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW; + logBubbleEvent(event); + } + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index b83b5f341dda..8ef20d1d6b93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -44,7 +44,8 @@ object PipUtils { private const val TAG = "PipUtils" // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. - private const val EPSILON = 1e-7 + // TODO b/377530560: Restore epsilon once a long term fix is merged for non-config-at-end issue. + private const val EPSILON = 0.05f /** * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 886330f3264a..0200e18b5c50 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -286,7 +286,7 @@ public class CompatUIController implements OnDisplaysChangedListener, // we need to ignore all the incoming TaskInfo until the education // completes. If we come from a double tap we follow the normal flow. final boolean topActivityPillarboxed = - taskInfo.appCompatTaskInfo.isTopActivityPillarboxed(); + taskInfo.appCompatTaskInfo.isTopActivityPillarboxShaped(); final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo); final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 706a67821a30..a472f79c98e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -779,7 +779,8 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, ReturnToDragStartAnimator returnToDragStartAnimator, - @DynamicOverride DesktopRepository desktopRepository) { + @DynamicOverride DesktopRepository desktopRepository, + DesktopModeEventLogger desktopModeEventLogger) { return new DesktopTilingDecorViewModel( context, displayController, @@ -789,7 +790,8 @@ public abstract class WMShellModule { shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - desktopRepository + desktopRepository, + desktopModeEventLogger ); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index 48bb2a8b4a74..cefcb757690f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -205,6 +205,11 @@ class DesktopMixedTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishCallback: TransitionFinishCallback, ): Boolean { + val launchChange = findDesktopTaskChange(info, pending.launchingTask) + if (launchChange == null) { + logV("No launch Change, returning") + return false + } // Check if there's also an immersive change during this launch. val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask -> findDesktopTaskChange(info, exitingTask) @@ -212,8 +217,6 @@ class DesktopMixedTransitionHandler( val minimizeChange = pending.minimizingTask?.let { minimizingTask -> findDesktopTaskChange(info, minimizingTask) } - val launchChange = findDesktopTaskChange(info, pending.launchingTask) - ?: error("Should have pending launching task change") var subAnimationCount = -1 var combinedWct: WindowContainerTransaction? = null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index edcc877ef58e..c7cf31081c8b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -275,7 +275,7 @@ private fun TaskInfo.hasPortraitTopActivity(): Boolean { } // Then check if the activity is portrait when letterboxed - appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed + appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxShaped // Then check if the activity is portrait appBounds != null -> appBounds.height() > appBounds.width() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index f8d2011d0934..b618bf1215ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -53,6 +53,7 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.protolog.ProtoLog; +import com.android.window.flags.Flags; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; @@ -72,6 +73,9 @@ import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; public class KeyguardTransitionHandler implements Transitions.TransitionHandler, KeyguardChangeListener, TaskStackListenerCallback { + private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS = + Flags.ensureKeyguardDoesTransitionStarting(); + private static final String TAG = "KeyguardTransition"; private final Transitions mTransitions; @@ -194,7 +198,7 @@ public class KeyguardTransitionHandler // Occlude/unocclude animations are only played if the keyguard is locked. if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { - if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) { + if (isKeyguardOccluding(info)) { if (hasOpeningDream(info)) { return startAnimation(mOccludeByDreamTransition, "occlude-by-dream", transition, info, startTransaction, finishTransaction, finishCallback); @@ -202,7 +206,7 @@ public class KeyguardTransitionHandler return startAnimation(mOccludeTransition, "occlude", transition, info, startTransaction, finishTransaction, finishCallback); } - } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { + } else if (isKeyguardUnoccluding(info)) { return startAnimation(mUnoccludeTransition, "unocclude", transition, info, startTransaction, finishTransaction, finishCallback); } @@ -325,6 +329,36 @@ public class KeyguardTransitionHandler return false; } + private static boolean isKeyguardOccluding(@NonNull TransitionInfo info) { + if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { + return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0; + } + + for (int i = 0; i < info.getChanges().size(); i++) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA) + && change.getMode() == TRANSIT_TO_FRONT) { + return true; + } + } + return false; + } + + private static boolean isKeyguardUnoccluding(@NonNull TransitionInfo info) { + if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { + return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0; + } + + for (int i = 0; i < info.getChanges().size(); i++) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA) + && change.getMode() == TRANSIT_TO_BACK) { + return true; + } + } + return false; + } + private void finishAnimationImmediately(IBinder transition, StartedTransition playing) { final IBinder fakeTransition = new Binder(); final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java index 24077a35d41c..026482004d51 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java @@ -24,7 +24,6 @@ import android.view.Choreographer; import android.view.SurfaceControl; import com.android.wm.shell.R; -import com.android.wm.shell.transition.Transitions; /** * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition. @@ -180,8 +179,7 @@ public class PipSurfaceTransactionHelper { // destination are different. final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH; final Rect crop = mTmpDestinationRect; - crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH - : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH); + crop.set(0, 0, destW, destH); // Inverse scale for crop to fit in screen coordinates. crop.scale(1 / scale); crop.offset(insets.left, insets.top); @@ -200,8 +198,8 @@ public class PipSurfaceTransactionHelper { } } mTmpTransform.setScale(scale, scale); - mTmpTransform.postRotate(degrees); mTmpTransform.postTranslate(positionX, positionY); + mTmpTransform.postRotate(degrees); tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop); return this; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java index 3f9b0c30e314..fb1aba399585 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java @@ -16,6 +16,8 @@ package com.android.wm.shell.pip2.animation; +import static android.view.Surface.ROTATION_90; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; @@ -73,6 +75,7 @@ public class PipExpandAnimator extends ValueAnimator { mAnimationStartCallback.run(); } if (mStartTransaction != null) { + onExpandAnimationUpdate(mStartTransaction, 0f); mStartTransaction.apply(); } } @@ -81,13 +84,7 @@ public class PipExpandAnimator extends ValueAnimator { public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (mFinishTransaction != null) { - // finishTransaction might override some state (eg. corner radii) so we want to - // manually set the state to the end of the animation - mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, - mSourceRectHint, mBaseBounds, mAnimatedRect, getInsets(1f), - false /* isInPipDirection */, 1f) - .round(mFinishTransaction, mLeash, false /* applyCornerRadius */) - .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */); + onExpandAnimationUpdate(mFinishTransaction, 1f); } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); @@ -102,14 +99,7 @@ public class PipExpandAnimator extends ValueAnimator { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); final float fraction = getAnimatedFraction(); - - // TODO (b/350801661): implement fixed rotation - Rect insets = getInsets(fraction); - mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, - mBaseBounds, mAnimatedRect, - insets, false /* isInPipDirection */, fraction) - .round(tx, mLeash, false /* applyCornerRadius */) - .shadow(tx, mLeash, false /* applyCornerRadius */); + onExpandAnimationUpdate(tx, fraction); tx.apply(); } }; @@ -167,6 +157,32 @@ public class PipExpandAnimator extends ValueAnimator { mAnimationEndCallback = runnable; } + private void onExpandAnimationUpdate(SurfaceControl.Transaction tx, float fraction) { + Rect insets = getInsets(fraction); + if (mRotation == Surface.ROTATION_0) { + mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mBaseBounds, + mAnimatedRect, insets, false /* isInPipDirection */, fraction); + } else { + // Fixed rotation case. + Rect start = mStartBounds; + Rect end = mEndBounds; + float degrees, x, y; + x = fraction * (end.left - start.left) + start.left; + y = fraction * (end.top - start.top) + start.top; + + if (mRotation == ROTATION_90) { + degrees = 90 * fraction; + } else { + degrees = -90 * fraction; + } + mPipSurfaceTransactionHelper.rotateAndScaleWithCrop(tx, mLeash, mBaseBounds, + mAnimatedRect, insets, degrees, x, y, + true /* isExpanding */, mRotation == ROTATION_90); + } + mPipSurfaceTransactionHelper.round(tx, mLeash, false /* applyCornerRadius */) + .shadow(tx, mLeash, false /* applyShadowRadius */); + } + private Rect getInsets(float fraction) { final Rect startInsets = mSourceRectHintInsets; final Rect endInsets = mZeroInsets; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java index 012dabbbb9f8..4558a9f141c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java @@ -30,6 +30,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.shared.animation.Interpolators; /** * Animator that handles any resize related animation for PIP. @@ -128,6 +129,7 @@ public class PipResizeAnimator extends ValueAnimator { mRectEvaluator = new RectEvaluator(mAnimatedRect); setObjectValues(startBounds, endBounds); + setInterpolator(Interpolators.FAST_OUT_SLOW_IN); addListener(mAnimatorListener); addUpdateListener(mAnimatorUpdateListener); setEvaluator(mRectEvaluator); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 58d2a8577d8c..44900ce1db8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -573,7 +573,7 @@ public class PhonePipMenuController implements PipMenuController, @PipTransitionState.TransitionState int newState, Bundle extra) { switch (newState) { case PipTransitionState.ENTERED_PIP: - attach(mPipTransitionState.mPinnedTaskLeash); + attach(mPipTransitionState.getPinnedTaskLeash()); break; case PipTransitionState.EXITED_PIP: detach(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 9a93371621c9..d3f537b8f904 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -376,18 +376,20 @@ public class PipController implements ConfigurationChangeListener, private void setLauncherKeepClearAreaHeight(boolean visible, int height) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height); - if (visible) { - Rect rect = new Rect( - 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height, - mPipDisplayLayoutState.getDisplayBounds().right, - mPipDisplayLayoutState.getDisplayBounds().bottom); - mPipBoundsState.setNamedUnrestrictedKeepClearArea( - PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); - } else { - mPipBoundsState.setNamedUnrestrictedKeepClearArea( - PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); - } - mPipTouchHandler.onShelfVisibilityChanged(visible, height); + mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { + if (visible) { + Rect rect = new Rect( + 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height, + mPipDisplayLayoutState.getDisplayBounds().right, + mPipDisplayLayoutState.getDisplayBounds().bottom); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); + } else { + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); + } + mPipTouchHandler.onShelfVisibilityChanged(visible, height); + }); } @Override 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 17392bc521d8..3738353dd0a3 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 @@ -785,7 +785,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void handleFlingTransition(SurfaceControl.Transaction startTx, SurfaceControl.Transaction finishTx, Rect destinationBounds) { - startTx.setPosition(mPipTransitionState.mPinnedTaskLeash, + startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(), destinationBounds.left, destinationBounds.top); startTx.apply(); @@ -799,7 +799,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void startResizeAnimation(SurfaceControl.Transaction startTx, SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) { - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkState(pipLeash != null, "No leash cached by mPipTransitionState=" + mPipTransitionState); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java index 751175f0f3e9..d98be55f28e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -531,7 +531,7 @@ public class PipResizeGestureHandler implements // If resize transition was scheduled from this component, handle leash updates. mWaitingForBoundsChangeTransition = false; - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkState(pipLeash != null, "No leash cached by mPipTransitionState=" + mPipTransitionState); 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 8b25b11e3a47..607de0eccd77 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 @@ -118,7 +118,7 @@ public class PipScheduler { public void removePipAfterAnimation() { SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); PipAlphaAnimator animator = new PipAlphaAnimator(mContext, - mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT); + mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT); animator.setAnimationEndCallback(this::scheduleRemovePipImmediately); animator.start(); } @@ -203,7 +203,7 @@ public class PipScheduler { "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG); return; } - SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash(); final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); Matrix transformTensor = new Matrix(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index 2c7584af1f07..2f9371536a16 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -29,6 +29,8 @@ import android.view.SurfaceControl; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; @@ -36,6 +38,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip2.animation.PipResizeAnimator; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.annotations.ShellMainThread; import java.util.ArrayList; @@ -49,7 +52,8 @@ import java.util.List; public class PipTaskListener implements ShellTaskOrganizer.TaskListener, PipTransitionState.PipTransitionStateChangedListener { private static final int ASPECT_RATIO_CHANGE_DURATION = 250; - private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change"; + @VisibleForTesting + static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change"; private final Context mContext; private final PipTransitionState mPipTransitionState; @@ -63,6 +67,8 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, private boolean mWaitingForAspectRatioChange = false; private final List<PipParamsChangedCallback> mPipParamsChangedListeners = new ArrayList<>(); + private PipResizeAnimatorSupplier mPipResizeAnimatorSupplier; + public PipTaskListener(Context context, ShellTaskOrganizer shellTaskOrganizer, PipTransitionState pipTransitionState, @@ -84,6 +90,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP); }); } + mPipResizeAnimatorSupplier = PipResizeAnimator::new; } void setPictureInPictureParams(@Nullable PictureInPictureParams params) { @@ -121,6 +128,9 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, if (mPictureInPictureParams.equals(params)) { return; } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s", + taskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, params); setPictureInPictureParams(params); float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat(); if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) { @@ -167,18 +177,18 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION, PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION); - Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash, + Preconditions.checkNotNull(mPipTransitionState.getPinnedTaskLeash(), "Leash is null for bounds transition."); if (mWaitingForAspectRatioChange) { - PipResizeAnimator animator = new PipResizeAnimator(mContext, - mPipTransitionState.mPinnedTaskLeash, startTx, finishTx, + mWaitingForAspectRatioChange = false; + PipResizeAnimator animator = mPipResizeAnimatorSupplier.get(mContext, + mPipTransitionState.getPinnedTaskLeash(), startTx, finishTx, destinationBounds, mPipBoundsState.getBounds(), destinationBounds, duration, 0f /* delta */); - animator.setAnimationEndCallback(() -> { - mPipScheduler.scheduleFinishResizePip(destinationBounds); - }); + animator.setAnimationEndCallback( + () -> mPipScheduler.scheduleFinishResizePip(destinationBounds)); animator.start(); } break; @@ -192,4 +202,22 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) { } } + + @VisibleForTesting + interface PipResizeAnimatorSupplier { + PipResizeAnimator get(@NonNull Context context, + @NonNull SurfaceControl leash, + @Nullable SurfaceControl.Transaction startTx, + @Nullable SurfaceControl.Transaction finishTx, + @NonNull Rect baseBounds, + @NonNull Rect startBounds, + @NonNull Rect endBounds, + int duration, + float delta); + } + + @VisibleForTesting + void setPipResizeAnimatorSupplier(@NonNull PipResizeAnimatorSupplier supplier) { + mPipResizeAnimatorSupplier = supplier; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 19d293e829ad..65972fb7df48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -231,17 +231,15 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha // KCA triggered movement to wait for other transitions (e.g. due to IME changes). return; } - mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { - boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip() - || mPipBoundsState.hasUserResizedPip()); - int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top - - mPipBoundsState.getBounds().top; + boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip() + || mPipBoundsState.hasUserResizedPip()); + int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top + - mPipBoundsState.getBounds().top; - if (!mIsImeShowing && !hasUserInteracted && delta != 0) { - // If the user hasn't interacted with PiP, we respect the keep clear areas - mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta); - } - }); + if (!mIsImeShowing && !hasUserInteracted && delta != 0) { + // If the user hasn't interacted with PiP, we respect the keep clear areas + mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta); + } }; if (PipUtils.isPip2ExperimentEnabled()) { @@ -877,7 +875,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mPipBoundsState.getMovementBounds().bottom; mMotionHelper.setSpringingToTouch(false); - mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash); + mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.getPinnedTaskLeash()); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it 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 6bf92f69cfb6..ea783e9cadb6 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 @@ -325,9 +325,7 @@ public class PipTransition extends PipTransitionController implements return false; } - SurfaceControl pipLeash = pipChange.getLeash(); - Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition."); - + final SurfaceControl pipLeash = getLeash(pipChange); final Rect destinationBounds = pipChange.getEndAbsBounds(); final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay(); if (swipePipToHomeOverlay != null) { @@ -349,7 +347,7 @@ public class PipTransition extends PipTransitionController implements : startRotation - endRotation; if (delta != ROTATION_0) { mPipTransitionState.setInFixedRotation(true); - handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation); + handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation); } prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange, @@ -399,7 +397,7 @@ public class PipTransition extends PipTransitionController implements final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio); - final SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); // For opening type transitions, if there is a change of mode TO_FRONT/OPEN, // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f @@ -414,15 +412,15 @@ public class PipTransition extends PipTransitionController implements } final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); - int startRotation = pipChange.getStartRotation(); - int endRotation = fixedRotationChange != null + final int startRotation = pipChange.getStartRotation(); + final int endRotation = fixedRotationChange != null ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 : startRotation - endRotation; if (delta != ROTATION_0) { mPipTransitionState.setInFixedRotation(true); - handleBoundsTypeFixedRotation(pipChange, pipActivityChange, + handleBoundsEnterFixedRotation(pipChange, pipActivityChange, fixedRotationChange.getEndFixedRotation()); } @@ -459,7 +457,7 @@ public class PipTransition extends PipTransitionController implements animator.start(); } - private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange, + private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange, TransitionInfo.Change pipActivityChange, int endRotation) { final Rect endBounds = pipTaskChange.getEndAbsBounds(); final Rect endActivityBounds = pipActivityChange.getEndAbsBounds(); @@ -492,6 +490,26 @@ public class PipTransition extends PipTransitionController implements endBounds.top + activityEndOffset.y); } + private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) { + final Rect endBounds = pipTaskChange.getEndAbsBounds(); + final int width = endBounds.width(); + final int height = endBounds.height(); + final int left = endBounds.left; + final int top = endBounds.top; + int newTop, newLeft; + + if (endRotation == Surface.ROTATION_90) { + newLeft = top; + newTop = -(left + width); + } else { + newLeft = -(height + top); + newTop = left; + } + // Modify the endBounds, rotating and placing them potentially off-screen, so that + // as we translate and rotate around the origin, we place them right into the target. + endBounds.set(newLeft, newTop, newLeft + height, newTop + width); + } + private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -503,7 +521,7 @@ public class PipTransition extends PipTransitionController implements } Rect destinationBounds = pipChange.getEndAbsBounds(); - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition."); // Start transition with 0 alpha at the entry bounds. @@ -544,33 +562,51 @@ public class PipTransition extends PipTransitionController implements } } - // for multi activity, we need to manually set the leash layer - if (pipChange.getTaskInfo() == null) { - TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent()); - if (parent != null) { - startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1); - } + // The parent change if we were in a multi-activity PiP; null if single activity PiP. + final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null + ? getChangeByToken(info, pipChange.getParent()) : null; + if (parentBeforePip != null) { + // For multi activity, we need to manually set the leash layer + startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1); } - Rect startBounds = pipChange.getStartAbsBounds(); - Rect endBounds = pipChange.getEndAbsBounds(); - SurfaceControl pipLeash = pipChange.getLeash(); - Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition."); + final Rect startBounds = pipChange.getStartAbsBounds(); + final Rect endBounds = pipChange.getEndAbsBounds(); + final SurfaceControl pipLeash = getLeash(pipChange); - Rect sourceRectHint = null; - if (pipChange.getTaskInfo() != null - && pipChange.getTaskInfo().pictureInPictureParams != null) { + PictureInPictureParams params = null; + if (pipChange.getTaskInfo() != null) { // single activity - sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint(); - } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) { + params = pipChange.getTaskInfo().pictureInPictureParams; + } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) { // multi activity - sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint(); + params = parentBeforePip.getTaskInfo().pictureInPictureParams; + } + final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds, + startBounds); + + final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + final int startRotation = pipChange.getStartRotation(); + final int endRotation = fixedRotationChange != null + ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; + final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 + : endRotation - startRotation; + + if (delta != ROTATION_0) { + handleExpandFixedRotation(pipChange, endRotation); } PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash, startTransaction, finishTransaction, endBounds, startBounds, endBounds, - sourceRectHint, Surface.ROTATION_0); - animator.setAnimationEndCallback(this::finishTransition); + sourceRectHint, delta); + animator.setAnimationEndCallback(() -> { + if (parentBeforePip != null) { + // TODO b/377362511: Animate local leash instead to also handle letterbox case. + // For multi-activity, set the crop to be null + finishTransaction.setCrop(pipLeash, null); + } + finishTransition(); + }); animator.start(); return true; } @@ -717,6 +753,13 @@ public class PipTransition extends PipTransitionController implements } } + @NonNull + private SurfaceControl getLeash(TransitionInfo.Change change) { + SurfaceControl leash = change.getLeash(); + Preconditions.checkNotNull(leash, "Leash is null for change=" + change); + return leash; + } + // // Miscellaneous callbacks and listeners // @@ -754,17 +797,17 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState.mPipTaskToken = extra.getParcelable( PIP_TASK_TOKEN, WindowContainerToken.class); - mPipTransitionState.mPinnedTaskLeash = extra.getParcelable( - PIP_TASK_LEASH, SurfaceControl.class); + mPipTransitionState.setPinnedTaskLeash(extra.getParcelable( + PIP_TASK_LEASH, SurfaceControl.class)); boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null - && mPipTransitionState.mPinnedTaskLeash != null; + && mPipTransitionState.getPinnedTaskLeash() != null; Preconditions.checkState(hasValidTokenAndLeash, "Unexpected bundle for " + mPipTransitionState); break; case PipTransitionState.EXITED_PIP: mPipTransitionState.mPipTaskToken = null; - mPipTransitionState.mPinnedTaskLeash = null; + mPipTransitionState.setPinnedTaskLeash(null); break; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java index ccdd66b5d1a8..03e06f906015 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java @@ -142,7 +142,7 @@ public class PipTransitionState { // pinned PiP task's leash @Nullable - SurfaceControl mPinnedTaskLeash; + private SurfaceControl mPinnedTaskLeash; // Overlay leash potentially used during swipe PiP to home transition; // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid. @@ -304,6 +304,14 @@ public class PipTransitionState { mSwipePipToHomeAppBounds.setEmpty(); } + @Nullable SurfaceControl getPinnedTaskLeash() { + return mPinnedTaskLeash; + } + + void setPinnedTaskLeash(@Nullable SurfaceControl leash) { + mPinnedTaskLeash = leash; + } + /** * @return true if either in swipe or button-nav fixed rotation. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl index 799028a5507a..4a301cc0b603 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -24,7 +24,7 @@ import android.os.Bundle; import com.android.wm.shell.recents.IRecentsAnimationRunner; import com.android.wm.shell.recents.IRecentTasksListener; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; /** * Interface that is exposed to remote callers to fetch recent tasks. @@ -44,7 +44,7 @@ interface IRecentTasks { /** * Gets the set of recent tasks. */ - GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; + GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; /** * Gets the set of running tasks. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl index 371bdd5c6469..b58f0681c571 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl @@ -18,6 +18,8 @@ package com.android.wm.shell.recents; import android.app.ActivityManager.RunningTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; + /** * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. */ @@ -44,8 +46,8 @@ oneway interface IRecentTasksListener { void onRunningTaskChanged(in RunningTaskInfo taskInfo); /** A task has moved to front. */ - oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo); + void onTaskMovedToFront(in GroupedTaskInfo[] visibleTasks); /** A task info has changed. */ - oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo); + void onTaskInfoChanged(in RunningTaskInfo taskInfo); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java index 8c5d1e7e069d..364a087211c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -19,7 +19,7 @@ package com.android.wm.shell.recents; import android.annotation.Nullable; import android.graphics.Color; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import java.util.List; @@ -35,7 +35,7 @@ public interface RecentTasks { * Gets the set of recent tasks. */ default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor, - Consumer<List<GroupedRecentTaskInfo>> callback) { + Consumer<List<GroupedTaskInfo>> callback) { } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index faa20159f64a..9911669d2cb8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -24,10 +24,12 @@ import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_R import android.Manifest; import android.annotation.RequiresPermission; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.KeyguardManager; import android.app.PendingIntent; +import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -55,7 +57,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -379,7 +381,8 @@ public class RecentTasksController implements TaskStackListenerCallback, return; } try { - mListener.onTaskMovedToFront(taskInfo); + GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo); + mListener.onTaskMovedToFront(new GroupedTaskInfo[]{ runningTask }); } catch (RemoteException e) { Slog.w(TAG, "Failed call onTaskMovedToFront", e); } @@ -407,27 +410,27 @@ public class RecentTasksController implements TaskStackListenerCallback, } @VisibleForTesting - ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { + ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { // Note: the returned task list is from the most-recent to least-recent order - final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks( + final List<RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks( maxNum, flags, userId); // Make a mapping of task id -> task info - final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>(); + final SparseArray<TaskInfo> rawMapping = new SparseArray<>(); for (int i = 0; i < rawList.size(); i++) { - final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + final TaskInfo taskInfo = rawList.get(i); rawMapping.put(taskInfo.taskId, taskInfo); } - ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>(); + ArrayList<TaskInfo> freeformTasks = new ArrayList<>(); Set<Integer> minimizedFreeformTasks = new HashSet<>(); int mostRecentFreeformTaskIndex = Integer.MAX_VALUE; // Pull out the pairs as we iterate back in the list - ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>(); + ArrayList<GroupedTaskInfo> recentTasks = new ArrayList<>(); for (int i = 0; i < rawList.size(); i++) { - final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + final RecentTaskInfo taskInfo = rawList.get(i); if (!rawMapping.contains(taskInfo.taskId)) { // If it's not in the mapping, then it was already paired with another task continue; @@ -460,20 +463,20 @@ public class RecentTasksController implements TaskStackListenerCallback, final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID); if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains( pairedTaskId)) { - final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); + final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); - recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, + recentTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); } else { - recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo)); + recentTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); } } // Add a special entry for freeform tasks if (!freeformTasks.isEmpty()) { recentTasks.add(mostRecentFreeformTaskIndex, - GroupedRecentTaskInfo.forFreeformTasks( - freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]), + GroupedTaskInfo.forFreeformTasks( + freeformTasks, minimizedFreeformTasks)); } @@ -514,16 +517,16 @@ public class RecentTasksController implements TaskStackListenerCallback, * {@param ignoreTaskToken} if it is non-null. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName, + public RecentTaskInfo findTaskInBackground(ComponentName componentName, int userId, @Nullable WindowContainerToken ignoreTaskToken) { if (componentName == null) { return null; } - List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < tasks.size(); i++) { - final ActivityManager.RecentTaskInfo task = tasks.get(i); + final RecentTaskInfo task = tasks.get(i); if (task.isVisible) { continue; } @@ -541,12 +544,12 @@ public class RecentTasksController implements TaskStackListenerCallback, * Find the background task that match the given taskId. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) { - List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + public RecentTaskInfo findTaskInBackground(int taskId) { + List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < tasks.size(); i++) { - final ActivityManager.RecentTaskInfo task = tasks.get(i); + final RecentTaskInfo task = tasks.get(i); if (task.isVisible) { continue; } @@ -570,7 +573,7 @@ public class RecentTasksController implements TaskStackListenerCallback, pw.println(prefix + TAG); pw.println(prefix + " mListener=" + mListener); pw.println(prefix + "Tasks:"); - ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, + ArrayList<GroupedTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < recentTasks.size(); i++) { pw.println(innerPrefix + recentTasks.get(i)); @@ -584,9 +587,9 @@ public class RecentTasksController implements TaskStackListenerCallback, private class RecentTasksImpl implements RecentTasks { @Override public void getRecentTasks(int maxNum, int flags, int userId, Executor executor, - Consumer<List<GroupedRecentTaskInfo>> callback) { + Consumer<List<GroupedTaskInfo>> callback) { mMainExecutor.execute(() -> { - List<GroupedRecentTaskInfo> tasks = + List<GroupedTaskInfo> tasks = RecentTasksController.this.getRecentTasks(maxNum, flags, userId); executor.execute(() -> callback.accept(tasks)); }); @@ -650,7 +653,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { + public void onTaskMovedToFront(GroupedTaskInfo[] taskInfo) { mListener.call(l -> l.onTaskMovedToFront(taskInfo)); } @@ -692,17 +695,20 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) + public GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) throws RemoteException { if (mController == null) { // The controller is already invalidated -- just return an empty task list for now - return new GroupedRecentTaskInfo[0]; + return new GroupedTaskInfo[0]; } - final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null}; + final GroupedTaskInfo[][] out = new GroupedTaskInfo[][]{null}; executeRemoteCallWithTaskPermission(mController, "getRecentTasks", - (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId) - .toArray(new GroupedRecentTaskInfo[0]), + (controller) -> { + List<GroupedTaskInfo> tasks = controller.getRecentTasks( + maxNum, flags, userId); + out[0] = tasks.toArray(new GroupedTaskInfo[0]); + }, true /* blocking */); return out[0]; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index ec58292b352c..29e4b5bca5cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -395,10 +395,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { continue; } // No default animation for this, so just update bounds/position. - final int rootIdx = TransitionUtil.rootIndexFor(change, info); - startTransaction.setPosition(change.getLeash(), - change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x, - change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y); + if (change.getParent() == null) { + // For independent change without a parent, we have reparented it to the root + // leash in Transitions#setupAnimHierarchy. + final int rootIdx = TransitionUtil.rootIndexFor(change, info); + startTransaction.setPosition(change.getLeash(), + change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x, + change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y); + } else { + startTransaction.setPosition(change.getLeash(), + change.getEndRelOffset().x, change.getEndRelOffset().y); + } // Seamless display transition doesn't need to animate. if (isSeamlessDisplayChange) continue; if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index be4fd7c5eeec..7265fb8f8027 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -24,6 +24,8 @@ import static android.content.pm.PackageManager.FEATURE_PC; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.view.WindowManager.TRANSIT_CHANGE; +import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; + import android.app.ActivityManager.RunningTaskInfo; import android.content.ContentResolver; import android.content.Context; @@ -195,7 +197,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT return; } - decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + if (enableDisplayFocusInShellTransitions()) { + // Pass the current global focus status to avoid updates outside of a ShellTransition. + decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + } else { + decoration.relayout(taskInfo, taskInfo.isFocused); + } } @Override @@ -496,4 +503,4 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT return Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0; } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 17e3dd2fc68e..f2d8a782de34 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -31,6 +31,7 @@ import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.statusBars; import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU; +import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; @@ -468,7 +469,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, removeTaskFromEventReceiver(oldTaskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId); } - decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + if (enableDisplayFocusInShellTransitions()) { + // Pass the current global focus status to avoid updates outside of a ShellTransition. + decoration.relayout(taskInfo, decoration.mHasGlobalFocus); + } else { + decoration.relayout(taskInfo, taskInfo.isFocused); + } mActivityOrientationChangeHandler.ifPresent(handler -> handler.handleActivityOrientationChange(oldTaskInfo, taskInfo)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt index e43c3a613157..61963cde2d06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt @@ -30,6 +30,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -48,6 +49,7 @@ class DesktopTilingDecorViewModel( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val taskRepository: DesktopRepository, + private val desktopModeEventLogger: DesktopModeEventLogger, ) : DisplayChangeController.OnDisplayChangingListener { @VisibleForTesting var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>() @@ -80,6 +82,7 @@ class DesktopTilingDecorViewModel( toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, taskRepository, + desktopModeEventLogger, ) tilingTransitionHandlerByDisplayId.put(displayId, newHandler) newHandler diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt index 209eb5e501b2..6cdc517c9cb7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt @@ -23,6 +23,7 @@ import android.graphics.Rect import android.graphics.Region import android.os.Binder import android.view.LayoutInflater +import android.view.MotionEvent import android.view.RoundedCorner import android.view.SurfaceControl import android.view.SurfaceControlViewHost @@ -39,6 +40,7 @@ import android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER import android.view.WindowlessWindowManager import com.android.wm.shell.R import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import java.util.function.Supplier /** @@ -141,8 +143,9 @@ class DesktopTilingDividerWindowManager( t.setRelativeLayer(leash, relativeLeash, 1) } - override fun onDividerMoveStart(pos: Int) { + override fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) { setSlippery(false) + transitionHandler.onDividerHandleDragStart(motionEvent) } /** @@ -161,13 +164,13 @@ class DesktopTilingDividerWindowManager( * Notifies the transition handler of tiling operations ending, which might result in resizing * WindowContainerTransactions if the sizes of the tiled tasks changed. */ - override fun onDividerMovedEnd(pos: Int) { + override fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) { setSlippery(true) val t = transactionSupplier.get() t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat()) val dividerWidth = dividerBounds.width() dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom) - transitionHandler.onDividerHandleDragEnd(dividerBounds, t) + transitionHandler.onDividerHandleDragEnd(dividerBounds, t, motionEvent) } private fun getWindowManagerParams(): WindowManager.LayoutParams { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index c46767c3a51d..1c593c0362ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -25,6 +25,7 @@ import android.graphics.Rect import android.os.IBinder import android.os.UserHandle import android.util.Slog +import android.view.MotionEvent import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_CHANGE @@ -44,6 +45,8 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -70,6 +73,7 @@ class DesktopTilingWindowDecoration( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val taskRepository: DesktopRepository, + private val desktopModeEventLogger: DesktopModeEventLogger, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, ) : Transitions.TransitionHandler, @@ -218,6 +222,25 @@ class DesktopTilingWindowDecoration( return tilingManager } + fun onDividerHandleDragStart(motionEvent: MotionEvent) { + val leftTiledTask = leftTaskResizingHelper ?: return + val rightTiledTask = rightTaskResizingHelper ?: return + + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + leftTiledTask.taskInfo, + displayController + ) + + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + rightTiledTask.taskInfo, + displayController + ) + } + fun onDividerHandleMoved(dividerBounds: Rect, t: SurfaceControl.Transaction): Boolean { val leftTiledTask = leftTaskResizingHelper ?: return false val rightTiledTask = rightTaskResizingHelper ?: return false @@ -266,10 +289,32 @@ class DesktopTilingWindowDecoration( return true } - fun onDividerHandleDragEnd(dividerBounds: Rect, t: SurfaceControl.Transaction) { + fun onDividerHandleDragEnd( + dividerBounds: Rect, + t: SurfaceControl.Transaction, + motionEvent: MotionEvent, + ) { val leftTiledTask = leftTaskResizingHelper ?: return val rightTiledTask = rightTaskResizingHelper ?: return + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + leftTiledTask.taskInfo, + leftTiledTask.newBounds.height(), + leftTiledTask.newBounds.width(), + displayController + ) + + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + rightTiledTask.taskInfo, + rightTiledTask.newBounds.height(), + rightTiledTask.newBounds.width(), + displayController + ) + if (leftTiledTask.newBounds == leftTiledTask.bounds) { leftTiledTask.hideVeil() rightTiledTask.hideVeil() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt index b3b30ad4c09e..9799d01afc9f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt @@ -15,14 +15,16 @@ */ package com.android.wm.shell.windowdecor.tiling +import android.view.MotionEvent + /** Divider move callback to whichever entity that handles the moving logic. */ interface DividerMoveCallback { /** Called on the divider move start gesture. */ - fun onDividerMoveStart(pos: Int) + fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) /** Called on the divider moved by dragging it. */ fun onDividerMove(pos: Int): Boolean /** Called on divider move gesture end. */ - fun onDividerMovedEnd(pos: Int) + fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt index 89229051941c..111e28e450bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt @@ -206,7 +206,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { if (!isWithinHandleRegion(yTouchPosInDivider)) return true - callback.onDividerMoveStart(touchPos) + callback.onDividerMoveStart(touchPos, event) setTouching() canResize = true } @@ -230,7 +230,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion if (!canResize) return true if (moving && resized) { dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos - callback.onDividerMovedEnd(dividerBounds.left) + callback.onDividerMovedEnd(dividerBounds.left, event) } moving = false canResize = false diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index df061e368071..b06c2dad4ffc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -447,6 +447,37 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() { + val wct = WindowContainerTransaction() + val launchingTask = createTask(WINDOWING_MODE_FREEFORM) + val nonLaunchTaskChange = createChange(createTask(WINDOWING_MODE_FREEFORM)) + val transition = Binder() + whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull())) + .thenReturn(transition) + mixedHandler.addPendingMixedTransition( + PendingMixedTransition.Launch( + transition = transition, + launchingTask = launchingTask.taskId, + minimizingTask = null, + exitingImmersiveTask = null, + ) + ) + + val started = mixedHandler.startAnimation( + transition, + createTransitionInfo( + TRANSIT_OPEN, + listOf(nonLaunchTaskChange) + ), + SurfaceControl.Transaction(), + SurfaceControl.Transaction(), + ) { } + + assertFalse("Should not start animation without launching desktop task", started) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java index e19a10a78417..b816f0ef041e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java @@ -93,6 +93,17 @@ public class PipExpandAnimatorTest { .thenReturn(mMockTransaction); when(mMockTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat())) .thenReturn(mMockTransaction); + // No-op on the mMockStartTransaction + when(mMockStartTransaction.setAlpha(any(SurfaceControl.class), anyFloat())) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setCrop(any(SurfaceControl.class), any(Rect.class))) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(mMockFinishTransaction); + when(mMockStartTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(mMockFinishTransaction); // Do the same for mMockFinishTransaction when(mMockFinishTransaction.setAlpha(any(SurfaceControl.class), anyFloat())) .thenReturn(mMockFinishTransaction); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java new file mode 100644 index 000000000000..89cb729d17b5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java @@ -0,0 +1,331 @@ +/* + * 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.pip2.phone; + +import static com.android.wm.shell.pip2.phone.PipTaskListener.ANIMATING_ASPECT_RATIO_CHANGE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; +import static org.mockito.kotlin.MatchersKt.eq; +import static org.mockito.kotlin.VerificationKt.clearInvocations; +import static org.mockito.kotlin.VerificationKt.times; +import static org.mockito.kotlin.VerificationKt.verify; +import static org.mockito.kotlin.VerificationKt.verifyZeroInteractions; + +import android.app.ActivityManager; +import android.app.PendingIntent; +import android.app.PictureInPictureParams; +import android.app.RemoteAction; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Rational; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.pip2.animation.PipResizeAnimator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit test against {@link PipTaskListener}. + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class PipTaskListenerTest { + + @Mock private Context mMockContext; + @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; + @Mock private PipTransitionState mMockPipTransitionState; + @Mock private SurfaceControl mMockLeash; + @Mock private PipScheduler mMockPipScheduler; + @Mock private PipBoundsState mMockPipBoundsState; + @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm; + @Mock private ShellExecutor mMockShellExecutor; + + @Mock private Icon mMockIcon; + @Mock private PendingIntent mMockPendingIntent; + + @Mock private PipTaskListener.PipParamsChangedCallback mMockPipParamsChangedCallback; + + @Mock private PipResizeAnimator mMockPipResizeAnimator; + + private ArgumentCaptor<List<RemoteAction>> mRemoteActionListCaptor; + + private PipTaskListener mPipTaskListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mRemoteActionListCaptor = ArgumentCaptor.forClass(List.class); + when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(mMockLeash); + } + + @Test + public void constructor_addPipTransitionStateChangedListener() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + + verify(mMockPipTransitionState).addPipTransitionStateChangedListener(eq(mPipTaskListener)); + } + + @Test + public void setPictureInPictureParams_updatePictureInPictureParams() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + PictureInPictureParams params = mPipTaskListener.getPictureInPictureParams(); + assertEquals(aspectRatio, params.getAspectRatio()); + assertTrue(params.hasSetActions()); + assertEquals(1, params.getActions().size()); + assertEquals(action1, params.getActions().get(0).getTitle()); + } + + @Test + public void setPictureInPictureParams_withActionsChanged_callbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + action1 = "modified action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + verify(mMockPipParamsChangedCallback).onActionsChanged( + mRemoteActionListCaptor.capture(), any()); + assertEquals(1, mRemoteActionListCaptor.getValue().size()); + assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle()); + } + + @Test + public void setPictureInPictureParams_withoutActionsChanged_doesNotCallbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + verifyZeroInteractions(mMockPipParamsChangedCallback); + } + + @Test + public void onTaskInfoChanged_withActionsChanged_callbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + clearInvocations(mMockPipBoundsState); + action1 = "modified action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verify(mMockPipTransitionState, times(0)) + .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + verify(mMockPipParamsChangedCallback).onActionsChanged( + mRemoteActionListCaptor.capture(), any()); + assertEquals(1, mRemoteActionListCaptor.getValue().size()); + assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle()); + } + + @Test + public void onTaskInfoChanged_withAspectRatioChanged_callbackAspectRatioChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + clearInvocations(mMockPipBoundsState); + aspectRatio = new Rational(16, 9); + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verify(mMockPipTransitionState).setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + verifyZeroInteractions(mMockPipParamsChangedCallback); + } + + @Test + public void onTaskInfoChanged_withoutParamsChanged_doesNotCallbackAspectRatioChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verifyZeroInteractions(mMockPipParamsChangedCallback); + verify(mMockPipTransitionState, times(0)) + .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + } + + @Test + public void onPipTransitionStateChanged_scheduledBoundsChangeWithAspectRatioChange_schedule() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extras); + + verify(mMockPipScheduler).scheduleAnimateResizePip(any(), anyBoolean(), anyInt()); + } + + @Test + public void onPipTransitionStateChanged_scheduledBoundsChangeWithoutAspectRatioChange_noop() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + + verifyZeroInteractions(mMockPipScheduler); + } + + @Test + public void onPipTransitionStateChanged_changingPipBoundsWaitAspectRatioChange_animate() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true); + extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS, + new Rect(0, 0, 100, 100)); + when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200)); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + mPipTaskListener.setPipResizeAnimatorSupplier( + (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds, + duration, delta) -> mMockPipResizeAnimator); + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + PipTransitionState.CHANGING_PIP_BOUNDS, + extras); + + verify(mMockPipResizeAnimator, times(1)).start(); + } + + @Test + public void onPipTransitionStateChanged_changingPipBoundsNotAspectRatioChange_noop() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false); + extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS, + new Rect(0, 0, 100, 100)); + when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200)); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + mPipTaskListener.setPipResizeAnimatorSupplier( + (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds, + duration, delta) -> mMockPipResizeAnimator); + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + PipTransitionState.CHANGING_PIP_BOUNDS, + extras); + + verify(mMockPipResizeAnimator, times(0)).start(); + } + + private PictureInPictureParams getPictureInPictureParams(Rational aspectRatio, + String... actions) { + final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder(); + builder.setAspectRatio(aspectRatio); + final List<RemoteAction> remoteActions = new ArrayList<>(); + for (String action : actions) { + remoteActions.add(new RemoteAction(mMockIcon, action, action, mMockPendingIntent)); + } + if (!remoteActions.isEmpty()) { + builder.setActions(remoteActions); + } + return builder.build(); + } + + private ActivityManager.RunningTaskInfo getTaskInfo(Rational aspectRatio, + String... actions) { + final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.pictureInPictureParams = getPictureInPictureParams(aspectRatio, actions); + return taskInfo; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt index 0c100fca2036..2b30bc360d06 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.recents import android.app.ActivityManager +import android.app.TaskInfo import android.graphics.Rect import android.os.Parcel import android.testing.AndroidTestingRunner @@ -24,11 +25,10 @@ import android.window.IWindowContainerToken import android.window.WindowContainerToken import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.shared.GroupedRecentTaskInfo -import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT +import com.android.wm.shell.shared.GroupedTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT import com.android.wm.shell.shared.split.SplitBounds import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Correspondence @@ -39,15 +39,15 @@ import org.junit.runner.RunWith import org.mockito.Mockito.mock /** - * Tests for [GroupedRecentTaskInfo] + * Tests for [GroupedTaskInfo] */ @SmallTest @RunWith(AndroidTestingRunner::class) -class GroupedRecentTaskInfoTest : ShellTestCase() { +class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSingleTask_hasCorrectType() { - assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_SINGLE) + assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN) } @Test @@ -117,8 +117,9 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) - assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SINGLE) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) + assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FULLSCREEN) assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) assertThat(recentTaskInfoParcel.taskInfo2).isNull() } @@ -130,7 +131,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SPLIT) assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) assertThat(recentTaskInfoParcel.taskInfo2).isNotNull() @@ -146,11 +148,12 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) assertThat(recentTaskInfoParcel.taskInfoList).hasSize(3) // Only compare task ids - val taskIdComparator = Correspondence.transforming<ActivityManager.RecentTaskInfo, Int>( + val taskIdComparator = Correspondence.transforming<TaskInfo, Int>( { it?.taskId }, "has taskId of" ) assertThat(recentTaskInfoParcel.taskInfoList).comparingElementsUsing(taskIdComparator) @@ -167,7 +170,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray()) } @@ -177,24 +181,24 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { token = WindowContainerToken(mock(IWindowContainerToken::class.java)) } - private fun singleTaskGroupInfo(): GroupedRecentTaskInfo { + private fun singleTaskGroupInfo(): GroupedTaskInfo { val task = createTaskInfo(id = 1) - return GroupedRecentTaskInfo.forSingleTask(task) + return GroupedTaskInfo.forFullscreenTasks(task) } - private fun splitTasksGroupInfo(): GroupedRecentTaskInfo { + private fun splitTasksGroupInfo(): GroupedTaskInfo { val task1 = createTaskInfo(id = 1) val task2 = createTaskInfo(id = 2) val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50) - return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds) + return GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds) } private fun freeformTasksGroupInfo( freeformTaskIds: Array<Int>, minimizedTaskIds: Array<Int> = emptyArray() - ): GroupedRecentTaskInfo { - return GroupedRecentTaskInfo.forFreeformTasks( - freeformTaskIds.map { createTaskInfo(it) }.toTypedArray(), + ): GroupedTaskInfo { + return GroupedTaskInfo.forFreeformTasks( + freeformTaskIds.map { createTaskInfo(it) }.toList(), minimizedTaskIds.toSet()) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 9b73d53e0639..dede583ca970 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.when; import static java.lang.Integer.MAX_VALUE; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager; import android.app.KeyguardManager; import android.content.ComponentName; @@ -71,7 +72,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.ShellSharedConstants; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.split.SplitBounds; @@ -193,8 +194,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddRemoveSplitNotifyChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class)); @@ -207,8 +208,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddSameSplitBoundsInfoSkipNotifyChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); // Verify only one update if the split info is the same @@ -223,13 +224,13 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); setRawList(t1, t2, t3); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t2.taskId, -1, @@ -238,12 +239,12 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_withPairs() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); - ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t6 = makeTaskInfo(6); setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] @@ -255,8 +256,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t2.taskId, t4.taskId, @@ -267,14 +268,14 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() { @SuppressWarnings("unchecked") - final List<GroupedRecentTaskInfo>[] recentTasks = new List[1]; - Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument; - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); - ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + final List<GroupedTaskInfo>[] recentTasks = new List[1]; + Consumer<List<GroupedTaskInfo>> consumer = argument -> recentTasks[0] = argument; + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t6 = makeTaskInfo(6); setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] @@ -287,7 +288,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); mRecentTasksController.asRecentTasks() - .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer); + .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, + consumer); mMainExecutor.flushAll(); assertGroupedTasksListEquals(recentTasks[0], @@ -299,28 +301,28 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 2 freeform tasks should be grouped into one, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); - GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2); + GroupedTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo singleGroup1 = recentTasks.get(1); + GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); // Check freeform group entries assertEquals(t1, freeformGroup.getTaskInfoList().get(0)); @@ -333,11 +335,11 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); setRawList(t1, t2, t3, t4, t5); SplitBounds pair1Bounds = @@ -347,19 +349,19 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(3)).thenReturn(true); when(mDesktopRepository.isActiveTask(5)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo splitGroup = recentTasks.get(0); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup = recentTasks.get(2); + GroupedTaskInfo splitGroup = recentTasks.get(0); + GroupedTaskInfo freeformGroup = recentTasks.get(1); + GroupedTaskInfo singleGroup = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_SPLIT, splitGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup.getType()); // Check freeform group entries assertEquals(t3, freeformGroup.getTaskInfoList().get(0)); @@ -378,24 +380,24 @@ public class RecentTasksControllerTest extends ShellTestCase { ExtendedMockito.doReturn(false) .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // Expect no grouping of tasks assertEquals(4, recentTasks.size()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(0).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(1).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(2).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(3).getType()); assertEquals(t1, recentTasks.get(0).getTaskInfo1()); assertEquals(t2, recentTasks.get(1).getTaskInfo1()); @@ -405,11 +407,11 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); setRawList(t1, t2, t3, t4, t5); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); @@ -417,19 +419,19 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(5)).thenReturn(true); when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); - GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2); + GroupedTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo singleGroup1 = recentTasks.get(1); + GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); // Check freeform group entries assertEquals(3, freeformGroup.getTaskInfoList().size()); @@ -445,8 +447,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400); t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450); @@ -455,11 +457,11 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(2)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertEquals(1, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo freeformGroup = recentTasks.get(0); // Check bounds assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get( @@ -478,9 +480,9 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testRemovedTaskRemovesSplit() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); setRawList(t1, t2, t3); // Add a pair @@ -500,7 +502,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testTaskWindowingModeChangedNotifiesChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t1 = makeTaskInfo(1); setRawList(t1); // Remove one of the tasks and ensure the pair is removed @@ -607,7 +609,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo); - verify(mRecentTasksListener).onTaskMovedToFront(taskInfo); + GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo); + verify(mRecentTasksListener).onTaskMovedToFront(eq(new GroupedTaskInfo[] { runningTask })); } @Test @@ -656,8 +659,8 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to create a task with a given task id. */ - private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) { - ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo(); + private RecentTaskInfo makeTaskInfo(int taskId) { + RecentTaskInfo info = new RecentTaskInfo(); info.taskId = taskId; info.lastNonFullscreenBounds = new Rect(); return info; @@ -676,10 +679,10 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to set the raw task list on the controller. */ - private ArrayList<ActivityManager.RecentTaskInfo> setRawList( - ActivityManager.RecentTaskInfo... tasks) { - ArrayList<ActivityManager.RecentTaskInfo> rawList = new ArrayList<>(); - for (ActivityManager.RecentTaskInfo task : tasks) { + private ArrayList<RecentTaskInfo> setRawList( + RecentTaskInfo... tasks) { + ArrayList<RecentTaskInfo> rawList = new ArrayList<>(); + for (RecentTaskInfo task : tasks) { rawList.add(task); } doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(), @@ -693,11 +696,11 @@ public class RecentTasksControllerTest extends ShellTestCase { * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in * the grouped task list */ - private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks, + private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks, int... expectedTaskIds) { int[] flattenedTaskIds = new int[recentTasks.size() * 2]; for (int i = 0; i < recentTasks.size(); i++) { - GroupedRecentTaskInfo pair = recentTasks.get(i); + GroupedTaskInfo pair = recentTasks.get(i); int taskId1 = pair.getTaskInfo1().taskId; flattenedTaskIds[2 * i] = taskId1; flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index ef3af8e7bdac..966651f19711 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -217,7 +217,6 @@ public class SplitScreenControllerTests extends ShellTestCase { // Put the same component to the top running task ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, @@ -238,7 +237,6 @@ public class SplitScreenControllerTests extends ShellTestCase { // Put the same component to the top running task ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); // Put the same component into a task in the background ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 6cde0569796d..2442a55d78d0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -19,6 +19,7 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -363,6 +364,25 @@ public class ShellTransitionTests extends ShellTestCase { } @Test + public void testTransitionFilterWindowingMode() { + TransitionFilter filter = new TransitionFilter(); + filter.mRequirements = + new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()}; + filter.mRequirements[0].mWindowingMode = WINDOWING_MODE_FREEFORM; + filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; + + final TransitionInfo fullscreenStd = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN, createTaskInfo( + 1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD)).build(); + assertFalse(filter.matches(fullscreenStd)); + + final TransitionInfo freeformStd = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN, createTaskInfo( + 1, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD)).build(); + assertTrue(filter.matches(freeformStd)); + } + + @Test public void testTransitionFilterMultiRequirement() { // filter that requires at-least one opening and one closing app TransitionFilter filter = new TransitionFilter(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 56267174ba75..956100d9bc03 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -1307,6 +1307,48 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decor).closeMaximizeMenu() } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) + fun testOnTaskInfoChanged_enableShellTransitionsFlag() { + val task = createTask( + windowingMode = WINDOWING_MODE_FREEFORM + ) + val taskSurface = SurfaceControl() + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task, taskSurface) + assertTrue(windowDecorByTaskIdSpy.contains(task.taskId)) + + decoration.mHasGlobalFocus = true + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(task, true) + + decoration.mHasGlobalFocus = false + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(task, false) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS) + fun testOnTaskInfoChanged_disableShellTransitionsFlag() { + val task = createTask( + windowingMode = WINDOWING_MODE_FREEFORM + ) + val taskSurface = SurfaceControl() + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task, taskSurface) + assertTrue(windowDecorByTaskIdSpy.contains(task.taskId)) + + task.isFocused = true + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(task, true) + + task.isFocused = false + desktopModeWindowDecorViewModel.onTaskInfoChanged(task) + verify(decoration).relayout(task, false) + } + private fun createOpenTaskDecoration( @WindowingMode windowingMode: Int, taskSurface: SurfaceControl = SurfaceControl(), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt index 80ad1df44a1b..d44c01592ff7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt @@ -25,6 +25,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -52,6 +53,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val transitionsMock: Transitions = mock() private val shellTaskOrganizerMock: ShellTaskOrganizer = mock() private val desktopRepository: DesktopRepository = mock() + private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val toggleResizeDesktopTaskTransitionHandlerMock: ToggleResizeDesktopTaskTransitionHandler = mock() @@ -74,6 +76,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandlerMock, returnToDragStartAnimatorMock, desktopRepository, + desktopModeEventLogger, ) whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt index f371f5223419..057d8fa3adb0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt @@ -21,6 +21,7 @@ import android.content.res.Resources import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner +import android.view.MotionEvent import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_TO_FRONT @@ -33,6 +34,8 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask @@ -91,7 +94,9 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private val info: TransitionInfo = mock() private val finishCallback: Transitions.TransitionFinishCallback = mock() private val desktopRepository: DesktopRepository = mock() + private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock() + private val motionEvent: MotionEvent = mock() private lateinit var tilingDecoration: DesktopTilingWindowDecoration private val split_divider_width = 10 @@ -112,6 +117,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, desktopRepository, + desktopModeEventLogger, ) whenever(context.createContextAsUser(any(), any())).thenReturn(context) } @@ -371,13 +377,13 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { // End moving, no startTransition because bounds did not change. tiledTaskHelper.newBounds.set(BOUNDS) - tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) verify(tiledTaskHelper, times(2)).hideVeil() verify(transitions, never()).startTransition(any(), any(), any()) // Move then end again with bounds changing to ensure startTransition is called. tilingDecoration.onDividerHandleMoved(BOUNDS, transaction) - tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) verify(transitions, times(1)) .startTransition(eq(TRANSIT_CHANGE), any(), eq(tilingDecoration)) // No hide veil until start animation is called. @@ -389,6 +395,64 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { } @Test + fun tiledTasksResizedUsingDividerHandle_shouldLogResizingEvents() { + // Setup + val task1 = createFreeformTask() + val task2 = createFreeformTask() + val stableBounds = STABLE_BOUNDS_MOCK + whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout) + whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(stableBounds) + } + whenever(context.resources).thenReturn(resources) + whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) + desktopWindowDecoration.mTaskInfo = task1 + task1.minWidth = 0 + task1.minHeight = 0 + initTiledTaskHelperMock(task1) + desktopWindowDecoration.mDecorWindowContext = context + whenever(resources.getBoolean(any())).thenReturn(true) + + // Act + tilingDecoration.onAppTiled( + task1, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.RIGHT, + BOUNDS, + ) + tilingDecoration.onAppTiled( + task2, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.LEFT, + BOUNDS, + ) + tilingDecoration.leftTaskResizingHelper = tiledTaskHelper + tilingDecoration.rightTaskResizingHelper = tiledTaskHelper + tilingDecoration.onDividerHandleDragStart(motionEvent) + // Log start event for task1 and task2, but the tasks are the same in + // this test, so we verify the same log twice. + verify(desktopModeEventLogger, times(2)).logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + task1, + displayController, + ) + + tilingDecoration.onDividerHandleMoved(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) + // Log end event for task1 and task2, but the tasks are the same in + // this test, so we verify the same log twice. + verify(desktopModeEventLogger, times(2)).logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + task1, + BOUNDS.height(), + BOUNDS.width(), + displayController, + ) + } + + @Test fun taskTiled_shouldBeRemoved_whenTileBroken() { val task1 = createFreeformTask() val stableBounds = STABLE_BOUNDS_MOCK diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt index c96ce955f217..734815cdd915 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt @@ -68,7 +68,7 @@ class TilingDividerViewTest : ShellTestCase() { val downMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, downMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any()) whenever(dividerMoveCallbackMock.onDividerMove(any())).thenReturn(true) val motionEvent = @@ -79,7 +79,7 @@ class TilingDividerViewTest : ShellTestCase() { val upMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, upMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any(), any()) } @Test @@ -92,12 +92,12 @@ class TilingDividerViewTest : ShellTestCase() { val downMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, downMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any()) val upMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, upMotionEvent) - verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any()) + verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any(), any()) } private fun getMotionEvent(eventTime: Long, action: Int, x: Float, y: Float): MotionEvent { diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index b71abdc011c1..fcb7efc35c94 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -580,6 +580,7 @@ cc_defaults { "utils/Color.cpp", "utils/LinearAllocator.cpp", "utils/StringUtils.cpp", + "utils/StatsUtils.cpp", "utils/TypefaceUtils.cpp", "utils/VectorDrawableUtils.cpp", "AnimationContext.cpp", diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index e074a27db38f..a9a5db8181ba 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -27,8 +27,8 @@ #include <SkColorSpace.h> #include <SkColorType.h> #include <SkEncodedOrigin.h> -#include <SkImageInfo.h> #include <SkGainmapInfo.h> +#include <SkImageInfo.h> #include <SkMatrix.h> #include <SkPaint.h> #include <SkPngChunkReader.h> @@ -43,6 +43,8 @@ #include <memory> +#include "modules/skcms/src/skcms_public.h" + using namespace android; sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index 9cd6e253140e..e5fb75575ac3 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -49,6 +49,7 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikinPaint.fontStyle = resolvedFace->fStyle; minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings(); minikinPaint.fontVariationSettings = paint->getFontVariationOverride(); + minikinPaint.verticalText = paint->isVerticalText(); const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant(); if (familyVariant.has_value()) { diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 7eb849fe6e3d..594ea31387ad 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -158,6 +158,7 @@ public: SkSamplingOptions sampling() const { return SkSamplingOptions(this->filterMode()); } + bool isVerticalText() const { return mVerticalText; } void setVariationOverride(minikin::VariationSettings&& varSettings) { mFontVariationOverride = std::move(varSettings); @@ -202,6 +203,7 @@ private: bool mUnderline = false; bool mDevKern = false; minikin::RunFlag mRunFlag = minikin::RunFlag::NONE; + bool mVerticalText = false; }; } // namespace android diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index 6dfcedc3d918..fa5325d90218 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -49,7 +49,8 @@ Paint::Paint(const Paint& paint) , mStrikeThru(paint.mStrikeThru) , mUnderline(paint.mUnderline) , mDevKern(paint.mDevKern) - , mRunFlag(paint.mRunFlag) {} + , mRunFlag(paint.mRunFlag) + , mVerticalText(paint.mVerticalText) {} Paint::~Paint() {} @@ -71,6 +72,7 @@ Paint& Paint::operator=(const Paint& other) { mUnderline = other.mUnderline; mDevKern = other.mDevKern; mRunFlag = other.mRunFlag; + mVerticalText = other.mVerticalText; return *this; } @@ -83,7 +85,8 @@ bool operator==(const Paint& a, const Paint& b) { a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && a.mTypeface == b.mTypeface && a.mAlign == b.mAlign && a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru && - a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag; + a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag && + a.mVerticalText == b.mVerticalText; } void Paint::reset() { @@ -97,6 +100,7 @@ void Paint::reset() { mStrikeThru = false; mUnderline = false; mDevKern = false; + mVerticalText = false; mRunFlag = minikin::RunFlag::NONE; } @@ -135,6 +139,7 @@ static const uint32_t sForceAutoHinting = 0x800; // flags related to minikin::Paint static const uint32_t sUnderlineFlag = 0x08; static const uint32_t sStrikeThruFlag = 0x10; +static const uint32_t sVerticalTextFlag = 0x1000; static const uint32_t sTextRunLeftEdge = 0x2000; static const uint32_t sTextRunRightEdge = 0x4000; // flags no longer supported on native side (but mirrored for compatibility) @@ -190,6 +195,7 @@ uint32_t Paint::getJavaFlags() const { flags |= -(int)mUnderline & sUnderlineFlag; flags |= -(int)mDevKern & sDevKernFlag; flags |= -(int)mFilterBitmap & sFilterBitmapFlag; + flags |= -(int)mVerticalText & sVerticalTextFlag; if (mRunFlag & minikin::RunFlag::LEFT_EDGE) { flags |= sTextRunLeftEdge; } @@ -206,6 +212,7 @@ void Paint::setJavaFlags(uint32_t flags) { mUnderline = (flags & sUnderlineFlag) != 0; mDevKern = (flags & sDevKernFlag) != 0; mFilterBitmap = (flags & sFilterBitmapFlag) != 0; + mVerticalText = (flags & sVerticalTextFlag) != 0; std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE; if (flags & sTextRunLeftEdge) { diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp index 49a7f73fb3a3..8b43f1db84af 100644 --- a/libs/hwui/jni/BitmapFactory.cpp +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -10,6 +10,7 @@ #include <stdint.h> #include <stdio.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include <memory> @@ -630,6 +631,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, } bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); outputBitmap.notifyPixelsChanged(); + uirenderer::logBitmapDecode(*reuseBitmap); // If a java bitmap was passed in for reuse, pass it back return javaBitmap; } @@ -650,6 +652,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, } } + uirenderer::logBitmapDecode(*hardwareBitmap); return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); } @@ -659,6 +662,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, heapBitmap->setGainmap(std::move(gainmap)); } + uirenderer::logBitmapDecode(*heapBitmap); // now create the java bitmap return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index f7e8e073a272..5ffd5b9016d8 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -19,6 +19,7 @@ #include <HardwareBitmapUploader.h> #include <androidfw/Asset.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include <memory> @@ -376,6 +377,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in recycledBitmap->setGainmap(std::move(gainmap)); } bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul); + uirenderer::logBitmapDecode(*recycledBitmap); return javaBitmap; } @@ -392,12 +394,14 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in hardwareBitmap->setGainmap(std::move(gm)); } } + uirenderer::logBitmapDecode(*hardwareBitmap); return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags); } Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset(); if (hasGainmap && heapBitmap != nullptr) { heapBitmap->setGainmap(std::move(gainmap)); } + uirenderer::logBitmapDecode(*heapBitmap); return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags); } diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index aebc4db37898..90fd3d87cba7 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -37,6 +37,7 @@ #include <hwui/Bitmap.h> #include <hwui/ImageDecoder.h> #include <sys/stat.h> +#include <utils/StatsUtils.h> #include "Bitmap.h" #include "BitmapFactory.h" @@ -485,6 +486,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong hwBitmap->setGainmap(std::move(gm)); } } + uirenderer::logBitmapDecode(*hwBitmap); return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets); } @@ -498,6 +500,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativeBitmap->setImmutable(); } + + uirenderer::logBitmapDecode(*nativeBitmap); return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets); } diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt index 2414299321a9..b5591941453d 100644 --- a/libs/hwui/libhwui.map.txt +++ b/libs/hwui/libhwui.map.txt @@ -67,6 +67,7 @@ LIBHWUI_PLATFORM { SkFILEStream::SkFILEStream*; SkImageInfo::*; SkMemoryStream::SkMemoryStream*; + android::uirenderer::logBitmapDecode*; }; local: *; diff --git a/libs/hwui/utils/StatsUtils.cpp b/libs/hwui/utils/StatsUtils.cpp new file mode 100644 index 000000000000..5c4027e1a846 --- /dev/null +++ b/libs/hwui/utils/StatsUtils.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 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. + */ + +#ifdef __ANDROID__ +#include <dlfcn.h> +#include <log/log.h> +#include <statslog_hwui.h> +#include <statssocket_lazy.h> +#include <utils/Errors.h> + +#include <mutex> +#endif + +#include <unistd.h> + +#include "StatsUtils.h" + +namespace android { +namespace uirenderer { + +#ifdef __ANDROID__ + +namespace { + +int32_t toStatsColorSpaceTransfer(skcms_TFType transferType) { + switch (transferType) { + case skcms_TFType_sRGBish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_SRGBISH; + case skcms_TFType_PQish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_PQISH; + case skcms_TFType_HLGish: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_HLGISH; + default: + return stats::IMAGE_DECODED__COLOR_SPACE_TRANSFER__COLOR_SPACE_TRANSFER_UNKNOWN; + } +} + +int32_t toStatsBitmapFormat(SkColorType type) { + switch (type) { + case kAlpha_8_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_A_8; + case kRGB_565_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGB_565; + case kN32_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_ARGB_8888; + case kRGBA_F16_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_F16; + case kRGBA_1010102_SkColorType: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_RGBA_1010102; + default: + return stats::IMAGE_DECODED__FORMAT__BITMAP_FORMAT_UNKNOWN; + } +} + +} // namespace + +#endif + +void logBitmapDecode(const SkImageInfo& info, bool hasGainmap) { +#ifdef __ANDROID__ + + if (!statssocket::lazy::IsAvailable()) { + std::once_flag once; + std::call_once(once, []() { ALOGD("libstatssocket not available, dropping stats"); }); + return; + } + + skcms_TFType tfnType = skcms_TFType_Invalid; + + if (info.colorSpace()) { + skcms_TransferFunction tfn; + info.colorSpace()->transferFn(&tfn); + tfnType = skcms_TransferFunction_getType(&tfn); + } + + auto status = + stats::stats_write(uirenderer::stats::IMAGE_DECODED, static_cast<int32_t>(getuid()), + uirenderer::toStatsColorSpaceTransfer(tfnType), hasGainmap, + uirenderer::toStatsBitmapFormat(info.colorType())); + ALOGW_IF(status != OK, "Image decoding logging dropped!"); +#endif +} + +void logBitmapDecode(const Bitmap& bitmap) { + logBitmapDecode(bitmap.info(), bitmap.hasGainmap()); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/StatsUtils.h b/libs/hwui/utils/StatsUtils.h new file mode 100644 index 000000000000..0c247014a8eb --- /dev/null +++ b/libs/hwui/utils/StatsUtils.h @@ -0,0 +1,33 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include <SkBitmap.h> +#include <SkColorSpace.h> +#include <SkColorType.h> +#include <cutils/compiler.h> +#include <hwui/Bitmap.h> + +namespace android { +namespace uirenderer { + +ANDROID_API void logBitmapDecode(const SkImageInfo& info, bool hasGainmap); + +ANDROID_API void logBitmapDecode(const Bitmap& bitmap); + +} // namespace uirenderer +} // namespace android diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index 55c8ed5debf8..530d48d3e60b 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -40,6 +40,8 @@ import android.os.ParcelFileDescriptor; import android.os.Trace; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import dalvik.system.VMRuntime; import java.io.IOException; @@ -1208,6 +1210,11 @@ public class ImageReader implements AutoCloseable { default: width = nativeGetWidth(); } + if (Flags.cameraHeifGainmap()) { + if (getFormat() == ImageFormat.HEIC_ULTRAHDR){ + width = ImageReader.this.getWidth(); + } + } return width; } @@ -1227,6 +1234,11 @@ public class ImageReader implements AutoCloseable { default: height = nativeGetHeight(); } + if (Flags.cameraHeifGainmap()) { + if (getFormat() == ImageFormat.HEIC_ULTRAHDR){ + height = ImageReader.this.getHeight(); + } + } return height; } diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index f4caad727407..c7678067f0b5 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -23,6 +23,8 @@ import android.media.Image.Plane; import android.util.Log; import android.util.Size; +import com.android.internal.camera.flags.Flags; + import libcore.io.Memory; import java.nio.ByteBuffer; @@ -44,6 +46,11 @@ class ImageUtils { * are used. */ public static int getNumPlanesForFormat(int format) { + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + return 1; + } + } switch (format) { case ImageFormat.YV12: case ImageFormat.YUV_420_888: @@ -229,6 +236,11 @@ class ImageUtils { public static int getEstimatedNativeAllocBytes(int width, int height, int format, int numImages) { double estimatedBytePerPixel; + if (Flags.cameraHeifGainmap()) { + if (format == ImageFormat.HEIC_ULTRAHDR) { + estimatedBytePerPixel = 0.3; + } + } switch (format) { // 10x compression from RGB_888 case ImageFormat.JPEG: @@ -283,6 +295,11 @@ class ImageUtils { } private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) { + if (Flags.cameraHeifGainmap()) { + if (image.getFormat() == ImageFormat.HEIC_ULTRAHDR){ + return new Size(image.getWidth(), image.getHeight()); + } + } switch (image.getFormat()) { case ImageFormat.YCBCR_P010: case ImageFormat.YV12: diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java index 5cbc81d5f92d..472d7985b304 100644 --- a/media/java/android/media/quality/MediaQualityContract.java +++ b/media/java/android/media/quality/MediaQualityContract.java @@ -21,11 +21,16 @@ import android.annotation.FlaggedApi; import android.media.tv.flags.Flags; /** + * The contract between the media quality service and applications. Contains definitions for the + * commonly used parameter names. * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) public class MediaQualityContract { + /** + * @hide + */ public interface BaseParameters { String PARAMETER_ID = "_id"; String PARAMETER_NAME = "_name"; @@ -33,13 +38,50 @@ public class MediaQualityContract { String PARAMETER_INPUT_ID = "_input_id"; } - public static final class PictureQuality implements BaseParameters { + + /** + * Parameters picture quality. + * @hide + */ + public static final class PictureQuality { + /** + * The brightness. + * + * <p>Type: INTEGER + */ public static final String PARAMETER_BRIGHTNESS = "brightness"; + + /** + * The contrast. + * + * <p>The ratio between the luminance of the brightest white and the darkest black. + * <p>Type: INTEGER + */ public static final String PARAMETER_CONTRAST = "contrast"; + + /** + * The sharpness. + * + * <p>Sharpness indicates the clarity of detail. + * <p>Type: INTEGER + */ public static final String PARAMETER_SHARPNESS = "sharpness"; + + /** + * The saturation. + * + * <p>Saturation indicates the intensity of the color. + * <p>Type: INTEGER + */ public static final String PARAMETER_SATURATION = "saturation"; + + private PictureQuality() { + } } + /** + * @hide + */ public static final class SoundQuality implements BaseParameters { public static final String PARAMETER_BALANCE = "balance"; public static final String PARAMETER_BASS = "bass"; diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index 1237d673162c..38a2025535f4 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -34,7 +34,8 @@ import java.util.List; import java.util.concurrent.Executor; /** - * Expose TV setting APIs for the application to use + * Central system API to the overall media quality, which arbitrates interaction between + * applications and media quality service. * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) @@ -177,6 +178,7 @@ public final class MediaQualityManager { * Gets picture profile by given profile ID. * @return the corresponding picture profile if available; {@code null} if the ID doesn't * exist or the profile is not accessible to the caller. + * @hide */ public PictureProfile getPictureProfileById(long profileId) { try { @@ -187,7 +189,10 @@ public final class MediaQualityManager { } - /** @SystemApi gets profiles that available to the given package */ + /** + * @SystemApi gets profiles that available to the given package + * @hide + */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<PictureProfile> getPictureProfilesByPackage(String packageName) { try { @@ -197,7 +202,10 @@ public final class MediaQualityManager { } } - /** Gets profiles that available to the caller package */ + /** + * Gets profiles that available to the caller. + */ + @NonNull public List<PictureProfile> getAvailablePictureProfiles() { try { return mService.getAvailablePictureProfiles(); @@ -206,7 +214,10 @@ public final class MediaQualityManager { } } - /** @SystemApi all stored picture profiles of all packages */ + /** + * @SystemApi all stored picture profiles of all packages + * @hide + */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<PictureProfile> getAllPictureProfiles() { try { @@ -221,6 +232,7 @@ public final class MediaQualityManager { * Creates a picture profile and store it in the system. * * @return the stored profile with an assigned profile ID. + * @hide */ public PictureProfile createPictureProfile(PictureProfile pp) { try { @@ -233,6 +245,7 @@ public final class MediaQualityManager { /** * Updates an existing picture profile and store it in the system. + * @hide */ public void updatePictureProfile(long profileId, PictureProfile pp) { try { @@ -245,6 +258,7 @@ public final class MediaQualityManager { /** * Removes a picture profile from the system. + * @hide */ public void removePictureProfile(long profileId) { try { @@ -291,6 +305,7 @@ public final class MediaQualityManager { * Gets sound profile by given profile ID. * @return the corresponding sound profile if available; {@code null} if the ID doesn't * exist or the profile is not accessible to the caller. + * @hide */ public SoundProfile getSoundProfileById(long profileId) { try { @@ -301,7 +316,10 @@ public final class MediaQualityManager { } - /** @SystemApi gets profiles that available to the given package */ + /** + * @SystemApi gets profiles that available to the given package + * @hide + */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public List<SoundProfile> getSoundProfilesByPackage(String packageName) { try { @@ -311,7 +329,10 @@ public final class MediaQualityManager { } } - /** Gets profiles that available to the caller package */ + /** + * Gets profiles that available to the caller package + * @hide + */ public List<SoundProfile> getAvailableSoundProfiles() { try { return mService.getAvailableSoundProfiles(); @@ -320,7 +341,10 @@ public final class MediaQualityManager { } } - /** @SystemApi all stored sound profiles of all packages */ + /** + * @SystemApi all stored sound profiles of all packages + * @hide + */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public List<SoundProfile> getAllSoundProfiles() { try { @@ -335,6 +359,7 @@ public final class MediaQualityManager { * Creates a sound profile and store it in the system. * * @return the stored profile with an assigned profile ID. + * @hide */ public SoundProfile createSoundProfile(SoundProfile sp) { try { @@ -347,6 +372,7 @@ public final class MediaQualityManager { /** * Updates an existing sound profile and store it in the system. + * @hide */ public void updateSoundProfile(long profileId, SoundProfile sp) { try { @@ -359,6 +385,7 @@ public final class MediaQualityManager { /** * Removes a sound profile from the system. + * @hide */ public void removeSoundProfile(long profileId) { try { @@ -370,6 +397,7 @@ public final class MediaQualityManager { /** * Gets capability information of the given parameters. + * @hide */ public List<ParamCapability> getParamCapabilities(List<String> names) { try { @@ -396,6 +424,7 @@ public final class MediaQualityManager { * different use cases. * * @param enabled {@code true} to enable, {@code false} to disable. + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setAutoPictureQualityEnabled(boolean enabled) { @@ -408,6 +437,7 @@ public final class MediaQualityManager { /** * Returns {@code true} if auto picture quality is enabled; {@code false} otherwise. + * @hide */ public boolean isAutoPictureQualityEnabled() { try { @@ -422,6 +452,7 @@ public final class MediaQualityManager { * <p>Super resolution is a feature to improve resolution. * * @param enabled {@code true} to enable, {@code false} to disable. + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setSuperResolutionEnabled(boolean enabled) { @@ -434,6 +465,7 @@ public final class MediaQualityManager { /** * Returns {@code true} if super resolution is enabled; {@code false} otherwise. + * @hide */ public boolean isSuperResolutionEnabled() { try { @@ -449,6 +481,7 @@ public final class MediaQualityManager { * different use cases. * * @param enabled {@code true} to enable, {@code false} to disable. + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public void setAutoSoundQualityEnabled(boolean enabled) { @@ -461,6 +494,7 @@ public final class MediaQualityManager { /** * Returns {@code true} if auto sound quality is enabled; {@code false} otherwise. + * @hide */ public boolean isAutoSoundQualityEnabled() { try { @@ -507,6 +541,7 @@ public final class MediaQualityManager { * Set the ambient backlight settings. * * @param settings The settings to use for the backlight detector. + * @hide */ public void setAmbientBacklightSettings( @NonNull AmbientBacklightSettings settings) { @@ -522,6 +557,7 @@ public final class MediaQualityManager { * Enables or disables the ambient backlight detection. * * @param enabled {@code true} to enable, {@code false} to disable. + * @hide */ public void setAmbientBacklightEnabled(boolean enabled) { try { @@ -700,6 +736,7 @@ public final class MediaQualityManager { public abstract static class AmbientBacklightCallback { /** * Called when new ambient backlight event is emitted. + * @hide */ public void onAmbientBacklightEvent(AmbientBacklightEvent event) { } diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java index 36c49d80e13d..8fb57124d33e 100644 --- a/media/java/android/media/quality/PictureProfile.java +++ b/media/java/android/media/quality/PictureProfile.java @@ -17,6 +17,8 @@ package android.media.quality; import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.media.tv.TvInputInfo; import android.media.tv.flags.Flags; import android.os.Bundle; import android.os.Parcel; @@ -24,30 +26,55 @@ import android.os.Parcelable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** + * Profile for picture quality. * @hide */ - @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) -public class PictureProfile implements Parcelable { +public final class PictureProfile implements Parcelable { @Nullable - private Long mId; + private String mId; + private final int mType; @NonNull private final String mName; @Nullable private final String mInputId; - @Nullable + @NonNull private final String mPackageName; @NonNull private final Bundle mParams; - protected PictureProfile(Parcel in) { - if (in.readByte() == 0) { - mId = null; - } else { - mId = in.readLong(); - } + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "TYPE_", value = { + TYPE_SYSTEM, + TYPE_APPLICATION}) + public @interface ProfileType {} + + /** + * System profile type. + * + * <p>A profile of system type is managed by the system, and readable to the package define in + * {@link #getPackageName()}. + */ + public static final int TYPE_SYSTEM = 1; + /** + * Application profile type. + * + * <p>A profile of application type is managed by the package define in + * {@link #getPackageName()}. + */ + public static final int TYPE_APPLICATION = 2; + + + private PictureProfile(@NonNull Parcel in) { + mId = in.readString(); + mType = in.readInt(); mName = in.readString(); mInputId = in.readString(); mPackageName = in.readString(); @@ -55,13 +82,9 @@ public class PictureProfile implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { - if (mId == null) { - dest.writeByte((byte) 0); - } else { - dest.writeByte((byte) 1); - dest.writeLong(mId); - } + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mId); + dest.writeInt(mType); dest.writeString(mName); dest.writeString(mInputId); dest.writeString(mPackageName); @@ -73,6 +96,7 @@ public class PictureProfile implements Parcelable { return 0; } + @NonNull public static final Creator<PictureProfile> CREATOR = new Creator<PictureProfile>() { @Override public PictureProfile createFromParcel(Parcel in) { @@ -92,95 +116,166 @@ public class PictureProfile implements Parcelable { * @hide */ public PictureProfile( - @Nullable Long id, + @Nullable String id, + int type, @NonNull String name, @Nullable String inputId, - @Nullable String packageName, + @NonNull String packageName, @NonNull Bundle params) { this.mId = id; + this.mType = type; this.mName = name; - com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name); this.mInputId = inputId; this.mPackageName = packageName; this.mParams = params; } + /** + * Gets profile ID. + * + * <p>A profile ID is a globally unique ID generated and assigned by the system. For profile + * objects retrieved from system (e.g {@link MediaQualityManager#getAvailablePictureProfiles()}) + * this profile ID is non-null; For profiles built locally with {@link Builder}, it's + * {@code null}. + * + * @return the unique profile ID; {@code null} if the profile is built locally with + * {@link Builder}. + */ @Nullable - public Long getProfileId() { + public String getProfileId() { return mId; } + /** + * Only used by system to assign the ID. + * @hide + */ + public void setProfileId(String id) { + mId = id; + } + + /** + * Gets profile type. + */ + @ProfileType + public int getProfileType() { + return mType; + } + + /** + * Gets the profile name. + */ @NonNull public String getName() { return mName; } + /** + * Gets the input ID if the profile is for a TV input. + * + * @return the corresponding TV input ID; {@code null} if the profile is not associated with a + * TV input. + * + * @see TvInputInfo#getId() + */ @Nullable public String getInputId() { return mInputId; } + /** + * Gets the package name of this profile. + * + * <p>The package name defines the user of a profile. Only this specific package and system app + * can access to this profile. + * + * @return the package name; {@code null} if the profile is built locally using + * {@link Builder} and the package is not set. + */ @Nullable public String getPackageName() { return mPackageName; } + + /** + * Gets the parameters of this profile. + * + * <p>The keys of commonly used parameters can be found in + * {@link MediaQualityContract.PictureQuality}. + */ @NonNull public Bundle getParameters() { return new Bundle(mParams); } /** - * A builder for {@link PictureProfile} + * A builder for {@link PictureProfile}. + * @hide */ - public static class Builder { + public static final class Builder { @Nullable - private Long mId; + private String mId; + private int mType = TYPE_APPLICATION; @NonNull private String mName; @Nullable private String mInputId; - @Nullable + @NonNull private String mPackageName; @NonNull private Bundle mParams; /** * Creates a new Builder. - * - * @hide */ public Builder(@NonNull String name) { mName = name; - com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name); } /** - * Copy constructor. - * - * @hide + * Copy constructor of builder. */ public Builder(@NonNull PictureProfile p) { mId = null; // ID needs to be reset + mType = p.getProfileType(); mName = p.getName(); mPackageName = p.getPackageName(); mInputId = p.getInputId(); + mParams = p.getParameters(); } /* @hide using by MediaQualityService */ /** - * Sets profile ID. - * @hide using by MediaQualityService + * Only used by system to assign the ID. + * @hide */ @NonNull - public Builder setProfileId(@Nullable Long id) { + public Builder setProfileId(@Nullable String id) { mId = id; return this; } /** + * Sets profile type. + * + * @hide @SystemApi + */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) + @NonNull + public Builder setProfileType(@ProfileType int value) { + mType = value; + return this; + } + + /** * Sets input ID. + * + * @see PictureProfile#getInputId() + * + * @hide @SystemApi */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) @NonNull public Builder setInputId(@NonNull String value) { mInputId = value; @@ -189,7 +284,12 @@ public class PictureProfile implements Parcelable { /** * Sets package name of the profile. + * + * @see PictureProfile#getPackageName() + * + * @hide @SystemApi */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) @NonNull public Builder setPackageName(@NonNull String value) { mPackageName = value; @@ -198,6 +298,8 @@ public class PictureProfile implements Parcelable { /** * Sets profile parameters. + * + * @see PictureProfile#getParameters() */ @NonNull public Builder setParameters(@NonNull Bundle params) { @@ -213,6 +315,7 @@ public class PictureProfile implements Parcelable { PictureProfile o = new PictureProfile( mId, + mType, mName, mInputId, mPackageName, diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java index 0e98488c93c0..c514f6ed04ef 100644 --- a/media/java/android/media/tv/TvInputServiceExtensionManager.java +++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java @@ -17,13 +17,17 @@ package android.media.tv; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringDef; import android.media.tv.flags.Flags; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -33,10 +37,14 @@ import java.util.Set; /** + * This class provides a list of available standardized TvInputService extension interface names + * and a container storing IBinder objects that implement these interfaces created by SoC/OEMs. + * It also provides an API for SoC/OEMs to register implemented IBinder objects. + * * @hide */ @FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION) -public class TvInputServiceExtensionManager { +public final class TvInputServiceExtensionManager { private static final String TAG = "TvInputServiceExtensionManager"; private static final String SCAN_PACKAGE = "android.media.tv.extension.scan."; private static final String OAD_PACKAGE = "android.media.tv.extension.oad."; @@ -54,404 +62,608 @@ public class TvInputServiceExtensionManager { private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog."; private static final String TUNE_PACKAGE = "android.media.tv.extension.tune."; - /** Register binder returns success when it abides standardized interface structure */ + /** @hide */ + @IntDef(prefix = {"REGISTER_"}, value = { + REGISTER_SUCCESS, + REGISTER_FAIL_NAME_NOT_STANDARDIZED, + REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED, + REGISTER_FAIL_REMOTE_EXCEPTION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RegisterResult {} + + /** + * Registering binder returns success when it abides standardized interface structure + */ public static final int REGISTER_SUCCESS = 0; - /** Register binder returns fail when the extension name is not in the standardization list */ + /** + * Registering binder returns failure when the extension name is not in the standardization + * list + */ public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1; - /** Register binder returns fail when the IBinder does not implement standardized interface */ + /** + * Registering binder returns failure when the IBinder does not implement standardized interface + */ public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2; - /** Register binder returns fail when remote server not available */ + /** + * Registering binder returns failure when remote server is not available + */ public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3; + /** @hide */ + @StringDef({ + ISCAN_INTERFACE, + ISCAN_SESSION, + ISCAN_LISTENER, + IHDPLUS_INFO, + IOPERATOR_DETECTION, + IOPERATOR_DETECTION_LISTENER, + IREGION_CHANNEL_LIST, + IREGION_CHANNEL_LIST_LISTENER, + ITARGET_REGION, + ITARGET_REGION_LISTENER, + ILCN_CONFLICT, + ILCN_CONFLICT_LISTENER, + ILCNV2_CHANNEL_LIST, + ILCNV2_CHANNEL_LIST_LISTENER, + IFAVORITE_NETWORK, + IFAVORITE_NETWORK_LISTENER, + ITKGS_INFO, + ITKGS_INFO_LISTENER, + ISCAN_SAT_SEARCH, + IOAD_UPDATE_INTERFACE, + ICAM_APP_INFO_SERVICE, + ICAM_APP_INFO_LISTENER, + ICAM_MONITORING_SERVICE, + ICAM_INFO_LISTENER, + ICI_OPERATOR_INTERFACE, + ICI_OPERATOR_LISTENER, + ICAM_PROFILE_INTERFACE, + ICONTENT_CONTROL_SERVICE, + ICAM_DRM_INFO_LISTENER, + ICAM_PIN_SERVICE, + ICAM_PIN_CAPABILITY_LISTENER, + ICAM_PIN_STATUS_LISTENER, + ICAM_HOST_CONTROL_SERVICE, + ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK, + ICAM_HOST_CONTROL_INFO_LISTENER, + ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG, + ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER, + IMMI_INTERFACE, + IMMI_SESSION, + IMMI_STATUS_CALLBACK, + IENTER_MENU_ERROR_CALLBACK, + IDOWNLOADABLE_RATING_TABLE_MONITOR, + IRATING_INTERFACE, + IPMT_RATING_INTERFACE, + IPMT_RATING_LISTENER, + IVBI_RATING_INTERFACE, + IVBI_RATING_LISTENER, + IPROGRAM_INFO, + IPROGRAM_INFO_LISTENER, + IBROADCAST_TIME, + IDATA_SERVICE_SIGNAL_INFO, + IDATA_SERVICE_SIGNAL_INFO_LISTENER, + ITELETEXT_PAGE_SUB_CODE, + ISCAN_BACKGROUND_SERVICE_UPDATE, + ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER, + ICLIENT_TOKEN, + ISCREEN_MODE_SETTINGS, + IHDMI_SIGNAL_INTERFACE, + IHDMI_SIGNAL_INFO_LISTENER, + IAUDIO_SIGNAL_INFO, + IANALOG_AUDIO_INFO, + IAUDIO_SIGNAL_INFO_LISTENER, + IVIDEO_SIGNAL_INFO, + IVIDEO_SIGNAL_INFO_LISTENER, + ISERVICE_LIST_EDIT, + ISERVICE_LIST_EDIT_LISTENER, + ISERVICE_LIST, + ISERVICE_LIST_TRANSFER_INTERFACE, + ISERVICE_LIST_EXPORT_SESSION, + ISERVICE_LIST_EXPORT_LISTENER, + ISERVICE_LIST_IMPORT_SESSION, + ISERVICE_LIST_IMPORT_LISTENER, + ISERVICE_LIST_SET_CHANNEL_LIST_SESSION, + ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER, + ICHANNEL_LIST_TRANSFER, + IRECORDED_CONTENTS, + IDELETE_RECORDED_CONTENTS_CALLBACK, + IGET_INFO_RECORDED_CONTENTS_CALLBACK, + IEVENT_MONITOR, + IEVENT_MONITOR_LISTENER, + IEVENT_DOWNLOAD, + IEVENT_DOWNLOAD_LISTENER, + IEVENT_DOWNLOAD_SESSION, + IANALOG_ATTRIBUTE_INTERFACE, + ICHANNEL_TUNED_INTERFACE, + ICHANNEL_TUNED_LISTENER, + ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE, + ITUNER_FRONTEND_SIGNAL_INFO_LISTENER, + IMUX_TUNE_SESSION, + IMUX_TUNE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StandardizedExtensionName {} /** * Interface responsible for creating scan session and obtain parameters. + * @hide */ public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface"; /** * Interface that handles scan session and get/store related information. + * @hide */ public static final String ISCAN_SESSION = SCAN_PACKAGE + "IScanSession"; /** * Interface that notifies changes related to scan session. + * @hide */ public static final String ISCAN_LISTENER = SCAN_PACKAGE + "IScanListener"; /** * Interface for setting HDPlus information. + * @hide */ public static final String IHDPLUS_INFO = SCAN_PACKAGE + "IHDPlusInfo"; /** * Interface for handling operator detection for scanning. + * @hide */ public static final String IOPERATOR_DETECTION = SCAN_PACKAGE + "IOperatorDetection"; /** * Interface for changes related to operator detection searches. + * @hide */ public static final String IOPERATOR_DETECTION_LISTENER = SCAN_PACKAGE + "IOperatorDetectionListener"; /** * Interface for handling region channel list for scanning. + * @hide */ public static final String IREGION_CHANNEL_LIST = SCAN_PACKAGE + "IRegionChannelList"; /** * Interface for changes related to changes in region channel list search. + * @hide */ public static final String IREGION_CHANNEL_LIST_LISTENER = SCAN_PACKAGE + "IRegionChannelListListener"; /** * Interface for handling target region information. + * @hide */ public static final String ITARGET_REGION = SCAN_PACKAGE + "ITargetRegion"; /** * Interface for changes related to target regions during scanning. + * @hide */ public static final String ITARGET_REGION_LISTENER = SCAN_PACKAGE + "ITargetRegionListener"; /** * Interface for handling LCN conflict groups. + * @hide */ public static final String ILCN_CONFLICT = SCAN_PACKAGE + "ILcnConflict"; /** * Interface for detecting LCN conflicts during scanning. + * @hide */ public static final String ILCN_CONFLICT_LISTENER = SCAN_PACKAGE + "ILcnConflictListener"; /** * Interface for handling LCN V2 channel list information. + * @hide */ public static final String ILCNV2_CHANNEL_LIST = SCAN_PACKAGE + "ILcnV2ChannelList"; /** * Interface for detecting LCN V2 channel list during scanning. + * @hide */ public static final String ILCNV2_CHANNEL_LIST_LISTENER = SCAN_PACKAGE + "ILcnV2ChannelListListener"; /** * Interface for handling favorite network related information. + * @hide */ public static final String IFAVORITE_NETWORK = SCAN_PACKAGE + "IFavoriteNetwork"; /** * Interface for detecting favorite network during scanning. + * @hide */ public static final String IFAVORITE_NETWORK_LISTENER = SCAN_PACKAGE + "IFavoriteNetworkListener"; /** * Interface for handling Turksat channel update system service. + * @hide */ public static final String ITKGS_INFO = SCAN_PACKAGE + "ITkgsInfo"; /** * Interface for changes related to TKGS information. + * @hide */ public static final String ITKGS_INFO_LISTENER = SCAN_PACKAGE + "ITkgsInfoListener"; /** * Interface for satellite search related to low noise block downconverter. + * @hide */ public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch"; /** * Interface for Over-the-Air Download. + * @hide */ public static final String IOAD_UPDATE_INTERFACE = OAD_PACKAGE + "IOadUpdateInterface"; /** * Interface for handling conditional access module app related information. + * @hide */ public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService"; /** * Interface for changes on conditional access module app related information. + * @hide */ public static final String ICAM_APP_INFO_LISTENER = CAM_PACKAGE + "ICamAppInfoListener"; /** * Interface for handling conditional access module related information. + * @hide */ public static final String ICAM_MONITORING_SERVICE = CAM_PACKAGE + "ICamMonitoringService"; /** * Interface for changes on conditional access module related information. + * @hide */ public static final String ICAM_INFO_LISTENER = CAM_PACKAGE + "ICamInfoListener"; /** * Interface for handling control of CI+ operations. + * @hide */ public static final String ICI_OPERATOR_INTERFACE = CAM_PACKAGE + "ICiOperatorInterface"; /** * Interfaces for changes on CI+ operations. + * @hide */ public static final String ICI_OPERATOR_LISTENER = CAM_PACKAGE + "ICiOperatorListener"; /** * Interface for handling conditional access module profile related information. + * @hide */ public static final String ICAM_PROFILE_INTERFACE = CAM_PACKAGE + "ICamProfileInterface"; /** * Interface for handling conditional access module DRM related information. + * @hide */ public static final String ICONTENT_CONTROL_SERVICE = CAM_PACKAGE + "IContentControlService"; /** * Interface for changes on DRM. + * @hide */ public static final String ICAM_DRM_INFO_LISTENER = CAM_PACKAGE + "ICamDrmInfoListener"; /** * Interface for handling conditional access module pin related information. + * @hide */ public static final String ICAM_PIN_SERVICE = CAM_PACKAGE + "ICamPinService"; /** * Interface for changes on conditional access module pin capability. + * @hide */ public static final String ICAM_PIN_CAPABILITY_LISTENER = CAM_PACKAGE + "ICamPinCapabilityListener"; /** * Interface for changes on conditional access module pin status. + * @hide */ public static final String ICAM_PIN_STATUS_LISTENER = CAM_PACKAGE + "ICamPinStatusListener"; /** * Interface for handling conditional access module host control service. + * @hide */ public static final String ICAM_HOST_CONTROL_SERVICE = CAM_PACKAGE + "ICamHostControlService"; /** * Interface for handling conditional access module ask release reply. + * @hide */ public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = CAM_PACKAGE + "ICamHostControlAskReleaseReplyCallback"; /** * Interface for changes on conditional access module host control service. + * @hide */ public static final String ICAM_HOST_CONTROL_INFO_LISTENER = CAM_PACKAGE + "ICamHostControlInfoListener"; /** * Interface for handling conditional access module host control service tune_quietly_flag. + * @hide */ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG = CAM_PACKAGE + "ICamHostControlTuneQuietlyFlag"; /** * Interface for changes on conditional access module host control service tune_quietly_flag. + * @hide */ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = CAM_PACKAGE + "ICamHostControlTuneQuietlyFlagListener"; /** * Interface for handling conditional access module multi media interface. + * @hide */ public static final String IMMI_INTERFACE = CAM_PACKAGE + "IMmiInterface"; /** * Interface for controlling conditional access module multi media session. + * @hide */ public static final String IMMI_SESSION = CAM_PACKAGE + "IMmiSession"; /** * Interface for changes on conditional access module multi media session status. + * @hide */ public static final String IMMI_STATUS_CALLBACK = CAM_PACKAGE + "IMmiStatusCallback"; /** * Interface for changes on conditional access app info related to entering menu. + * @hide */ public static final String IENTER_MENU_ERROR_CALLBACK = CAM_PACKAGE + "IEnterMenuErrorCallback"; /** * Interface for handling RRT downloadable rating data. + * @hide */ public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE + "IDownloadableRatingTableMonitor"; /** * Interface for handling RRT rating related information. + * @hide */ public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface"; /** * Interface for handling PMT rating related information. + * @hide */ public static final String IPMT_RATING_INTERFACE = RATING_PACKAGE + "IPmtRatingInterface"; /** * Interface for changes on PMT rating related information. + * @hide */ public static final String IPMT_RATING_LISTENER = RATING_PACKAGE + "IPmtRatingListener"; /** * Interface for handling IVBI rating related information. + * @hide */ public static final String IVBI_RATING_INTERFACE = RATING_PACKAGE + "IVbiRatingInterface"; /** * Interface for changes on IVBI rating related information. + * @hide */ public static final String IVBI_RATING_LISTENER = RATING_PACKAGE + "IVbiRatingListener"; /** * Interface for handling program rating related information. + * @hide */ public static final String IPROGRAM_INFO = RATING_PACKAGE + "IProgramInfo"; /** * Interface for changes on program rating related information. + * @hide */ public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener"; /** * Interface for getting broadcast time related information. + * @hide */ - public static final String BROADCAST_TIME = TIME_PACKAGE + "BroadcastTime"; + public static final String IBROADCAST_TIME = TIME_PACKAGE + "BroadcastTime"; /** * Interface for handling data service signal information on teletext. + * @hide */ public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE + "IDataServiceSignalInfo"; /** * Interface for changes on data service signal information on teletext. + * @hide */ public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = TELETEXT_PACKAGE + "IDataServiceSignalInfoListener"; /** * Interface for handling teletext page information. + * @hide */ public static final String ITELETEXT_PAGE_SUB_CODE = TELETEXT_PACKAGE + "ITeletextPageSubCode"; /** * Interface for handling scan background service update. + * @hide */ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = SCAN_BSU_PACKAGE + "IScanBackgroundServiceUpdate"; /** * Interface for changes on background service update + * @hide */ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = SCAN_BSU_PACKAGE + "IScanBackgroundServiceUpdateListener"; /** * Interface for generating client token. + * @hide */ public static final String ICLIENT_TOKEN = CLIENT_TOKEN_PACKAGE + "IClientToken"; /** * Interfaces for handling screen mode information. + * @hide */ public static final String ISCREEN_MODE_SETTINGS = SCREEN_MODE_PACKAGE + "IScreenModeSettings"; /** * Interfaces for handling HDMI signal information update. + * @hide */ public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface"; /** * Interfaces for changes on HDMI signal information update. + * @hide */ public static final String IHDMI_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "IHdmiSignalInfoListener"; /** * Interfaces for handling audio signal information update. + * @hide */ public static final String IAUDIO_SIGNAL_INFO = SIGNAL_PACKAGE + "IAudioSignalInfo"; /** * Interfaces for handling analog audio signal information update. + * @hide */ public static final String IANALOG_AUDIO_INFO = SIGNAL_PACKAGE + "IAnalogAudioInfo"; /** * Interfaces for change on audio signal information update. + * @hide */ public static final String IAUDIO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "IAudioSignalInfoListener"; /** * Interfaces for handling video signal information update. + * @hide */ public static final String IVIDEO_SIGNAL_INFO = SIGNAL_PACKAGE + "IVideoSignalInfo"; /** * Interfaces for changes on video signal information update. + * @hide */ public static final String IVIDEO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "IVideoSignalInfoListener"; /** * Interfaces for handling service database updates. + * @hide */ public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit"; /** * Interfaces for changes on service database updates. + * @hide */ public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListEditListener"; /** * Interfaces for getting service database related information. + * @hide */ public static final String ISERVICE_LIST = SERVICE_DATABASE_PACKAGE + "IServiceList"; /** * Interfaces for transferring service database related information. + * @hide */ public static final String ISERVICE_LIST_TRANSFER_INTERFACE = SERVICE_DATABASE_PACKAGE + "IServiceListTransferInterface"; /** * Interfaces for exporting service database session. + * @hide */ public static final String ISERVICE_LIST_EXPORT_SESSION = SERVICE_DATABASE_PACKAGE + "IServiceListExportSession"; /** * Interfaces for changes on exporting service database session. + * @hide */ public static final String ISERVICE_LIST_EXPORT_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListExportListener"; /** * Interfaces for importing service database session. + * @hide */ public static final String ISERVICE_LIST_IMPORT_SESSION = SERVICE_DATABASE_PACKAGE + "IServiceListImportSession"; /** * Interfaces for changes on importing service database session. + * @hide */ public static final String ISERVICE_LIST_IMPORT_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListImportListener"; /** * Interfaces for setting channel list resources. + * @hide */ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = SERVICE_DATABASE_PACKAGE + "IServiceListSetChannelListSession"; /** * Interfaces for changes on setting channel list resources. + * @hide */ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = SERVICE_DATABASE_PACKAGE + "IServiceListSetChannelListListener"; /** * Interfaces for transferring channel list resources. + * @hide */ public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE + "IChannelListTransfer"; /** * Interfaces for record contents updates. + * @hide */ public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents"; /** * Interfaces for changes on deleting record contents. + * @hide */ public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE + "IDeleteRecordedContentsCallback"; /** * Interfaces for changes on getting record contents. + * @hide */ public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE + "IGetInfoRecordedContentsCallback"; /** * Interfaces for monitoring present event information. + * @hide */ public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor"; /** * Interfaces for changes on present event information. + * @hide */ public static final String IEVENT_MONITOR_LISTENER = EVENT_PACKAGE + "IEventMonitorListener"; /** * Interfaces for handling download event information. + * @hide */ public static final String IEVENT_DOWNLOAD = EVENT_PACKAGE + "IEventDownload"; /** * Interfaces for changes on downloading event information. + * @hide */ public static final String IEVENT_DOWNLOAD_LISTENER = EVENT_PACKAGE + "IEventDownloadListener"; /** * Interfaces for handling download event information for DVB and DTMB. + * @hide */ public static final String IEVENT_DOWNLOAD_SESSION = EVENT_PACKAGE + "IEventDownloadSession"; /** * Interfaces for handling analog color system. + * @hide */ public static final String IANALOG_ATTRIBUTE_INTERFACE = ANALOG_PACKAGE + "IAnalogAttributeInterface"; /** * Interfaces for monitoring channel tuned information. + * @hide */ public static final String ICHANNEL_TUNED_INTERFACE = TUNE_PACKAGE + "IChannelTunedInterface"; /** * Interfaces for changes on channel tuned information. + * @hide */ public static final String ICHANNEL_TUNED_LISTENER = TUNE_PACKAGE + "IChannelTunedListener"; /** * Interfaces for handling tuner frontend signal info. + * @hide */ public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = SIGNAL_PACKAGE + "ITunerFrontendSignalInfoInterface"; /** * Interfaces for changes on tuner frontend signal info. + * @hide */ public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE + "ITunerFrontendSignalInfoListener"; /** * Interfaces for handling mux tune operations. + * @hide */ public static final String IMUX_TUNE_SESSION = TUNE_PACKAGE + "IMuxTuneSession"; /** * Interfaces for initing mux tune session. + * @hide */ public static final String IMUX_TUNE = TUNE_PACKAGE + "IMuxTune"; @@ -506,7 +718,7 @@ public class TvInputServiceExtensionManager { IVBI_RATING_LISTENER, IPROGRAM_INFO, IPROGRAM_INFO_LISTENER, - BROADCAST_TIME, + IBROADCAST_TIME, IDATA_SERVICE_SIGNAL_INFO, IDATA_SERVICE_SIGNAL_INFO_LISTENER, ITELETEXT_PAGE_SUB_CODE, @@ -586,7 +798,8 @@ public class TvInputServiceExtensionManager { * * @hide */ - public int registerExtensionIBinder(@NonNull String extensionName, + @RegisterResult + public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName, @NonNull IBinder binder) { if (!checkIsStandardizedInterfaces(extensionName)) { return REGISTER_FAIL_NAME_NOT_STANDARDIZED; diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index 0fb3049f63d8..23dd9b7aee37 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -48,7 +48,9 @@ cc_library_shared { "libhwui_internal_headers", ], - static_libs: ["libarect"], + static_libs: [ + "libarect", + ], host_supported: true, target: { @@ -60,6 +62,11 @@ cc_library_shared { shared_libs: [ "libandroid", ], + static_libs: [ + "libstatslog_hwui", + "libstatspull_lazy", + "libstatssocket_lazy", + ], version_script: "libjnigraphics.map.txt", }, host: { diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp index e18b4a9d2420..cb95bcf5d2b1 100644 --- a/native/graphics/jni/imagedecoder.cpp +++ b/native/graphics/jni/imagedecoder.cpp @@ -14,18 +14,9 @@ * limitations under the License. */ -#include "aassetstreamadaptor.h" - -#include <android/asset_manager.h> -#include <android/bitmap.h> -#include <android/data_space.h> -#include <android/imagedecoder.h> #include <MimeType.h> -#include <android/rect.h> -#include <hwui/ImageDecoder.h> -#include <log/log.h> -#include <SkAndroidCodec.h> #include <SkAlphaType.h> +#include <SkAndroidCodec.h> #include <SkCodec.h> #include <SkCodecAnimation.h> #include <SkColorSpace.h> @@ -35,14 +26,24 @@ #include <SkRefCnt.h> #include <SkSize.h> #include <SkStream.h> -#include <utils/Color.h> - +#include <android/asset_manager.h> +#include <android/bitmap.h> +#include <android/data_space.h> +#include <android/imagedecoder.h> +#include <android/rect.h> #include <fcntl.h> -#include <limits> -#include <optional> +#include <hwui/ImageDecoder.h> +#include <log/log.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <utils/Color.h> +#include <utils/StatsUtils.h> + +#include <limits> +#include <optional> + +#include "aassetstreamadaptor.h" using namespace android; @@ -400,9 +401,7 @@ size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) { return info.minRowBytes(); } -int AImageDecoder_decodeImage(AImageDecoder* decoder, - void* pixels, size_t stride, - size_t size) { +int AImageDecoder_decodeImage(AImageDecoder* decoder, void* pixels, size_t stride, size_t size) { if (!decoder || !pixels || !stride) { return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } @@ -419,7 +418,13 @@ int AImageDecoder_decodeImage(AImageDecoder* decoder, return ANDROID_IMAGE_DECODER_FINISHED; } - return ResultToErrorCode(imageDecoder->decode(pixels, stride)); + const auto result = ResultToErrorCode(imageDecoder->decode(pixels, stride)); + + if (result == ANDROID_IMAGE_DECODER_SUCCESS) { + uirenderer::logBitmapDecode(imageDecoder->getOutputInfo(), false); + } + + return result; } void AImageDecoder_delete(AImageDecoder* decoder) { 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 f1103e19c90e..992f581a8a70 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java @@ -896,7 +896,8 @@ public class RescueParty { int systemUserId = UserHandle.SYSTEM.getIdentifier(); int[] userIds = { systemUserId }; try { - for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) { + for (File file : FileUtils.listFilesOrEmpty( + Environment.getDataSystemDeviceProtectedDirectory())) { try { final int userId = Integer.parseInt(file.getName()); if (userId != systemUserId) { 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 8277e573e7c2..311def80f248 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 @@ -499,8 +499,8 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve // Check if the package is listed among the system modules or is an // APK inside an updatable APEX. try { - final PackageInfo pkg = mContext.getPackageManager() - .getPackageInfo(packageName, 0 /* flags */); + PackageManager pm = mContext.getPackageManager(); + final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */); String apexPackageName = pkg.getApexPackageName(); if (apexPackageName != null) { packageName = apexPackageName; 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 2acedd56e505..be339cdb75ab 100644 --- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java @@ -300,6 +300,31 @@ public class PackageWatchdog { sPackageWatchdog = this; } + /** + * Creating this temp constructor to match module constructor. + * Note: To be only used in tests. + * Creates a PackageWatchdog that allows injecting dependencies, + * except for connectivity module connector. + */ + @VisibleForTesting + PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler, + Handler longTaskHandler, ExplicitHealthCheckController controller, + SystemClock clock) { + mContext = context; + mPolicyFile = policyFile; + mShortTaskHandler = shortTaskHandler; + mLongTaskHandler = longTaskHandler; + mHealthCheckController = controller; + mConnectivityModuleConnector = ConnectivityModuleConnector.getInstance(); + mSystemClock = clock; + mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS; + mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS); + + loadFromFile(); + sPackageWatchdog = this; + } + /** Creates or gets singleton instance of PackageWatchdog. */ public static @NonNull PackageWatchdog getInstance(@NonNull Context context) { synchronized (sPackageWatchdogLock) { diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt index 72cf40387a25..49acc1d44144 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt @@ -115,7 +115,7 @@ interface PreferenceScreenCreator : PreferenceScreenMetadata, PreferenceScreenPr fun isFlagEnabled(context: Context): Boolean = true val preferenceBindingFactory: PreferenceBindingFactory - get() = DefaultPreferenceBindingFactory + get() = PreferenceBindingFactory.defaultFactory override fun createPreferenceScreen(factory: PreferenceScreenFactory) = factory.getOrCreatePreferenceScreen().apply { diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt index 87c289f48ff0..51b9aac029a5 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt @@ -45,10 +45,15 @@ interface PreferenceBindingFactory { /** Returns the [PreferenceBinding] associated with the [PreferenceMetadata]. */ fun getPreferenceBinding(metadata: PreferenceMetadata): PreferenceBinding? + + companion object { + /** Default preference binding factory. */ + @JvmStatic var defaultFactory: PreferenceBindingFactory = DefaultPreferenceBindingFactory() + } } /** Default [PreferenceBindingFactory]. */ -object DefaultPreferenceBindingFactory : PreferenceBindingFactory { +open class DefaultPreferenceBindingFactory : PreferenceBindingFactory { override fun getPreferenceBinding(metadata: PreferenceMetadata) = metadata as? PreferenceBinding @@ -66,5 +71,6 @@ class KeyedPreferenceBindingFactory(private val bindings: Map<String, Preference PreferenceBindingFactory { override fun getPreferenceBinding(metadata: PreferenceMetadata) = - bindings[metadata.key] ?: DefaultPreferenceBindingFactory.getPreferenceBinding(metadata) + bindings[metadata.key] + ?: PreferenceBindingFactory.defaultFactory.getPreferenceBinding(metadata) } diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt index f3142d031aa9..00bad5203f07 100644 --- a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt +++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt @@ -25,7 +25,7 @@ import com.android.settingslib.metadata.PreferenceMetadata /** Creates [Preference] widget and binds with metadata. */ @VisibleForTesting fun <P : Preference> PreferenceMetadata.createAndBindWidget(context: Context): P { - val binding = DefaultPreferenceBindingFactory.getPreferenceBinding(this) + val binding = PreferenceBindingFactory.defaultFactory.getPreferenceBinding(this)!! return (binding.createWidget(context) as P).also { if (this is PersistentPreference<*>) { storage(context)?.let { keyValueStore -> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java index 6578eb7d50a6..c36ade979d47 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java @@ -22,14 +22,20 @@ import androidx.annotation.NonNull; import androidx.preference.DropDownPreference; import androidx.preference.PreferenceViewHolder; -public class RestrictedDropDownPreference extends DropDownPreference { - RestrictedPreferenceHelper mHelper; +public class RestrictedDropDownPreference extends DropDownPreference implements + RestrictedPreferenceHelperProvider { + private final RestrictedPreferenceHelper mHelper; public RestrictedDropDownPreference(@NonNull Context context) { super(context); mHelper = new RestrictedPreferenceHelper(context, this, null); } + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + /** * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this * package. Marks the preference as disabled if so. diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt b/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt deleted file mode 100644 index 14f9a19ee753..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt +++ /dev/null @@ -1,49 +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.settingslib - -import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin - -interface RestrictedInterface { - fun useAdminDisabledSummary(useSummary: Boolean) - - fun checkRestrictionAndSetDisabled(userRestriction: String) - - fun checkRestrictionAndSetDisabled(userRestriction: String, userId: Int) - - /** - * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this - * package. Marks the preference as disabled if so. - * - * @param settingIdentifier The key identifying the setting - * @param packageName the package to check the settingIdentifier for - */ - fun checkEcmRestrictionAndSetDisabled( - settingIdentifier: String, - packageName: String - ) - - val isDisabledByAdmin: Boolean - - fun setDisabledByAdmin(admin: EnforcedAdmin?) - - val isDisabledByEcm: Boolean - - val uid: Int - - val packageName: String? -} diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java index 495410b0a8ae..332042a5c4f9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java @@ -34,7 +34,9 @@ import com.android.settingslib.widget.TwoTargetPreference; * Preference class that supports being disabled by a user restriction * set by a device admin. */ -public class RestrictedPreference extends TwoTargetPreference { +public class RestrictedPreference extends TwoTargetPreference implements + RestrictedPreferenceHelperProvider { + RestrictedPreferenceHelper mHelper; public RestrictedPreference(Context context, AttributeSet attrs, @@ -67,6 +69,11 @@ public class RestrictedPreference extends TwoTargetPreference { } @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); mHelper.onBindViewHolder(holder); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt new file mode 100644 index 000000000000..f2860845e3d3 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib + +/** Provider of [RestrictedPreferenceHelper]. */ +interface RestrictedPreferenceHelperProvider { + + /** Returns the [RestrictedPreferenceHelper] applied to the preference. */ + fun getRestrictedPreferenceHelper(): RestrictedPreferenceHelper +} diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java index c52c7ea7328b..573869db5073 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java @@ -32,8 +32,9 @@ import com.android.settingslib.widget.SelectorWithWidgetPreference; /** * Selector with widget preference that can be disabled by a device admin using a user restriction. */ -public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPreference { - private RestrictedPreferenceHelper mHelper; +public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPreference implements + RestrictedPreferenceHelperProvider { + private final RestrictedPreferenceHelper mHelper; /** * Perform inflation from XML and apply a class-specific base style. @@ -82,8 +83,11 @@ public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPr */ public RestrictedSelectorWithWidgetPreference(@NonNull Context context) { this(context, null); - mHelper = - new RestrictedPreferenceHelper(context, /* preference= */ this, /* attrs= */ null); + } + + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java index 1dc5281c266c..b690816a6fb6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java @@ -19,7 +19,6 @@ package com.android.settingslib; import android.annotation.SuppressLint; import android.content.Context; import android.os.Process; -import android.os.UserHandle; import android.util.AttributeSet; import androidx.annotation.NonNull; @@ -34,8 +33,10 @@ import com.android.settingslib.widget.SliderPreference; * Slide Preference that supports being disabled by a user restriction * set by a device admin. */ -public class RestrictedSliderPreference extends SliderPreference implements RestrictedInterface { - RestrictedPreferenceHelper mHelper; +public class RestrictedSliderPreference extends SliderPreference implements + RestrictedPreferenceHelperProvider { + + private final RestrictedPreferenceHelper mHelper; public RestrictedSliderPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, @Nullable String packageName, int uid) { @@ -66,6 +67,11 @@ public class RestrictedSliderPreference extends SliderPreference implements Rest } @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + + @Override public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { super.onBindViewHolder(holder); mHelper.onBindViewHolder(holder); @@ -81,12 +87,12 @@ public class RestrictedSliderPreference extends SliderPreference implements Rest @Override public void setEnabled(boolean enabled) { - if (enabled && isDisabledByAdmin()) { + if (enabled && mHelper.isDisabledByAdmin()) { mHelper.setDisabledByAdmin(null); return; } - if (enabled && isDisabledByEcm()) { + if (enabled && mHelper.isDisabledByEcm()) { mHelper.setDisabledByEcm(null); return; } @@ -95,55 +101,6 @@ public class RestrictedSliderPreference extends SliderPreference implements Rest } @Override - public void useAdminDisabledSummary(boolean useSummary) { - mHelper.useAdminDisabledSummary(useSummary); - } - - @Override - public void checkRestrictionAndSetDisabled(@NonNull String userRestriction) { - mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId()); - } - - @Override - public void checkRestrictionAndSetDisabled(@NonNull String userRestriction, int userId) { - mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); - } - - @Override - public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, - @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); - } - - @Override - public void setDisabledByAdmin(@Nullable RestrictedLockUtils.EnforcedAdmin admin) { - if (mHelper.setDisabledByAdmin(admin)) { - notifyChanged(); - } - } - - @Override - public boolean isDisabledByAdmin() { - return mHelper.isDisabledByAdmin(); - } - - @Override - public boolean isDisabledByEcm() { - return mHelper.isDisabledByEcm(); - } - - @Override - public int getUid() { - return mHelper != null ? mHelper.uid : Process.INVALID_UID; - } - - @Override - @Nullable - public String getPackageName() { - return mHelper != null ? mHelper.packageName : null; - } - - @Override protected void onAttachedToHierarchy(@NonNull PreferenceManager preferenceManager) { mHelper.onAttachedToHierarchy(); super.onAttachedToHierarchy(preferenceManager); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index fffbb547c662..727dbe1019ae 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -45,8 +45,9 @@ import androidx.preference.SwitchPreferenceCompat; * Version of SwitchPreferenceCompat that can be disabled by a device admin * using a user restriction. */ -public class RestrictedSwitchPreference extends SwitchPreferenceCompat { - RestrictedPreferenceHelper mHelper; +public class RestrictedSwitchPreference extends SwitchPreferenceCompat implements + RestrictedPreferenceHelperProvider { + private final RestrictedPreferenceHelper mHelper; AppOpsManager mAppOpsManager; boolean mUseAdditionalSummary = false; CharSequence mRestrictedSwitchSummary; @@ -98,6 +99,11 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { this(context, null); } + @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + @VisibleForTesting public void setAppOps(AppOpsManager appOps) { mAppOpsManager = appOps; diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java index 0096015aa875..34e4d8f7346f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java @@ -22,14 +22,16 @@ import android.content.Context; import android.os.UserHandle; import android.util.AttributeSet; +import androidx.annotation.NonNull; import androidx.core.content.res.TypedArrayUtils; import androidx.preference.Preference; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceViewHolder; /** Top level preference that can be disabled by a device admin using a user restriction. */ -public class RestrictedTopLevelPreference extends Preference { - private RestrictedPreferenceHelper mHelper; +public class RestrictedTopLevelPreference extends Preference implements + RestrictedPreferenceHelperProvider { + private final RestrictedPreferenceHelper mHelper; public RestrictedTopLevelPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -51,6 +53,11 @@ public class RestrictedTopLevelPreference extends Preference { } @Override + public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() { + return mHelper; + } + + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); mHelper.onBindViewHolder(holder); diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java index 79dabf029c49..5d2a1669e24a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconKeys.java @@ -41,7 +41,7 @@ class ZenIconKeys { private static final ImmutableMap<Integer, ZenIcon.Key> TYPE_DEFAULTS = ImmutableMap.of( AutomaticZenRule.TYPE_UNKNOWN, - ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown), + ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_special_dnd), AutomaticZenRule.TYPE_OTHER, ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_other), AutomaticZenRule.TYPE_SCHEDULE_TIME, @@ -61,7 +61,7 @@ class ZenIconKeys { ); private static final ZenIcon.Key FOR_UNEXPECTED_TYPE = - ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_unknown); + ZenIcon.Key.forSystemResource(R.drawable.ic_zen_mode_type_special_dnd); /** Default icon descriptors per mode {@link AutomaticZenRule.Type}. */ static ZenIcon.Key forType(@AutomaticZenRule.Type int ruleType) { diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 1919572ff571..2c8c261fa8f8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -715,6 +715,9 @@ <uses-permission android:name="android.permission.UWB_PRIVILEGED" /> <uses-permission android:name="android.permission.UWB_RANGING" /> + <!-- Permission required for CTS test - CtsRangingTestCases --> + <uses-permission android:name="android.permission.RANGING" /> + <!-- Permission required for CTS test - CtsAlarmManagerTestCases --> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index f0e1b437ec51..e0117368515b 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -1013,6 +1013,7 @@ android:autoRemoveFromRecents="true" android:launchMode="singleTop" android:showForAllUsers="true" + android:turnScreenOn="true" android:exported="false"> </activity> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 13a5248e193b..3bf3e24a2ba6 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -26,6 +26,16 @@ flag { } flag { + name: "user_encrypted_source" + namespace: "systemui" + description: "Get rid of the local cache and rely on UserManager.isUserUnlocked directly to determine whether user CE storage is encrypted." + bug: "333656491" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "modes_ui_dialog_paging" namespace: "systemui" description: "Add pagination to the Modes dialog in quick settings." @@ -1578,6 +1588,13 @@ flag { } flag { + name: "show_clipboard_indication" + namespace: "systemui" + description: "Show indication text under the clipboard overlay when copied something" + bug: "361199935" +} + +flag { name: "media_projection_dialog_behind_lockscreen" namespace: "systemui" description: "Ensure MediaProjection Dialog appears behind the lockscreen" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java index 0b15d230dee2..cbe11a3f2f60 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java @@ -268,7 +268,8 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner // skip changes that we didn't wrap if (!leashMap.containsKey(change.getLeash())) continue; // Only make the update if we are closing Desktop tasks. - if (change.getTaskInfo().isFreeform() && isClosingMode(change.getMode())) { + if (change.getTaskInfo() != null && change.getTaskInfo().isFreeform() + && isClosingMode(change.getMode())) { startTransaction.setAlpha(leashMap.get(launcherChange.getLeash()), 0f); return; } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index 4cf264253bf8..fdb4871423c3 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -20,6 +20,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context +import android.graphics.PointF import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.drawable.GradientDrawable @@ -33,13 +34,13 @@ import android.view.ViewOverlay import android.view.animation.Interpolator import android.window.WindowAnimationState import com.android.app.animation.Interpolators.LINEAR -import com.android.app.animation.MathUtils.max import com.android.internal.annotations.VisibleForTesting import com.android.internal.dynamicanimation.animation.SpringAnimation import com.android.internal.dynamicanimation.animation.SpringForce import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary import java.util.concurrent.Executor import kotlin.math.abs +import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt @@ -91,6 +92,14 @@ class TransitionAnimator( ) } + /** + * Similar to [getProgress] above, bug the delay and duration are expressed as percentages + * of the animation duration (between 0f and 1f). + */ + internal fun getProgress(linearProgress: Float, delay: Float, duration: Float): Float { + return getProgressInternal(totalDuration = 1f, linearProgress, delay, duration) + } + private fun getProgressInternal( totalDuration: Float, linearProgress: Float, @@ -262,10 +271,10 @@ class TransitionAnimator( var centerY: Float, var scale: Float = 0f, - // Cached values. - var previousCenterX: Float = -1f, - var previousCenterY: Float = -1f, - var previousScale: Float = -1f, + // Update flags (used to decide whether it's time to update the transition state). + var isCenterXUpdated: Boolean = false, + var isCenterYUpdated: Boolean = false, + var isScaleUpdated: Boolean = false, // Completion flags. var isCenterXDone: Boolean = false, @@ -286,6 +295,7 @@ class TransitionAnimator( override fun setValue(state: SpringState, value: Float) { state.centerX = value + state.isCenterXUpdated = true } }, CENTER_Y { @@ -295,6 +305,7 @@ class TransitionAnimator( override fun setValue(state: SpringState, value: Float) { state.centerY = value + state.isCenterYUpdated = true } }, SCALE { @@ -304,6 +315,7 @@ class TransitionAnimator( override fun setValue(state: SpringState, value: Float) { state.scale = value + state.isScaleUpdated = true } }; @@ -444,8 +456,8 @@ class TransitionAnimator( * punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole] * is true. * - * If [useSpring] is true, a multi-spring animation will be used instead of the default - * interpolators. + * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation + * using it for the initial momentum will be used instead of the default interpolators. */ fun startAnimation( controller: Controller, @@ -453,9 +465,9 @@ class TransitionAnimator( windowBackgroundColor: Int, fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, - useSpring: Boolean = false, + startVelocity: PointF? = null, ): Animation { - if (!controller.isLaunching || useSpring) checkReturnAnimationFrameworkFlag() + if (!controller.isLaunching || startVelocity != null) checkReturnAnimationFrameworkFlag() // We add an extra layer with the same color as the dialog/app splash screen background // color, which is usually the same color of the app background. We first fade in this layer @@ -474,7 +486,7 @@ class TransitionAnimator( windowBackgroundLayer, fadeWindowBackgroundLayer, drawHole, - useSpring, + startVelocity, ) .apply { start() } } @@ -487,7 +499,7 @@ class TransitionAnimator( windowBackgroundLayer: GradientDrawable, fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, - useSpring: Boolean = false, + startVelocity: PointF? = null, ): Animation { val transitionContainer = controller.transitionContainer val transitionContainerOverlay = transitionContainer.overlay @@ -504,11 +516,12 @@ class TransitionAnimator( openingWindowSyncView != null && openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl - return if (useSpring && springTimings != null && springInterpolators != null) { + return if (startVelocity != null && springTimings != null && springInterpolators != null) { createSpringAnimation( controller, startState, endState, + startVelocity, windowBackgroundLayer, transitionContainer, transitionContainerOverlay, @@ -693,6 +706,7 @@ class TransitionAnimator( controller: Controller, startState: State, endState: State, + startVelocity: PointF, windowBackgroundLayer: GradientDrawable, transitionContainer: View, transitionContainerOverlay: ViewGroupOverlay, @@ -721,19 +735,20 @@ class TransitionAnimator( fun updateProgress(state: SpringState) { if ( - (!state.isCenterXDone && state.centerX == state.previousCenterX) || - (!state.isCenterYDone && state.centerY == state.previousCenterY) || - (!state.isScaleDone && state.scale == state.previousScale) + !(state.isCenterXUpdated || state.isCenterXDone) || + !(state.isCenterYUpdated || state.isCenterYDone) || + !(state.isScaleUpdated || state.isScaleDone) ) { // Because all three springs use the same update method, we only actually update - // when all values have changed, avoiding two redundant calls per frame. + // when all properties have received their new value (which could be unchanged from + // the previous one), avoiding two redundant calls per frame. return } - // Update the latest values for the check above. - state.previousCenterX = state.centerX - state.previousCenterY = state.centerY - state.previousScale = state.scale + // Reset the update flags. + state.isCenterXUpdated = false + state.isCenterYUpdated = false + state.isScaleUpdated = false // Current scale-based values, that will be used to find the new animation bounds. val width = @@ -829,6 +844,7 @@ class TransitionAnimator( } setStartValue(startState.centerX) + setStartVelocity(startVelocity.x) setMinValue(min(startState.centerX, endState.centerX)) setMaxValue(max(startState.centerX, endState.centerX)) @@ -850,6 +866,7 @@ class TransitionAnimator( } setStartValue(startState.centerY) + setStartVelocity(startVelocity.y) setMinValue(min(startState.centerY, endState.centerY)) setMaxValue(max(startState.centerY, endState.centerY)) @@ -1057,15 +1074,13 @@ class TransitionAnimator( interpolators = springInterpolators!! val timings = springTimings!! fadeInProgress = - getProgressInternal( - totalDuration = 1f, + getProgress( linearProgress, timings.contentBeforeFadeOutDelay, timings.contentBeforeFadeOutDuration, ) fadeOutProgress = - getProgressInternal( - totalDuration = 1f, + getProgress( linearProgress, timings.contentAfterFadeInDelay, timings.contentAfterFadeInDuration, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 6d3039855077..87e9c427d695 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -42,7 +42,6 @@ import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions import com.android.systemui.communal.shared.model.CommunalBackgroundType @@ -124,7 +123,7 @@ val sceneTransitions = transitions { } timestampRange( startMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_DELAY_MS, - endMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_END_MS + endMillis = TransitionDuration.EDIT_MODE_TO_HUB_GRID_END_MS, ) { fade(Communal.Elements.Grid) } @@ -187,14 +186,13 @@ fun CommunalContainer( ) { scene( CommunalScenes.Blank, - userActions = - mapOf(Swipe(SwipeDirection.Start, fromSource = Edge.End) to CommunalScenes.Communal) + userActions = mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal), ) { // This scene shows nothing only allowing for transitions to the communal scene. Box(modifier = Modifier.fillMaxSize()) } - val userActions = mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank) + val userActions = mapOf(Swipe.End to CommunalScenes.Blank) scene(CommunalScenes.Communal, userActions = userActions) { CommunalScene( @@ -257,13 +255,9 @@ fun SceneScope.CommunalScene( /** Default background of the hub, a single color */ @Composable -private fun BoxScope.DefaultBackground( - colors: CommunalColors, -) { +private fun BoxScope.DefaultBackground(colors: CommunalColors) { val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle() - Box( - modifier = Modifier.matchParentSize().background(Color(backgroundColor.toArgb())), - ) + Box(modifier = Modifier.matchParentSize().background(Color(backgroundColor.toArgb()))) } /** Experimental hub background, static linear gradient */ @@ -273,7 +267,7 @@ private fun BoxScope.StaticLinearGradient() { Box( Modifier.matchParentSize() .background( - Brush.linearGradient(colors = listOf(colors.primary, colors.primaryContainer)), + Brush.linearGradient(colors = listOf(colors.primary, colors.primaryContainer)) ) ) BackgroundTopScrim() @@ -288,7 +282,7 @@ private fun BoxScope.AnimatedLinearGradient() { .background(colors.primary) .animatedRadialGradientBackground( toColor = colors.primary, - fromColor = colors.primaryContainer.copy(alpha = 0.6f) + fromColor = colors.primaryContainer.copy(alpha = 0.6f), ) ) BackgroundTopScrim() @@ -324,9 +318,9 @@ fun Modifier.animatedRadialGradientBackground(toColor: Color, fromColor: Color): durationMillis = ANIMATION_DURATION_MS, easing = CubicBezierEasing(0.33f, 0f, 0.67f, 1f), ), - repeatMode = RepeatMode.Reverse + repeatMode = RepeatMode.Reverse, ), - label = "radial gradient center fraction" + label = "radial gradient center fraction", ) // Offset to place the center of the gradients offscreen. This is applied to both the @@ -337,16 +331,9 @@ fun Modifier.animatedRadialGradientBackground(toColor: Color, fromColor: Color): val gradientRadius = (size.width / 2) + offsetPx val totalHeight = size.height + 2 * offsetPx - val leftCenter = - Offset( - x = -offsetPx, - y = totalHeight * centerFraction - offsetPx, - ) + val leftCenter = Offset(x = -offsetPx, y = totalHeight * centerFraction - offsetPx) val rightCenter = - Offset( - x = offsetPx + size.width, - y = totalHeight * (1f - centerFraction) - offsetPx, - ) + Offset(x = offsetPx + size.width, y = totalHeight * (1f - centerFraction) - offsetPx) // Right gradient drawCircle( @@ -354,7 +341,7 @@ fun Modifier.animatedRadialGradientBackground(toColor: Color, fromColor: Color): Brush.radialGradient( colors = listOf(fromColor, toColor), center = rightCenter, - radius = gradientRadius + radius = gradientRadius, ), center = rightCenter, radius = gradientRadius, @@ -367,7 +354,7 @@ fun Modifier.animatedRadialGradientBackground(toColor: Color, fromColor: Color): Brush.radialGradient( colors = listOf(fromColor, toColor), center = leftCenter, - radius = gradientRadius + radius = gradientRadius, ), center = leftCenter, radius = gradientRadius, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt index 5b996704eb12..2af5ffaee7ed 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt @@ -18,23 +18,27 @@ package com.android.systemui.notifications.ui.composable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp +import androidx.compose.ui.layout.layoutId import com.android.compose.animation.scene.ContentScope +import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn +import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel import com.android.systemui.scene.session.ui.composable.SaveableSession import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.composable.Overlay -import com.android.systemui.shade.ui.composable.ExpandedShadeHeader +import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.OverlayShade +import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager @@ -53,6 +57,8 @@ constructor( private val statusBarIconController: StatusBarIconController, private val shadeSession: SaveableSession, private val stackScrollView: Lazy<NotificationScrollView>, + private val clockSection: DefaultClockSection, + private val clockInteractor: KeyguardClockInteractor, ) : Overlay { override val key = Overlays.NotificationsShade @@ -80,13 +86,28 @@ constructor( OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) { Column { - ExpandedShadeHeader( - viewModelFactory = viewModel.shadeHeaderViewModelFactory, - createTintedIconManager = tintedIconManagerFactory::create, - createBatteryMeterViewController = batteryMeterViewControllerFactory::create, - statusBarIconController = statusBarIconController, - modifier = Modifier.padding(horizontal = 16.dp), - ) + if (viewModel.showHeader) { + val burnIn = rememberBurnIn(clockInteractor) + + CollapsedShadeHeader( + viewModelFactory = viewModel.shadeHeaderViewModelFactory, + createTintedIconManager = tintedIconManagerFactory::create, + createBatteryMeterViewController = + batteryMeterViewControllerFactory::create, + statusBarIconController = statusBarIconController, + modifier = + Modifier.element(NotificationsShade.Elements.StatusBar) + .layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader), + ) + + with(clockSection) { + SmallClock( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmallClockTopChanged, + modifier = Modifier.fillMaxWidth(), + ) + } + } NotificationScrollingStack( shadeSession = shadeSession, @@ -110,3 +131,9 @@ constructor( } } } + +object NotificationsShade { + object Elements { + val StatusBar = ElementKey("NotificationsShadeStatusBar") + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index 67f412ed27ac..2cde6787f730 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -105,13 +105,17 @@ val SceneContainerTransitions = transitions { // Overlay transitions + // TODO(b/376659778): Remove this transition once nested STLs are supported. + from(Scenes.Gone, to = Overlays.NotificationsShade) { + toNotificationsShadeTransition(translateClock = true) + } to(Overlays.NotificationsShade) { toNotificationsShadeTransition() } to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() } from(Overlays.NotificationsShade, to = Overlays.QuickSettingsShade) { notificationsShadeToQuickSettingsShadeTransition() } from(Scenes.Gone, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) { - toNotificationsShadeTransition(durationScale = 0.9) + toNotificationsShadeTransition(translateClock = true, durationScale = 0.9) } from(Scenes.Gone, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) { toQuickSettingsShadeTransition(durationScale = 0.9) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt index 23c4f12cb0ae..6bdb36331709 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt @@ -21,30 +21,42 @@ import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder +import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys import com.android.systemui.notifications.ui.composable.Notifications +import com.android.systemui.notifications.ui.composable.NotificationsShade +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.shade.ui.composable.OverlayShade import com.android.systemui.shade.ui.composable.Shade -import com.android.systemui.shade.ui.composable.ShadeHeader import kotlin.time.Duration.Companion.milliseconds -fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) { +fun TransitionBuilder.toNotificationsShadeTransition( + translateClock: Boolean = false, + durationScale: Double = 1.0, +) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) swipeSpec = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold, ) + // Ensure the clock isn't clipped by the shade outline during the transition from lockscreen. + sharedElement( + ClockElementKeys.smallClockElementKey, + elevateInContent = Overlays.NotificationsShade, + ) scaleSize(OverlayShade.Elements.Panel, height = 0f) + // TODO(b/376659778): This is a temporary hack to have a shared element transition with the + // lockscreen clock. Remove once nested STLs are supported. + if (!translateClock) { + translate(ClockElementKeys.smallClockElementKey) + } + // Avoid translating the status bar with the shade panel. + translate(NotificationsShade.Elements.StatusBar) + // Slide in the shade panel from the top edge. translate(OverlayShade.Elements.Panel, Edge.Top) fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) } - - fractionRange(start = .5f) { - fade(ShadeHeader.Elements.Clock) - fade(ShadeHeader.Elements.ExpandedContent) - fade(ShadeHeader.Elements.PrivacyChip) - fade(Notifications.Elements.NotificationScrim) - } + fractionRange(start = .5f) { fade(Notifications.Elements.NotificationScrim) } } private val DefaultDuration = 300.milliseconds diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 63c5d7aed3e1..e7b66c5f0d2f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -52,6 +52,7 @@ import com.android.compose.animation.scene.content.Content import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.drawInContainer import com.android.compose.ui.util.lerp @@ -187,6 +188,7 @@ private fun Modifier.maybeElevateInContent( state.transformationSpec .transformations(key, content.key) .shared + ?.transformation ?.elevateInContent == content.key && isSharedElement(stateByContent, state) && isSharedElementEnabled(key, state) && @@ -901,7 +903,7 @@ private fun shouldPlaceElement( } val sharedTransformation = sharedElementTransformation(element.key, transition) - if (sharedTransformation?.enabled == false) { + if (sharedTransformation?.transformation?.enabled == false) { return true } @@ -954,13 +956,13 @@ private fun isSharedElementEnabled( element: ElementKey, transition: TransitionState.Transition, ): Boolean { - return sharedElementTransformation(element, transition)?.enabled ?: true + return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true } internal fun sharedElementTransformation( element: ElementKey, transition: TransitionState.Transition, -): SharedElementTransformation? { +): TransformationWithRange<SharedElementTransformation>? { val transformationSpec = transition.transformationSpec val sharedInFromContent = transformationSpec.transformations(element, transition.fromContent).shared @@ -1244,7 +1246,7 @@ private inline fun <T> computeValue( element: Element, transition: TransitionState.Transition?, contentValue: (Element.State) -> T, - transformation: (ElementTransformations) -> PropertyTransformation<T>?, + transformation: (ElementTransformations) -> TransformationWithRange<PropertyTransformation<T>>?, currentValue: () -> T, isSpecified: (T) -> Boolean, lerp: (T, T, Float) -> T, @@ -1280,7 +1282,7 @@ private inline fun <T> computeValue( checkNotNull(if (currentContent == toContent) toState else fromState) val idleValue = contentValue(overscrollState) val targetValue = - with(propertySpec) { + with(propertySpec.transformation) { layoutImpl.propertyTransformationScope.transform( currentContent, element.key, @@ -1375,7 +1377,7 @@ private inline fun <T> computeValue( val idleValue = contentValue(contentState) val isEntering = content == toContent val previewTargetValue = - with(previewTransformation) { + with(previewTransformation.transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, @@ -1386,7 +1388,7 @@ private inline fun <T> computeValue( val targetValueOrNull = transformation?.let { transformation -> - with(transformation) { + with(transformation.transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, @@ -1461,7 +1463,7 @@ private inline fun <T> computeValue( val idleValue = contentValue(contentState) val targetValue = - with(transformation) { + with(transformation.transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 21d87e173728..dbf7d7b29834 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -405,7 +405,8 @@ data object Back : UserAction() { } /** The user swiped on the container. */ -data class Swipe( +data class Swipe +private constructor( val direction: SwipeDirection, val pointerCount: Int = 1, val pointersType: PointerType? = null, @@ -418,6 +419,42 @@ data class Swipe( val Down = Swipe(SwipeDirection.Down) val Start = Swipe(SwipeDirection.Start) val End = Swipe(SwipeDirection.End) + + fun Left( + pointerCount: Int = 1, + pointersType: PointerType? = null, + fromSource: SwipeSource? = null, + ) = Swipe(SwipeDirection.Left, pointerCount, pointersType, fromSource) + + fun Up( + pointerCount: Int = 1, + pointersType: PointerType? = null, + fromSource: SwipeSource? = null, + ) = Swipe(SwipeDirection.Up, pointerCount, pointersType, fromSource) + + fun Right( + pointerCount: Int = 1, + pointersType: PointerType? = null, + fromSource: SwipeSource? = null, + ) = Swipe(SwipeDirection.Right, pointerCount, pointersType, fromSource) + + fun Down( + pointerCount: Int = 1, + pointersType: PointerType? = null, + fromSource: SwipeSource? = null, + ) = Swipe(SwipeDirection.Down, pointerCount, pointersType, fromSource) + + fun Start( + pointerCount: Int = 1, + pointersType: PointerType? = null, + fromSource: SwipeSource? = null, + ) = Swipe(SwipeDirection.Start, pointerCount, pointersType, fromSource) + + fun End( + pointerCount: Int = 1, + pointersType: PointerType? = null, + fromSource: SwipeSource? = null, + ) = Swipe(SwipeDirection.End, pointerCount, pointersType, fromSource) } override fun resolve(layoutDirection: LayoutDirection): UserAction.Resolved { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index e1e2411da080..61332b61ed1b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -764,7 +764,8 @@ internal class MutableSceneTransitionLayoutStateImpl( return@fastForEach } - state.transformationSpec.transformations.fastForEach { transformation -> + state.transformationSpec.transformations.fastForEach { transformationWithRange -> + val transformation = transformationWithRange.transformation if ( transformation is SharedElementTransformation && transformation.elevateInContent != null diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index 8866fbfbf194..b083f79aebf5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -33,10 +33,10 @@ import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade import com.android.compose.animation.scene.transformation.OverscrollTranslate import com.android.compose.animation.scene.transformation.PropertyTransformation -import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.Transformation +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.animation.scene.transformation.Translate /** The transitions configuration of a [SceneTransitionLayout]. */ @@ -233,7 +233,7 @@ interface TransformationSpec { val distance: UserActionDistance? /** The list of [Transformation] applied to elements during this transition. */ - val transformations: List<Transformation> + val transformations: List<TransformationWithRange<*>> companion object { internal val Empty = @@ -325,7 +325,7 @@ internal class TransformationSpecImpl( override val progressSpec: AnimationSpec<Float>, override val swipeSpec: SpringSpec<Float>?, override val distance: UserActionDistance?, - override val transformations: List<Transformation>, + override val transformations: List<TransformationWithRange<*>>, ) : TransformationSpec { private val cache = mutableMapOf<ElementKey, MutableMap<ContentKey, ElementTransformations>>() @@ -340,59 +340,65 @@ internal class TransformationSpecImpl( element: ElementKey, content: ContentKey, ): ElementTransformations { - var shared: SharedElementTransformation? = null - var offset: PropertyTransformation<Offset>? = null - var size: PropertyTransformation<IntSize>? = null - var drawScale: PropertyTransformation<Scale>? = null - var alpha: PropertyTransformation<Float>? = null - - fun <T> onPropertyTransformation( - root: PropertyTransformation<T>, - current: PropertyTransformation<T> = root, - ) { - when (current) { + var shared: TransformationWithRange<SharedElementTransformation>? = null + var offset: TransformationWithRange<PropertyTransformation<Offset>>? = null + var size: TransformationWithRange<PropertyTransformation<IntSize>>? = null + var drawScale: TransformationWithRange<PropertyTransformation<Scale>>? = null + var alpha: TransformationWithRange<PropertyTransformation<Float>>? = null + + transformations.fastForEach { transformationWithRange -> + val transformation = transformationWithRange.transformation + if (!transformation.matcher.matches(element, content)) { + return@fastForEach + } + + when (transformation) { + is SharedElementTransformation -> { + throwIfNotNull(shared, element, name = "shared") + shared = + transformationWithRange + as TransformationWithRange<SharedElementTransformation> + } is Translate, is OverscrollTranslate, is EdgeTranslate, is AnchoredTranslate -> { throwIfNotNull(offset, element, name = "offset") - offset = root as PropertyTransformation<Offset> + offset = + transformationWithRange + as TransformationWithRange<PropertyTransformation<Offset>> } is ScaleSize, is AnchoredSize -> { throwIfNotNull(size, element, name = "size") - size = root as PropertyTransformation<IntSize> + size = + transformationWithRange + as TransformationWithRange<PropertyTransformation<IntSize>> } is DrawScale -> { throwIfNotNull(drawScale, element, name = "drawScale") - drawScale = root as PropertyTransformation<Scale> + drawScale = + transformationWithRange + as TransformationWithRange<PropertyTransformation<Scale>> } is Fade -> { throwIfNotNull(alpha, element, name = "alpha") - alpha = root as PropertyTransformation<Float> - } - is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate) - } - } - - transformations.fastForEach { transformation -> - if (!transformation.matcher.matches(element, content)) { - return@fastForEach - } - - when (transformation) { - is SharedElementTransformation -> { - throwIfNotNull(shared, element, name = "shared") - shared = transformation + alpha = + transformationWithRange + as TransformationWithRange<PropertyTransformation<Float>> } - is PropertyTransformation<*> -> onPropertyTransformation(transformation) + else -> error("Unknown transformation: $transformation") } } return ElementTransformations(shared, offset, size, drawScale, alpha) } - private fun throwIfNotNull(previous: Transformation?, element: ElementKey, name: String) { + private fun throwIfNotNull( + previous: TransformationWithRange<*>?, + element: ElementKey, + name: String, + ) { if (previous != null) { error("$element has multiple $name transformations") } @@ -401,9 +407,9 @@ internal class TransformationSpecImpl( /** The transformations of an element during a transition. */ internal class ElementTransformations( - val shared: SharedElementTransformation?, - val offset: PropertyTransformation<Offset>?, - val size: PropertyTransformation<IntSize>?, - val drawScale: PropertyTransformation<Scale>?, - val alpha: PropertyTransformation<Float>?, + val shared: TransformationWithRange<SharedElementTransformation>?, + val offset: TransformationWithRange<PropertyTransformation<Offset>>?, + val size: TransformationWithRange<PropertyTransformation<IntSize>>?, + val drawScale: TransformationWithRange<PropertyTransformation<Scale>>?, + val alpha: TransformationWithRange<PropertyTransformation<Float>>?, ) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 269d91b02e7d..e461f9ccc295 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -34,12 +34,11 @@ import com.android.compose.animation.scene.transformation.DrawScale import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade import com.android.compose.animation.scene.transformation.OverscrollTranslate -import com.android.compose.animation.scene.transformation.PropertyTransformation -import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.TransformationRange +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.animation.scene.transformation.Translate internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { @@ -158,7 +157,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { } internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { - val transformations = mutableListOf<Transformation>() + val transformations = mutableListOf<TransformationWithRange<*>>() private var range: TransformationRange? = null protected var reversed = false override var distance: UserActionDistance? = null @@ -174,19 +173,13 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { range = null } - protected fun transformation(transformation: PropertyTransformation<*>) { - val transformation = - if (range != null) { - RangedPropertyTransformation(transformation, range!!) - } else { - transformation - } - + protected fun transformation(transformation: Transformation) { + val transformationWithRange = TransformationWithRange(transformation, range) transformations.add( if (reversed) { - transformation.reversed() + transformationWithRange.reversed() } else { - transformation + transformationWithRange } ) } @@ -264,7 +257,7 @@ internal class TransitionBuilderImpl(override val transition: TransitionState.Tr "(${transition.toContent.debugName})" } - transformations.add(SharedElementTransformation(matcher, enabled, elevateInContent)) + transformation(SharedElementTransformation(matcher, enabled, elevateInContent)) } override fun timestampRange( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt index 5936d2595465..0ddeb7c7445f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt @@ -33,7 +33,7 @@ internal class AnchoredSize( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: IntSize, + idleValue: IntSize, ): IntSize { fun anchorSizeIn(content: ContentKey): IntSize { val size = @@ -45,8 +45,8 @@ internal class AnchoredSize( ) return IntSize( - width = if (anchorWidth) size.width else value.width, - height = if (anchorHeight) size.height else value.height, + width = if (anchorWidth) size.width else idleValue.width, + height = if (anchorHeight) size.height else idleValue.height, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt index 0a59dfe515fc..47508b41633c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt @@ -31,7 +31,7 @@ internal class AnchoredTranslate( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Offset, + idleValue: Offset, ): Offset { fun throwException(content: ContentKey?): Nothing { throwMissingAnchorException( @@ -51,9 +51,9 @@ internal class AnchoredTranslate( val offset = anchorToOffset - anchorFromOffset return if (content == transition.toContent) { - Offset(value.x - offset.x, value.y - offset.y) + Offset(idleValue.x - offset.x, idleValue.y - offset.y) } else { - Offset(value.x + offset.x, value.y + offset.y) + Offset(idleValue.x + offset.x, idleValue.y + offset.y) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt index 7223dad43a2e..8488ae5178b0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt @@ -33,12 +33,11 @@ internal class DrawScale( private val scaleY: Float, private val pivot: Offset = Offset.Unspecified, ) : PropertyTransformation<Scale> { - override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Scale, + idleValue: Scale, ): Scale { return Scale(scaleX, scaleY, pivot) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt index 4ae07c541011..884aae4b8b1a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt @@ -33,37 +33,37 @@ internal class EdgeTranslate( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Offset, + idleValue: Offset, ): Offset { val sceneSize = content.targetSize() ?: error("Content ${content.debugName} does not have a target size") - val elementSize = element.targetSize(content) ?: return value + val elementSize = element.targetSize(content) ?: return idleValue return when (edge.resolve(layoutDirection)) { Edge.Resolved.Top -> if (startsOutsideLayoutBounds) { - Offset(value.x, -elementSize.height.toFloat()) + Offset(idleValue.x, -elementSize.height.toFloat()) } else { - Offset(value.x, 0f) + Offset(idleValue.x, 0f) } Edge.Resolved.Left -> if (startsOutsideLayoutBounds) { - Offset(-elementSize.width.toFloat(), value.y) + Offset(-elementSize.width.toFloat(), idleValue.y) } else { - Offset(0f, value.y) + Offset(0f, idleValue.y) } Edge.Resolved.Bottom -> if (startsOutsideLayoutBounds) { - Offset(value.x, sceneSize.height.toFloat()) + Offset(idleValue.x, sceneSize.height.toFloat()) } else { - Offset(value.x, (sceneSize.height - elementSize.height).toFloat()) + Offset(idleValue.x, (sceneSize.height - elementSize.height).toFloat()) } Edge.Resolved.Right -> if (startsOutsideLayoutBounds) { - Offset(sceneSize.width.toFloat(), value.y) + Offset(sceneSize.width.toFloat(), idleValue.y) } else { - Offset((sceneSize.width - elementSize.width).toFloat(), value.y) + Offset((sceneSize.width - elementSize.width).toFloat(), idleValue.y) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt index c11ec977fe2b..ef769e7d0c19 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt @@ -27,7 +27,7 @@ internal class Fade(override val matcher: ElementMatcher) : PropertyTransformati content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Float, + idleValue: Float, ): Float { // Return the alpha value of [element] either when it starts fading in or when it finished // fading out, which is `0` in both cases. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt index a159a5b5b2bd..ef3654b65b0a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt @@ -36,11 +36,11 @@ internal class ScaleSize( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: IntSize, + idleValue: IntSize, ): IntSize { return IntSize( - width = (value.width * width).roundToInt(), - height = (value.height * height).roundToInt(), + width = (idleValue.width * width).roundToInt(), + height = (idleValue.height * height).roundToInt(), ) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index d38067d9af38..74a3ead3fbd7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -36,14 +36,6 @@ sealed interface Transformation { */ val matcher: ElementMatcher - /** - * The range during which the transformation is applied. If it is `null`, then the - * transformation will be applied throughout the whole scene transition. - */ - // TODO(b/240432457): Move this back to PropertyTransformation. - val range: TransformationRange? - get() = null - /* * Reverse this transformation. This is called when we use Transition(from = A, to = B) when * animating from B to A and there is no Transition(from = B, to = A) defined. @@ -66,14 +58,14 @@ interface PropertyTransformation<T> : Transformation { * - the value at progress = 100% for elements that are leaving the layout (i.e. elements in the * content we are transitioning from). * - * The returned value will be interpolated using the [transition] progress and [value], the + * The returned value will be interpolated using the [transition] progress and [idleValue], the * value of the property when we are idle. */ fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: T, + idleValue: T, ): T } @@ -82,20 +74,15 @@ interface PropertyTransformationScope : Density, ElementStateScope { val layoutDirection: LayoutDirection } -/** - * A [PropertyTransformation] associated to a range. This is a helper class so that normal - * implementations of [PropertyTransformation] don't have to take care of reversing their range when - * they are reversed. - */ -internal class RangedPropertyTransformation<T>( - val delegate: PropertyTransformation<T>, - override val range: TransformationRange, -) : PropertyTransformation<T> by delegate { - override fun reversed(): Transformation { - return RangedPropertyTransformation( - delegate.reversed() as PropertyTransformation<T>, - range.reversed(), - ) +/** A pair consisting of a [transformation] and optional [range]. */ +class TransformationWithRange<out T : Transformation>( + val transformation: T, + val range: TransformationRange?, +) { + fun reversed(): TransformationWithRange<T> { + if (range == null) return this + + return TransformationWithRange(transformation = transformation, range = range.reversed()) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt index af0a6edfa2fb..356ed9969458 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt @@ -35,9 +35,9 @@ internal class Translate( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Offset, + idleValue: Offset, ): Offset { - return Offset(value.x + x.toPx(), value.y + y.toPx()) + return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx()) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt index e924ebfd2a8d..20a0b390a037 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt @@ -207,6 +207,9 @@ class PriorityNestedScrollConnection( } override suspend fun onPreFling(available: Velocity): Velocity { + // Note: This method may be called multiple times. Due to NestedScrollDispatcher, the order + // of method calls (pre/post scroll/fling) cannot be guaranteed. + if (isStopping) return Velocity.Zero val controller = currentController ?: return Velocity.Zero // If in priority mode and can stop on pre-fling phase, stop the scroll. @@ -219,6 +222,9 @@ class PriorityNestedScrollConnection( } override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + // Note: This method may be called multiple times. Due to NestedScrollDispatcher, the order + // of method calls (pre/post scroll/fling) cannot be guaranteed. + if (isStopping) return Velocity.Zero val availableFloat = available.toFloat() val controller = currentController @@ -315,6 +321,7 @@ class PriorityNestedScrollConnection( * @return The consumed velocity. */ suspend fun stop(velocity: Float): Velocity { + if (isStopping) return Velocity.Zero val controller = requireController(isStopping = false) return coroutineScope { try { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index 5dad0d75cfc5..098673e5d186 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -101,10 +101,7 @@ class DraggableHandlerTest { scene( key = SceneC, userActions = - mapOf( - Swipe.Up to SceneB, - Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA, - ), + mapOf(Swipe.Up to SceneB, Swipe.Up(fromSource = Edge.Bottom) to SceneA), ) { Text("SceneC") } @@ -1231,7 +1228,7 @@ class DraggableHandlerTest { assertTransition( currentScene = SceneC, fromScene = SceneC, - // userAction: Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA + // userAction: Swipe.Up(fromSource = Edge.Bottom) to SceneA toScene = SceneA, progress = 0.1f, ) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index ee807e6a7ede..4a9051598ddc 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -869,10 +869,7 @@ class ElementTest { state = state, modifier = Modifier.size(layoutWidth, layoutHeight), ) { - scene( - SceneA, - userActions = mapOf(Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB), - ) { + scene(SceneA, userActions = mapOf(Swipe.Down(pointerCount = 2) to SceneB)) { Box( Modifier // A scrollable that does not consume the scroll gesture diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index aaeaba93304d..3b2ee98a2a93 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -128,10 +128,10 @@ class SwipeToSceneTest { if (swipesEnabled()) mapOf( Swipe.Down to SceneA, - Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB, - Swipe(SwipeDirection.Down, pointersType = PointerType.Mouse) to SceneD, - Swipe(SwipeDirection.Right, fromSource = Edge.Left) to SceneB, - Swipe(SwipeDirection.Down, fromSource = Edge.Top) to SceneB, + Swipe.Down(pointerCount = 2) to SceneB, + Swipe.Down(pointersType = PointerType.Mouse) to SceneD, + Swipe.Down(fromSource = Edge.Top) to SceneB, + Swipe.Right(fromSource = Edge.Left) to SceneB, ) else emptyMap(), ) { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index d66d6b3ab219..d31711496ff0 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt @@ -28,8 +28,8 @@ import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.OverscrollTranslate -import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.TransformationRange +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.test.transition import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat @@ -310,7 +310,8 @@ class TransitionDslTest { } val overscrollSpec = transitions.overscrollSpecs.single() - val transformation = overscrollSpec.transformationSpec.transformations.single() + val transformation = + overscrollSpec.transformationSpec.transformations.single().transformation assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java) } @@ -344,7 +345,7 @@ class TransitionDslTest { companion object { private val TRANSFORMATION_RANGE = - Correspondence.transforming<Transformation, TransformationRange?>( + Correspondence.transforming<TransformationWithRange<*>, TransformationRange?>( { it?.range }, "has range equal to", ) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt index 54428404bd0c..91079b89a56c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt @@ -29,6 +29,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.test.runMonotonicClockTest import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -262,4 +264,16 @@ class PriorityNestedScrollConnectionTest { scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero) assertThat(isStarted).isEqualTo(true) } + + @Test + fun handleMultipleOnPreFlingCalls() = runTest { + startPriorityModePostScroll() + + coroutineScope { + launch { scrollConnection.onPreFling(available = Velocity.Zero) } + launch { scrollConnection.onPreFling(available = Velocity.Zero) } + } + + assertThat(lastStop).isEqualTo(0f) + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt index 0b55a6e3ffa1..d86c0d664590 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt @@ -39,7 +39,7 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me val digitLeftTopMap = mutableMapOf<Int, Point>() var maxSingleDigitSize = Point(-1, -1) val lockscreenTranslate = Point(0, 0) - val aodTranslate = Point(0, 0) + var aodTranslate = Point(0, 0) init { setWillNotDraw(false) @@ -64,8 +64,7 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me maxSingleDigitSize.y = max(maxSingleDigitSize.y, textView.measuredHeight) } val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!! - aodTranslate.x = -(maxSingleDigitSize.x * AOD_HORIZONTAL_TRANSLATE_RATIO).toInt() - aodTranslate.y = (maxSingleDigitSize.y * AOD_VERTICAL_TRANSLATE_RATIO).toInt() + aodTranslate = Point(0, 0) return Point( ((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2), ((maxSingleDigitSize.y + abs(aodTranslate.y)) * 2), @@ -162,9 +161,6 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me val AOD_TRANSITION_DURATION = 750L val CHARGING_TRANSITION_DURATION = 300L - val AOD_HORIZONTAL_TRANSLATE_RATIO = 0.15F - val AOD_VERTICAL_TRANSLATE_RATIO = 0.075F - // Use the sign of targetTranslation to control the direction of digit translation fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point { val outPoint = Point(targetTranslation) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index c0899e3006a6..5c84f2d04ccc 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -148,7 +148,11 @@ open class SimpleDigitalClockTextView( lsFontVariation = ClockFontAxisSetting.toFVar(axes + OPTICAL_SIZE_AXIS) lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation) typeface = lockScreenPaint.typeface - textAnimator.setTextStyle(fvar = lsFontVariation, animate = true) + + lockScreenPaint.getTextBounds(text, 0, text.length, textBounds) + targetTextBounds.set(textBounds) + + textAnimator.setTextStyle(fvar = lsFontVariation, animate = false) measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) recomputeMaxSingleDigitSizes() requestLayout() @@ -201,7 +205,7 @@ open class SimpleDigitalClockTextView( } else { textBounds.height() + 2 * lockScreenPaint.strokeWidth.toInt() }, - MeasureSpec.getMode(measuredHeight), + MeasureSpec.getMode(measuredHeightAndState), ) } @@ -215,10 +219,10 @@ open class SimpleDigitalClockTextView( } else { max( textBounds.width() + 2 * lockScreenPaint.strokeWidth.toInt(), - MeasureSpec.getSize(measuredWidth), + MeasureSpec.getSize(measuredWidthAndState), ) }, - MeasureSpec.getMode(measuredWidth), + MeasureSpec.getMode(measuredWidthAndState), ) } diff --git a/packages/SystemUI/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md index ade5171d6415..c18d1f143b85 100644 --- a/packages/SystemUI/docs/demo_mode.md +++ b/packages/SystemUI/docs/demo_mode.md @@ -35,6 +35,7 @@ Commands are sent as string extras with key ```command``` (required). Possible v | | ```fully``` | | Sets MCS state to fully connected (```true```, ```false```) | | ```wifi``` | | ```show``` to show icon, any other value to hide | | | ```level``` | Sets wifi level (null or 0-4) +| | | ```hotspot``` | Sets the wifi to be from an Instant Hotspot. Values: ```none```, ```unknown```, ```phone```, ```tablet```, ```laptop```, ```watch```, ```auto```. (See `DemoModeWifiDataSource.kt`.) | | ```mobile``` | | ```show``` to show icon, any other value to hide | | | ```datatype``` | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide | | | ```level``` | Sets mobile signal strength level (null or 0-4) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index c74d340ee325..b087ecf1a488 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -19,7 +19,6 @@ package com.android.systemui.accessibility; import static com.android.systemui.accessibility.MagnificationImpl.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -190,8 +189,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void showMagnificationButton_delayedShowButton() throws RemoteException { - // magnification settings panel should not be showing - assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); + when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(false); mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); @@ -237,8 +235,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout() throws RemoteException { - // magnification settings panel should not be showing - assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); + when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(false); mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS new file mode 100644 index 000000000000..a2001e66e55b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/accessibility/OWNERS
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 25696bffdd66..f41d5c8eeb23 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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. @@ -20,7 +20,6 @@ import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static android.view.Choreographer.FrameCallback; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.systemGestures; @@ -28,14 +27,8 @@ import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityActi import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItems; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.AdditionalAnswers.returnsSecondArg; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; @@ -45,13 +38,17 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static java.util.Arrays.asList; + import android.animation.ValueAnimator; import android.annotation.IdRes; import android.annotation.Nullable; @@ -63,38 +60,36 @@ import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.RegionIterator; import android.os.Handler; import android.os.RemoteException; import android.os.SystemClock; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableLooper; import android.testing.TestableResources; -import android.text.TextUtils; import android.util.Size; +import android.view.AttachedSurfaceControl; import android.view.Display; -import android.view.IWindowSession; +import android.view.Gravity; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IRemoteMagnificationAnimationCallback; +import android.widget.FrameLayout; +import android.window.InputTransferToken; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import com.android.app.viewcapture.ViewCapture; -import com.android.app.viewcapture.ViewCaptureAwareWindowManager; -import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.AnimatorTestRule; @@ -109,8 +104,6 @@ import com.android.systemui.utils.os.FakeHandler; import com.google.common.util.concurrent.AtomicDouble; -import kotlin.Lazy; - import org.junit.After; import org.junit.Assume; import org.junit.Before; @@ -118,31 +111,27 @@ import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; @LargeTest @TestableLooper.RunWithLooper @RunWith(AndroidJUnit4.class) -@RequiresFlagsDisabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER) public class WindowMagnificationControllerTest extends SysuiTestCase { @Rule // NOTE: pass 'null' to allow this test advances time on the main thread. - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(null); - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null); private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; @Mock - private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; - @Mock private MirrorWindowControl mMirrorWindowControl; @Mock private WindowMagnifierCallback mWindowMagnifierCallback; @@ -150,12 +139,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { IRemoteMagnificationAnimationCallback mAnimationCallback; @Mock IRemoteMagnificationAnimationCallback mAnimationCallback2; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + + private SurfaceControl.Transaction mTransaction; @Mock private SecureSettings mSecureSettings; - @Mock - private Lazy<ViewCapture> mLazyViewCapture; private long mWaitAnimationDuration; private long mWaitBounceEffectDuration; @@ -170,11 +157,17 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0); private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); - private IWindowSession mWindowSessionSpy; - private View mSpyView; private View.OnTouchListener mTouchListener; + private MotionEventHelper mMotionEventHelper = new MotionEventHelper(); + + // This list contains all SurfaceControlViewHosts created during a given test. If the + // magnification window is recreated during a test, the list will contain more than a single + // element. + private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>(); + // The most recently created SurfaceControlViewHost. + private SurfaceControlViewHost mSurfaceControlViewHost; private KosmosJavaAdapter mKosmos; private FakeSharedPreferences mSharedPreferences; @@ -196,15 +189,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final WindowManager wm = mContext.getSystemService(WindowManager.class); mWindowManager = spy(new TestableWindowManager(wm)); - mWindowSessionSpy = spy(WindowManagerGlobal.getWindowSession()); - mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); - doAnswer(invocation -> { - FrameCallback callback = invocation.getArgument(0); - callback.doFrame(0); - return null; - }).when(mSfVsyncFrameProvider).postFrameCallback( - any(FrameCallback.class)); mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin()); mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class)); when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then( @@ -228,13 +213,20 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( mContext, mValueAnimator); + Supplier<SurfaceControlViewHost> scvhSupplier = () -> { + mSurfaceControlViewHost = spy(new SurfaceControlViewHost( + mContext, mContext.getDisplay(), new InputTransferToken(), + "WindowMagnification")); + ViewRootImpl viewRoot = mock(ViewRootImpl.class); + when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot); + mSurfaceControlViewHosts.add(mSurfaceControlViewHost); + return mSurfaceControlViewHost; + }; + mTransaction = spy(new SurfaceControl.Transaction()); mSharedPreferences = new FakeSharedPreferences(); when(mContext.getSharedPreferences( eq("window_magnification_preferences"), anyInt())) .thenReturn(mSharedPreferences); - ViewCaptureAwareWindowManager viewCaptureAwareWindowManager = new - ViewCaptureAwareWindowManager(mWindowManager, mLazyViewCapture, - /* isViewCaptureEnabled= */ false); mWindowMagnificationController = new WindowMagnificationController( mContext, @@ -245,10 +237,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnifierCallback, mSysUiState, mSecureSettings, - /* scvhSupplier= */ () -> null, - mSfVsyncFrameProvider, - /* globalWindowSessionSupplier= */ () -> mWindowSessionSpy, - viewCaptureAwareWindowManager); + scvhSupplier); verify(mMirrorWindowControl).setWindowDelegate( any(MirrorWindowControl.MirrorWindowDelegate.class)); @@ -277,7 +266,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { verify(mSecureSettings).getIntForUser( eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING), /* def */ eq(1), /* userHandle= */ anyInt()); - assertTrue(mWindowMagnificationController.isDiagonalScrollingEnabled()); + assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue(); } @Test @@ -325,7 +314,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS); - verify(mSfVsyncFrameProvider, atLeast(2)).postFrameCallback(any()); + verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(), + eq(Surface.ROTATION_0)); } @Test @@ -342,10 +332,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged( (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertEquals(mWindowMagnificationController.getCenterX(), - sourceBoundsCaptor.getValue().exactCenterX(), 0); - assertEquals(mWindowMagnificationController.getCenterY(), - sourceBoundsCaptor.getValue().exactCenterY(), 0); + assertThat(mWindowMagnificationController.getCenterX()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); + assertThat(mWindowMagnificationController.getCenterY()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); } @Test @@ -357,8 +347,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { // Wait for Rects updated. waitForIdleSync(); - List<Rect> rects = mWindowManager.getAttachedView().getSystemGestureExclusionRects(); - assertFalse(rects.isEmpty()); + List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects(); + assertThat(rects).isNotEmpty(); } @Ignore("The default window size should be constrained after fixing b/288056772") @@ -373,11 +363,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); final int halfScreenSize = screenSize / 2; - WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView(); + ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); // The frame size should be the half of smaller value of window height/width unless it //exceed the max frame size. - assertTrue(params.width < halfScreenSize); - assertTrue(params.height < halfScreenSize); + assertThat(params.width).isLessThan(halfScreenSize); + assertThat(params.height).isLessThan(halfScreenSize); } @Test @@ -411,7 +401,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); verify(mMirrorWindowControl).destroyControl(); - assertFalse(hasMagnificationOverlapFlag()); + assertThat(hasMagnificationOverlapFlag()).isFalse(); } @Test @@ -435,10 +425,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); - mWindowMagnificationController.moveWindowMagnifier(100f, 100f); }); - verify(mSfVsyncFrameProvider, atLeastOnce()).postFrameCallback(any()); + waitForIdleSync(); + mInstrumentation.runOnMainSync( + () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f)); + + verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(), + eq(Surface.ROTATION_0)); } @Test @@ -455,6 +449,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10; final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10; + reset(mWindowMagnifierCallback); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.moveWindowMagnifierToPosition( targetCenterX, targetCenterY, mAnimationCallback); @@ -465,12 +460,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { verify(mAnimationCallback, never()).onResult(eq(false)); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertEquals(mWindowMagnificationController.getCenterX(), - sourceBoundsCaptor.getValue().exactCenterX(), 0); - assertEquals(mWindowMagnificationController.getCenterY(), - sourceBoundsCaptor.getValue().exactCenterY(), 0); - assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0); - assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0); + assertThat(mWindowMagnificationController.getCenterX()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); + assertThat(mWindowMagnificationController.getCenterY()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); + assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX); + assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY); } @Test @@ -487,6 +482,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final float centerX = sourceBoundsCaptor.getValue().exactCenterX(); final float centerY = sourceBoundsCaptor.getValue().exactCenterY(); + reset(mWindowMagnifierCallback); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.moveWindowMagnifierToPosition( centerX + 10, centerY + 10, mAnimationCallback); @@ -505,12 +501,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { verify(mAnimationCallback, times(3)).onResult(eq(false)); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertEquals(mWindowMagnificationController.getCenterX(), - sourceBoundsCaptor.getValue().exactCenterX(), 0); - assertEquals(mWindowMagnificationController.getCenterY(), - sourceBoundsCaptor.getValue().exactCenterY(), 0); - assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0); - assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0); + assertThat(mWindowMagnificationController.getCenterX()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); + assertThat(mWindowMagnificationController.getCenterY()) + .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); + assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40); + assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40); } @Test @@ -521,10 +517,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f)); - assertEquals(3.0f, mWindowMagnificationController.getScale(), 0); - final View mirrorView = mWindowManager.getAttachedView(); - assertNotNull(mirrorView); - assertThat(mirrorView.getStateDescription().toString(), containsString("300")); + assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f); + final View mirrorView = mSurfaceControlViewHost.getView(); + assertThat(mirrorView).isNotNull(); + assertThat(mirrorView.getStateDescription().toString()).contains("300"); } @Test @@ -563,12 +559,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged( ActivityInfo.CONFIG_ORIENTATION)); - assertEquals(newRotation, mWindowMagnificationController.mRotation); + assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); final PointF expectedCenter = new PointF(magnifiedCenter.y, displayWidth - magnifiedCenter.x); final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(), mWindowMagnificationController.getCenterY()); - assertEquals(expectedCenter, actualCenter); + assertThat(actualCenter).isEqualTo(expectedCenter); } @Test @@ -583,7 +579,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged( ActivityInfo.CONFIG_ORIENTATION)); - assertEquals(newRotation, mWindowMagnificationController.mRotation); + assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); } @Test @@ -610,14 +606,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); // The ratio of center to window size should be the same. - assertEquals(expectedRatio, - mWindowMagnificationController.getCenterX() / testWindowBounds.width(), - 0); - assertEquals(expectedRatio, - mWindowMagnificationController.getCenterY() / testWindowBounds.height(), - 0); + assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width()) + .isEqualTo(expectedRatio); + assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height()) + .isEqualTo(expectedRatio); } + @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) @Test public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() { int newSmallestScreenWidthDp = @@ -635,7 +630,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - // Change screen density and size to trigger restoring the preferred window size + // Screen density and size change mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp; final Rect testWindowBounds = new Rect( mWindowManager.getCurrentWindowMetrics().getBounds()); @@ -648,12 +643,56 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { // wait for rect update waitForIdleSync(); - WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView(); + ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( R.dimen.magnification_mirror_surface_margin); // The width and height of the view include the magnification frame and the margins. - assertTrue(params.width == (windowFrameSize + 2 * mirrorSurfaceMargin)); - assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin)); + assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); + assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); + } + + @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) + @Test + public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() { + int newSmallestScreenWidthDp = + mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; + int windowFrameSize = mResources.getDimensionPixelSize( + com.android.internal.R.dimen.accessibility_window_magnifier_min_size); + Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize); + mSharedPreferences + .edit() + .putString(String.valueOf(newSmallestScreenWidthDp), + WindowMagnificationFrameSpec.serialize( + WindowMagnificationSettings.MagnificationSize.CUSTOM, + preferredWindowSize)) + .commit(); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, + Float.NaN); + }); + + // Screen density and size change + mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp; + final Rect testWindowBounds = new Rect( + mWindowManager.getCurrentWindowMetrics().getBounds()); + testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, + testWindowBounds.right + 100, testWindowBounds.bottom + 100); + mWindowManager.setWindowBounds(testWindowBounds); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); + }); + + // wait for rect update + waitForIdleSync(); + verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored( + eq(mContext.getDisplayId()), + eq(WindowMagnificationSettings.MagnificationSize.CUSTOM)); + ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); + final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( + R.dimen.magnification_mirror_surface_margin); + // The width and height of the view include the magnification frame and the margins. + assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); + assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); } @Test @@ -675,10 +714,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final int defaultWindowSize = mWindowMagnificationController.getMagnificationWindowSizeFromIndex( WindowMagnificationSettings.MagnificationSize.MEDIUM); - WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView(); + ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - assertTrue(params.width == defaultWindowSize); - assertTrue(params.height == defaultWindowSize); + assertThat(params.width).isEqualTo(defaultWindowSize); + assertThat(params.height).isEqualTo(defaultWindowSize); } @Test @@ -695,9 +734,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }); verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt()); - verify(mWindowManager).removeView(any()); + verify(mSurfaceControlViewHosts.get(0)).release(); verify(mMirrorWindowControl).destroyControl(); - verify(mWindowManager).addView(any(), any()); + verify(mSurfaceControlViewHosts.get(1)).setView(any(), any()); verify(mMirrorWindowControl).showControl(); } @@ -716,21 +755,30 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN, Float.NaN); }); - final View mirrorView = mWindowManager.getAttachedView(); - assertNotNull(mirrorView); + final View mirrorView = mSurfaceControlViewHost.getView(); + assertThat(mirrorView).isNotNull(); final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo); - assertNotNull(nodeInfo.getContentDescription()); - assertThat(nodeInfo.getStateDescription().toString(), containsString("250")); - assertThat(nodeInfo.getActionList(), - hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null), - new AccessibilityAction(R.id.accessibility_action_zoom_out, null), - new AccessibilityAction(R.id.accessibility_action_move_right, null), - new AccessibilityAction(R.id.accessibility_action_move_left, null), - new AccessibilityAction(R.id.accessibility_action_move_down, null), - new AccessibilityAction(R.id.accessibility_action_move_up, null))); + assertThat(nodeInfo.getContentDescription()).isNotNull(); + assertThat(nodeInfo.getStateDescription().toString()).contains("250"); + assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList( + new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), + mContext.getResources().getString( + R.string.magnification_open_settings_click_label)), + new AccessibilityAction(R.id.accessibility_action_zoom_in, + mContext.getString(R.string.accessibility_control_zoom_in)), + new AccessibilityAction(R.id.accessibility_action_zoom_out, + mContext.getString(R.string.accessibility_control_zoom_out)), + new AccessibilityAction(R.id.accessibility_action_move_right, + mContext.getString(R.string.accessibility_control_move_right)), + new AccessibilityAction(R.id.accessibility_action_move_left, + mContext.getString(R.string.accessibility_control_move_left)), + new AccessibilityAction(R.id.accessibility_action_move_down, + mContext.getString(R.string.accessibility_control_move_down)), + new AccessibilityAction(R.id.accessibility_action_move_up, + mContext.getString(R.string.accessibility_control_move_up)))); } @Test @@ -741,29 +789,34 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - final View mirrorView = mWindowManager.getAttachedView(); - assertTrue( - mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null)); + final View mirrorView = mSurfaceControlViewHost.getView(); + assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null)) + .isTrue(); // Minimum scale is 1.0. verify(mWindowMagnifierCallback).onPerformScaleAction( eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true)); - assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null)); + assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null)) + .isTrue(); verify(mWindowMagnifierCallback).onPerformScaleAction( eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true)); // TODO: Verify the final state when the mirror surface is visible. - assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null)); - assertTrue( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null)); - assertTrue( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null)); - assertTrue( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null)); + assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null)) + .isTrue(); + assertThat( + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null)) + .isTrue(); + assertThat( + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null)) + .isTrue(); + assertThat( + mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null)) + .isTrue(); verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId)); - assertTrue(mirrorView.performAccessibilityAction( - AccessibilityAction.ACTION_CLICK.getId(), null)); + assertThat(mirrorView.performAccessibilityAction( + AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue(); verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId)); } @@ -775,7 +828,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null); verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId)); @@ -795,20 +848,22 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { View topRightCorner = getInternalView(R.id.top_right_corner); View topLeftCorner = getInternalView(R.id.top_left_corner); - assertEquals(View.VISIBLE, closeButton.getVisibility()); - assertEquals(View.VISIBLE, bottomRightCorner.getVisibility()); - assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility()); - assertEquals(View.VISIBLE, topRightCorner.getVisibility()); - assertEquals(View.VISIBLE, topLeftCorner.getVisibility()); + assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE); - final View mirrorView = mWindowManager.getAttachedView(); - mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null); + final View mirrorView = mSurfaceControlViewHost.getView(); + mInstrumentation.runOnMainSync(() -> + mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), + null)); - assertEquals(View.GONE, closeButton.getVisibility()); - assertEquals(View.GONE, bottomRightCorner.getVisibility()); - assertEquals(View.GONE, bottomLeftCorner.getVisibility()); - assertEquals(View.GONE, topRightCorner.getVisibility()); - assertEquals(View.GONE, topLeftCorner.getVisibility()); + assertThat(closeButton.getVisibility()).isEqualTo(View.GONE); + assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE); + assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE); + assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE); + assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE); } @Test @@ -828,7 +883,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicInteger actualWindowHeight = new AtomicInteger(); final AtomicInteger actualWindowWidth = new AtomicInteger(); @@ -836,8 +891,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mirrorView.performAccessibilityAction( R.id.accessibility_action_increase_window_width, null); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( @@ -847,8 +904,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { int newWindowWidth = (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount)) + 2 * mirrorSurfaceMargin; - assertEquals(newWindowWidth, actualWindowWidth.get()); - assertEquals(startingHeight, actualWindowHeight.get()); + assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth); + assertThat(actualWindowHeight.get()).isEqualTo(startingHeight); } @Test @@ -868,7 +925,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicInteger actualWindowHeight = new AtomicInteger(); final AtomicInteger actualWindowWidth = new AtomicInteger(); @@ -876,8 +933,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mirrorView.performAccessibilityAction( R.id.accessibility_action_increase_window_height, null); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( @@ -887,8 +946,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { int newWindowHeight = (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount)) + 2 * mirrorSurfaceMargin; - assertEquals(startingWidth, actualWindowWidth.get()); - assertEquals(newWindowHeight, actualWindowHeight.get()); + assertThat(actualWindowWidth.get()).isEqualTo(startingWidth); + assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight); } @Test @@ -904,11 +963,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AccessibilityNodeInfo accessibilityNodeInfo = mirrorView.createAccessibilityNodeInfo(); - assertFalse(accessibilityNodeInfo.getActionList().contains( - new AccessibilityAction(R.id.accessibility_action_increase_window_width, null))); + assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( + new AccessibilityAction( + R.id.accessibility_action_increase_window_width, + mContext.getString( + R.string.accessibility_control_increase_window_width))); } @Test @@ -924,11 +986,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AccessibilityNodeInfo accessibilityNodeInfo = mirrorView.createAccessibilityNodeInfo(); - assertFalse(accessibilityNodeInfo.getActionList().contains( - new AccessibilityAction(R.id.accessibility_action_increase_window_height, null))); + assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( + new AccessibilityAction( + R.id.accessibility_action_increase_window_height, null)); } @Test @@ -947,7 +1010,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicInteger actualWindowHeight = new AtomicInteger(); final AtomicInteger actualWindowWidth = new AtomicInteger(); @@ -955,8 +1018,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mirrorView.performAccessibilityAction( R.id.accessibility_action_decrease_window_width, null); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( @@ -966,8 +1031,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { int newWindowWidth = (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount)) + 2 * mirrorSurfaceMargin; - assertEquals(newWindowWidth, actualWindowWidth.get()); - assertEquals(startingSize, actualWindowHeight.get()); + assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth); + assertThat(actualWindowHeight.get()).isEqualTo(startingSize); } @Test @@ -987,7 +1052,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicInteger actualWindowHeight = new AtomicInteger(); final AtomicInteger actualWindowWidth = new AtomicInteger(); @@ -995,8 +1060,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mirrorView.performAccessibilityAction( R.id.accessibility_action_decrease_window_height, null); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( @@ -1006,8 +1073,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { int newWindowHeight = (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount)) + 2 * mirrorSurfaceMargin; - assertEquals(startingSize, actualWindowWidth.get()); - assertEquals(newWindowHeight, actualWindowHeight.get()); + assertThat(actualWindowWidth.get()).isEqualTo(startingSize); + assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight); } @Test @@ -1023,15 +1090,16 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AccessibilityNodeInfo accessibilityNodeInfo = mirrorView.createAccessibilityNodeInfo(); - assertFalse(accessibilityNodeInfo.getActionList().contains( - new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null))); + assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( + new AccessibilityAction( + R.id.accessibility_action_decrease_window_width, null)); } @Test - public void windowHeightIsMin_noDecreaseWindowHeightA11yAcyion() { + public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() { int mMinWindowSize = mResources.getDimensionPixelSize( com.android.internal.R.dimen.accessibility_window_magnifier_min_size); final int startingSize = mMinWindowSize; @@ -1043,11 +1111,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AccessibilityNodeInfo accessibilityNodeInfo = mirrorView.createAccessibilityNodeInfo(); - assertFalse(accessibilityNodeInfo.getActionList().contains( - new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null))); + assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( + new AccessibilityAction( + R.id.accessibility_action_decrease_window_height, null)); } @Test @@ -1057,8 +1126,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - assertEquals(getContext().getResources().getString( - com.android.internal.R.string.android_system_label), getAccessibilityWindowTitle()); + assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString( + com.android.internal.R.string.android_system_label)); } @Test @@ -1073,14 +1142,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { Float.NaN); }); - assertEquals(Float.NaN, mWindowMagnificationController.getScale(), 0); + assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN); } @Test public void enableWindowMagnification_rotationIsChanged_updateRotationValue() { // the config orientation should not be undefined, since it would cause config.diff // returning 0 and thus the orientation changed would not be detected - assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation); + assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED); final Configuration config = mResources.getConfiguration(); config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT @@ -1091,7 +1160,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN)); - assertEquals(newRotation, mWindowMagnificationController.mRotation); + assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); } @Test @@ -1119,7 +1188,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE); }); - assertTrue(TextUtils.equals(newA11yWindowTitle, getAccessibilityWindowTitle())); + assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle); } @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925") @@ -1134,7 +1203,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.onSingleTap(mSpyView); }); - final View mirrorView = mWindowManager.getAttachedView(); + final View mirrorView = mSurfaceControlViewHost.getView(); final AtomicDouble maxScaleX = new AtomicDouble(); advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> { @@ -1142,10 +1211,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max); final double oldMax = maxScaleX.get(); final double newMax = Math.max(mirrorView.getScaleX(), oldMax); - assertTrue(maxScaleX.compareAndSet(oldMax, newMax)); + assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue(); }); - assertTrue(maxScaleX.get() > 1.0); + assertThat(maxScaleX.get()).isGreaterThan(1.0); } @Test @@ -1174,30 +1243,23 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.updateWindowMagnificationInternal( Float.NaN, Float.NaN, Float.NaN); }); + // Wait for Region updated. + waitForIdleSync(); mInstrumentation.runOnMainSync( () -> { mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0); }); - // Wait for Region updated. waitForIdleSync(); - final ArgumentCaptor<Region> tapExcludeRegionCapturer = - ArgumentCaptor.forClass(Region.class); - verify(mWindowSessionSpy, times(2)) - .updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture()); - Region tapExcludeRegion = tapExcludeRegionCapturer.getValue(); - RegionIterator iterator = new RegionIterator(tapExcludeRegion); + AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl(); + // Verifying two times in: (1) enable window magnification (2) reposition drag handle + verify(viewRoot, times(2)).setTouchableRegion(any()); - final Rect topRect = new Rect(); - final Rect bottomRect = new Rect(); - assertTrue(iterator.next(topRect)); - assertTrue(iterator.next(bottomRect)); - assertFalse(iterator.next(new Rect())); - - assertEquals(topRect.right, bottomRect.right); - assertNotEquals(topRect.left, bottomRect.left); + View dragButton = getInternalView(R.id.drag_handle); + FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams(); + assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT); } @Test @@ -1210,29 +1272,23 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.updateWindowMagnificationInternal( Float.NaN, Float.NaN, Float.NaN); }); + // Wait for Region updated. + waitForIdleSync(); mInstrumentation.runOnMainSync( () -> { mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0); }); - // Wait for Region updated. waitForIdleSync(); - final ArgumentCaptor<Region> tapExcludeRegionCapturer = - ArgumentCaptor.forClass(Region.class); - verify(mWindowSessionSpy).updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture()); - Region tapExcludeRegion = tapExcludeRegionCapturer.getValue(); - RegionIterator iterator = new RegionIterator(tapExcludeRegion); - - final Rect topRect = new Rect(); - final Rect bottomRect = new Rect(); - assertTrue(iterator.next(topRect)); - assertTrue(iterator.next(bottomRect)); - assertFalse(iterator.next(new Rect())); + AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl(); + // Verifying one times in: (1) enable window magnification + verify(viewRoot).setTouchableRegion(any()); - assertEquals(topRect.left, bottomRect.left); - assertNotEquals(topRect.right, bottomRect.right); + View dragButton = getInternalView(R.id.drag_handle); + FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams(); + assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT); } @Test @@ -1249,13 +1305,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final AtomicInteger actualWindowWidth = new AtomicInteger(); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(expectedWindowHeight, actualWindowHeight.get()); - assertEquals(expectedWindowWidth, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); + assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); } @Test @@ -1271,12 +1327,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight); mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(expectedWindowHeight, actualWindowHeight.get()); - assertEquals(expectedWindowWidth, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); + assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); } @Test @@ -1292,12 +1348,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.setWindowSize(minimumWindowSize - 10, minimumWindowSize - 10); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(minimumWindowSize, actualWindowHeight.get()); - assertEquals(minimumWindowSize, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize); + assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize); } @Test @@ -1311,12 +1367,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final AtomicInteger actualWindowWidth = new AtomicInteger(); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(bounds.height(), actualWindowHeight.get()); - assertEquals(bounds.width(), actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(bounds.height()); + assertThat(actualWindowWidth.get()).isEqualTo(bounds.width()); } @Test @@ -1342,12 +1398,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mWindowMagnificationController.changeMagnificationSize( WindowMagnificationSettings.MagnificationSize.LARGE); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(expectedWindowHeight, actualWindowHeight.get()); - assertEquals(expectedWindowWidth, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); + assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); } @Test @@ -1376,12 +1434,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { () -> { mWindowMagnificationController .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(startingSize + 1, actualWindowHeight.get()); - assertEquals(startingSize + 2, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1); + assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2); } @Test @@ -1404,11 +1464,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.setEditMagnifierSizeMode(true); mWindowMagnificationController .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f); - actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height); - actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width); + actualWindowHeight.set( + mSurfaceControlViewHost.getView().getLayoutParams().height); + actualWindowWidth.set( + mSurfaceControlViewHost.getView().getLayoutParams().width); }); - assertEquals(startingSize + 1, actualWindowHeight.get()); - assertEquals(startingSize, actualWindowWidth.get()); + assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1); + assertThat(actualWindowWidth.get()).isEqualTo(startingSize); } @Test @@ -1430,8 +1492,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { magnificationCenterY.set((int) mWindowMagnificationController.getCenterY()); }); - assertTrue(magnificationCenterX.get() < bounds.right); - assertTrue(magnificationCenterY.get() < bounds.bottom); + assertThat(magnificationCenterX.get()).isLessThan(bounds.right); + assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom); } @Test @@ -1451,13 +1513,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { dragButton.dispatchTouchEvent( obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100)); - verify(mWindowManager).addView(any(View.class), any()); + verify(mSurfaceControlViewHost).setView(any(View.class), any()); } private <T extends View> T getInternalView(@IdRes int idRes) { - View mirrorView = mWindowManager.getAttachedView(); + View mirrorView = mSurfaceControlViewHost.getView(); T view = mirrorView.findViewById(idRes); - assertNotNull(view); + assertThat(view).isNotNull(); return view; } @@ -1466,14 +1528,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y); } - private CharSequence getAccessibilityWindowTitle() { - final View mirrorView = mWindowManager.getAttachedView(); + private String getAccessibilityWindowTitle() { + final View mirrorView = mSurfaceControlViewHost.getView(); if (mirrorView == null) { return null; } WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) mirrorView.getLayoutParams(); - return layoutParams.accessibilityTitle; + return layoutParams.accessibilityTitle.toString(); } private boolean hasMagnificationOverlapFlag() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt index f58bbc3cf0cf..d3715926932c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt @@ -20,7 +20,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -71,7 +70,7 @@ class BouncerUserActionsViewModelTest : SysuiTestCase() { assertThat(actions) .containsEntriesExactly( Back to UserActionResult(Scenes.QuickSettings), - Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings), + Swipe.Down to UserActionResult(Scenes.QuickSettings), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt index 3388a785a26a..20cd8608d204 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt @@ -28,11 +28,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.Serializable @@ -68,6 +70,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { @Mock private lateinit var systemProperties: SystemPropertiesHelper @Mock private lateinit var resources: Resources @Mock private lateinit var restarter: Restarter + private lateinit var fakeExecutor: FakeExecutor private lateinit var userTracker: FakeUserTracker private val flagMap = mutableMapOf<String, Flag<*>>() private lateinit var broadcastReceiver: BroadcastReceiver @@ -83,6 +86,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA) flagMap.put(releasedFlagB.name, releasedFlagB) + fakeExecutor = FakeExecutor(FakeSystemClock()) userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockContext }) mFeatureFlagsClassicDebug = @@ -95,7 +99,8 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { serverFlagReader, flagMap, restarter, - userTracker + userTracker, + fakeExecutor, ) mFeatureFlagsClassicDebug.init() verify(flagManager).onSettingsChangedAction = any() @@ -325,14 +330,14 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { // trying to erase an id not in the map does nothing broadcastReceiver.onReceive( mockContext, - Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "") + Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, ""), ) verifyNoMoreInteractions(flagManager, globalSettings) // valid id with no value puts empty string in the setting broadcastReceiver.onReceive( mockContext, - Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1") + Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1"), ) verifyPutData("1", "", numReads = 0) } @@ -415,7 +420,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { serverFlagReader.setFlagValue( teamfoodableFlagA.namespace, teamfoodableFlagA.name, - !teamfoodableFlagA.default + !teamfoodableFlagA.default, ) verify(restarter, never()).restartSystemUI(anyString()) } @@ -428,7 +433,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { serverFlagReader.setFlagValue( teamfoodableFlagA.namespace, teamfoodableFlagA.name, - !teamfoodableFlagA.default + !teamfoodableFlagA.default, ) verify(restarter).restartSystemUI(anyString()) } @@ -441,7 +446,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { serverFlagReader.setFlagValue( teamfoodableFlagA.namespace, teamfoodableFlagA.name, - teamfoodableFlagA.default + teamfoodableFlagA.default, ) verify(restarter, never()).restartSystemUI(anyString()) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index 7f313564e35a..2c12f8782ddc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -193,7 +193,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { @Test @EnableSceneContainer - fun surfaceBehindVisibility_fromLockscreenToGone_noUserInput_trueThroughout() = + fun surfaceBehindVisibility_fromLockscreenToGone_dependsOnDeviceEntry() = testScope.runTest { val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) @@ -208,7 +208,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { SuccessFingerprintAuthenticationStatus(0, true) ) - // Start the transition to Gone, the surface should become immediately visible. + // Start the transition to Gone, the surface should remain invisible. kosmos.setSceneTransition( ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, @@ -220,9 +220,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { ) ) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - assertThat(isSurfaceBehindVisible).isTrue() + assertThat(isSurfaceBehindVisible).isFalse() - // Towards the end of the transition, the surface should continue to be visible. + // Towards the end of the transition, the surface should continue to remain invisible. kosmos.setSceneTransition( ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, @@ -234,7 +234,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { ) ) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - assertThat(isSurfaceBehindVisible).isTrue() + assertThat(isSurfaceBehindVisible).isFalse() // After the transition, settles on Gone. Surface behind should stay visible now. kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) @@ -245,43 +245,6 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { @Test @EnableSceneContainer - fun surfaceBehindVisibility_fromLockscreenToGone_withUserInput_falseUntilInputStops() = - testScope.runTest { - val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) - val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) - - // Before the transition, we start on Lockscreen so the surface should start invisible. - kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen)) - assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - assertThat(isSurfaceBehindVisible).isFalse() - - // Unlocked with fingerprint. - kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) - - // Start the transition to Gone, the surface should not be visible while - // isUserInputOngoing is true - val isUserInputOngoing = MutableStateFlow(true) - kosmos.setSceneTransition( - ObservableTransitionState.Transition( - fromScene = Scenes.Lockscreen, - toScene = Scenes.Gone, - isInitiatedByUserInput = true, - isUserInputOngoing = isUserInputOngoing, - progress = flowOf(0.51f), - currentScene = flowOf(Scenes.Gone), - ) - ) - assertThat(isSurfaceBehindVisible).isFalse() - - // When isUserInputOngoing becomes false, then the surface should become visible. - isUserInputOngoing.value = false - assertThat(isSurfaceBehindVisible).isTrue() - } - - @Test - @EnableSceneContainer fun surfaceBehindVisibility_fromBouncerToGone_becomesTrue() = testScope.runTest { val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 7f0937059494..0e3b03f74c02 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -44,6 +44,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Ignore @@ -107,6 +108,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { fun translationAndScale_whenNotDozing() = testScope.runTest { val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to not dozing (on lockscreen) keyguardTransitionRepository.sendTransitionStep( @@ -180,6 +182,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100)) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -221,6 +224,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -263,6 +267,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -305,6 +310,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -423,6 +429,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { .thenReturn(if (isWeatherClock) true else false) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -434,6 +441,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { ), validateStep = false, ) + runCurrent() // Trigger a change to the burn-in model burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt index 6397979d9627..5c4b7432e18d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt @@ -25,7 +25,6 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.UserActionResult.ShowOverlay @@ -201,8 +200,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { val userActions by collectLastValue(underTest.actions) val downDestination = userActions?.get( - Swipe( - SwipeDirection.Down, + Swipe.Down( fromSource = Edge.Top.takeIf { downFromEdge }, pointerCount = if (downWithTwoPointers) 2 else 1, ) @@ -292,8 +290,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { val downDestination = userActions?.get( - Swipe( - SwipeDirection.Down, + Swipe.Down( fromSource = Edge.Top.takeIf { downFromEdge }, pointerCount = if (downWithTwoPointers) 2 else 1, ) @@ -310,8 +307,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() { val downFromTopRightDestination = userActions?.get( - Swipe( - SwipeDirection.Down, + Swipe.Down( fromSource = SceneContainerEdge.TopRight, pointerCount = if (downWithTwoPointers) 2 else 1, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt index 9e3fdf377b83..8e67e602abd9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt @@ -20,7 +20,6 @@ import android.media.session.MediaSession import android.os.Bundle import android.os.Handler import android.os.looper -import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants import androidx.media3.common.Player @@ -69,10 +68,9 @@ class Media3ActionFactoryTest : SysuiTestCase() { private val testScope = kosmos.testScope private val controllerFactory = kosmos.fakeMediaControllerFactory private val tokenFactory = kosmos.fakeSessionTokenFactory - private lateinit var testableLooper: TestableLooper - private var commandCaptor = argumentCaptor<SessionCommand>() - private var runnableCaptor = argumentCaptor<Runnable>() + private val commandCaptor = argumentCaptor<SessionCommand>() + private val runnableCaptor = argumentCaptor<Runnable>() private val legacyToken = MediaSession.Token(1, null) private val token = mock<SessionToken>() @@ -97,8 +95,6 @@ class Media3ActionFactoryTest : SysuiTestCase() { @Before fun setup() { - testableLooper = TestableLooper.get(this) - underTest = Media3ActionFactory( context, @@ -246,7 +242,6 @@ class Media3ActionFactoryTest : SysuiTestCase() { assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 2") actions.custom0!!.action!!.run() runCurrent() - testableLooper.processAllMessages() verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>()) assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 2") verify(media3Controller).release() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt index 1a7265b09aae..cf503bbf1310 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt @@ -82,7 +82,7 @@ class MediaDataLoaderTest : SysuiTestCase() { private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic private val mediaFlags = kosmos.mediaFlags private val mediaControllerFactory = kosmos.fakeMediaControllerFactory - private val media3ActionFactory = kosmos.media3ActionFactory + private lateinit var media3ActionFactory: Media3ActionFactory private val session = MediaSession(context, "MediaDataLoaderTestSession") private val metadataBuilder = MediaMetadata.Builder().apply { @@ -94,6 +94,7 @@ class MediaDataLoaderTest : SysuiTestCase() { @Before fun setUp() { + media3ActionFactory = kosmos.media3ActionFactory mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController) whenever(mediaController.sessionToken).thenReturn(session.sessionToken) whenever(mediaController.metadata).then { metadataBuilder.build() } @@ -311,7 +312,6 @@ class MediaDataLoaderTest : SysuiTestCase() { } build() } - val result = underTest.loadMediaData(KEY, mediaNotification) assertThat(result).isNotNull() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index e12c67b24893..104aa517fa4f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -16,7 +16,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.wm.shell.recents.RecentTasks -import com.android.wm.shell.shared.GroupedRecentTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo import com.android.wm.shell.shared.split.SplitBounds import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Truth.assertThat @@ -219,9 +219,9 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { } @Suppress("UNCHECKED_CAST") - private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) { + private fun givenRecentTasks(vararg tasks: GroupedTaskInfo) { whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer { - val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>> + val consumer = it.arguments.last() as Consumer<List<GroupedTaskInfo>> consumer.accept(tasks.toList()) } } @@ -247,7 +247,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { userId: Int = 0, isVisible: Boolean = false, userType: RecentTask.UserType = STANDARD, - ): GroupedRecentTaskInfo { + ): GroupedTaskInfo { val userInfo = mock<UserInfo> { whenever(isCloneProfile).thenReturn(userType == CLONED) @@ -255,7 +255,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { whenever(isPrivateProfile).thenReturn(userType == PRIVATE) } whenever(userManager.getUserInfo(userId)).thenReturn(userInfo) - return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible)) + return GroupedTaskInfo.forFullscreenTasks(createTaskInfo(taskId, userId, isVisible)) } private fun createTaskPair( @@ -263,9 +263,9 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { userId1: Int = 0, taskId2: Int, userId2: Int = 0, - isVisible: Boolean = false - ): GroupedRecentTaskInfo = - GroupedRecentTaskInfo.forSplitTasks( + isVisible: Boolean = false, + ): GroupedTaskInfo = + GroupedTaskInfo.forSplitTasks( createTaskInfo(taskId1, userId1, isVisible), createTaskInfo(taskId2, userId2, isVisible), SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_2_50_50) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt index 642d9a0b1e9d..855931c32671 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt @@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -76,12 +75,8 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() { underTest.activateIn(this) assertThat( - (actions?.get( - Swipe( - direction = SwipeDirection.Down, - fromSource = SceneContainerEdge.TopRight, - ) - ) as? UserActionResult.ReplaceByOverlay) + (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopRight)) + as? UserActionResult.ReplaceByOverlay) ?.overlay ) .isEqualTo(Overlays.QuickSettingsShade) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt index ada2138d4d52..b30313e07012 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt @@ -35,9 +35,12 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -121,6 +124,38 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() { assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade) } + @Test + fun showHeader_showsOnNarrowScreen() = + testScope.runTest { + kosmos.shadeRepository.setShadeLayoutWide(false) + + // Shown when notifications are present. + kosmos.activeNotificationListRepository.setActiveNotifs(1) + runCurrent() + assertThat(underTest.showHeader).isTrue() + + // Hidden when notifications are not present. + kosmos.activeNotificationListRepository.setActiveNotifs(0) + runCurrent() + assertThat(underTest.showHeader).isFalse() + } + + @Test + fun showHeader_hidesOnWideScreen() = + testScope.runTest { + kosmos.shadeRepository.setShadeLayoutWide(true) + + // Hidden when notifications are present. + kosmos.activeNotificationListRepository.setActiveNotifs(1) + runCurrent() + assertThat(underTest.showHeader).isFalse() + + // Hidden when notifications are not present. + kosmos.activeNotificationListRepository.setActiveNotifs(0) + runCurrent() + assertThat(underTest.showHeader).isFalse() + } + private fun TestScope.lockDevice() { val currentScene by collectLastValue(sceneInteractor.currentScene) kosmos.powerInteractor.setAsleepForTest() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt index 9fe9ed2bf47a..5cba325450e6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.qs.composefragment.viewmodel.MediaState.ANY_MEDIA import com.android.systemui.qs.composefragment.viewmodel.MediaState.NO_MEDIA import com.android.systemui.qs.fgsManagerController import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor +import com.android.systemui.qs.panels.ui.viewmodel.setConfigurationForMediaInRow import com.android.systemui.res.R import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.statusbar.StatusBarState @@ -269,6 +270,54 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() } } + @Test + fun mediaNotInRow() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = false) + setMediaState(ACTIVE_MEDIA) + + assertThat(underTest.qqsMediaInRow).isFalse() + assertThat(underTest.qsMediaInRow).isFalse() + } + } + + @Test + fun mediaInRow_mediaActive_bothInRow() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = true) + setMediaState(ACTIVE_MEDIA) + + assertThat(underTest.qqsMediaInRow).isTrue() + assertThat(underTest.qsMediaInRow).isTrue() + } + } + + @Test + fun mediaInRow_mediaRecommendation_onlyQSInRow() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = true) + setMediaState(ANY_MEDIA) + + assertThat(underTest.qqsMediaInRow).isFalse() + assertThat(underTest.qsMediaInRow).isTrue() + } + } + + @Test + fun mediaInRow_correctConfig_noMediaVisible_noMediaInRow() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = true) + setMediaState(NO_MEDIA) + + assertThat(underTest.qqsMediaInRow).isFalse() + assertThat(underTest.qsMediaInRow).isFalse() + } + } + private fun TestScope.setMediaState(state: MediaState) { with(kosmos) { val activeMedia = state == ACTIVE_MEDIA diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt new file mode 100644 index 000000000000..ab78029684d4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/QSFragmentComposeTest.kt @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.getBoundsInRoot +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpRect +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.width +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.composefragment.QuickQuickSettingsLayout +import com.android.systemui.qs.composefragment.QuickSettingsLayout +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class QSFragmentComposeTest : SysuiTestCase() { + + @get:Rule val composeTestRule = createComposeRule() + + @Test + fun portraitLayout_qqs() { + composeTestRule.setContent { + QuickQuickSettingsLayout( + tiles = { Tiles(TILES_HEIGHT_PORTRAIT) }, + media = { Media() }, + mediaInRow = false, + ) + } + + composeTestRule.waitForIdle() + + val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot() + val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot() + + // All nodes aligned in a column + assertThat(tilesBounds.left).isEqualTo(mediaBounds.left) + assertThat(tilesBounds.right).isEqualTo(mediaBounds.right) + assertThat(tilesBounds.bottom).isLessThan(mediaBounds.top) + } + + @Test + fun landscapeLayout_qqs() { + composeTestRule.setContent { + QuickQuickSettingsLayout( + tiles = { Tiles(TILES_HEIGHT_LANDSCAPE) }, + media = { Media() }, + mediaInRow = true, + ) + } + + composeTestRule.waitForIdle() + + val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot() + val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot() + + // Media to the right of tiles + assertThat(tilesBounds.right).isLessThan(mediaBounds.left) + // "Same" width + assertThat((tilesBounds.width - mediaBounds.width).abs()).isAtMost(1.dp) + // Vertically centered + assertThat((tilesBounds.centerY - mediaBounds.centerY).abs()).isAtMost(1.dp) + } + + @Test + fun portraitLayout_qs() { + composeTestRule.setContent { + QuickSettingsLayout( + brightness = { Brightness() }, + tiles = { Tiles(TILES_HEIGHT_PORTRAIT) }, + media = { Media() }, + mediaInRow = false, + ) + } + + composeTestRule.waitForIdle() + + val brightnessBounds = composeTestRule.onNodeWithTag(BRIGHTNESS).getBoundsInRoot() + val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot() + val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot() + + assertThat(brightnessBounds.left).isEqualTo(tilesBounds.left) + assertThat(tilesBounds.left).isEqualTo(mediaBounds.left) + + assertThat(brightnessBounds.right).isEqualTo(tilesBounds.right) + assertThat(tilesBounds.right).isEqualTo(mediaBounds.right) + + assertThat(brightnessBounds.bottom).isLessThan(tilesBounds.top) + assertThat(tilesBounds.bottom).isLessThan(mediaBounds.top) + } + + @Test + fun landscapeLayout_qs() { + composeTestRule.setContent { + QuickSettingsLayout( + brightness = { Brightness() }, + tiles = { Tiles(TILES_HEIGHT_PORTRAIT) }, + media = { Media() }, + mediaInRow = true, + ) + } + + composeTestRule.waitForIdle() + + val brightnessBounds = composeTestRule.onNodeWithTag(BRIGHTNESS).getBoundsInRoot() + val tilesBounds = composeTestRule.onNodeWithTag(TILES).getBoundsInRoot() + val mediaBounds = composeTestRule.onNodeWithTag(MEDIA).getBoundsInRoot() + + // Brightness takes full width, with left end aligned with tiles and right end aligned with + // media + assertThat(brightnessBounds.left).isEqualTo(tilesBounds.left) + assertThat(brightnessBounds.right).isEqualTo(mediaBounds.right) + + // Brightness above tiles and media + assertThat(brightnessBounds.bottom).isLessThan(tilesBounds.top) + assertThat(brightnessBounds.bottom).isLessThan(mediaBounds.top) + + // Media to the right of tiles + assertThat(tilesBounds.right).isLessThan(mediaBounds.left) + // "Same" width + assertThat((tilesBounds.width - mediaBounds.width).abs()).isAtMost(1.dp) + // Vertically centered + assertThat((tilesBounds.centerY - mediaBounds.centerY).abs()).isAtMost(1.dp) + } + + private companion object { + const val BRIGHTNESS = "brightness" + const val TILES = "tiles" + const val MEDIA = "media" + val TILES_HEIGHT_PORTRAIT = 300.dp + val TILES_HEIGHT_LANDSCAPE = 150.dp + val MEDIA_HEIGHT = 100.dp + val BRIGHTNESS_HEIGHT = 64.dp + + @Composable + fun Brightness() { + Box(modifier = Modifier.testTag(BRIGHTNESS).height(BRIGHTNESS_HEIGHT).fillMaxWidth()) + } + + @Composable + fun Tiles(height: Dp) { + Box(modifier = Modifier.testTag(TILES).height(height).fillMaxWidth()) + } + + @Composable + fun Media() { + Box(modifier = Modifier.testTag(MEDIA).height(MEDIA_HEIGHT).fillMaxWidth()) + } + + val DpRect.centerY: Dp + get() = (top + bottom) / 2 + + fun Dp.abs() = if (this > 0.dp) this else -this + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt new file mode 100644 index 000000000000..635badac04f5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.Configuration +import android.content.res.mainResources +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.flags.setFlagValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS +import com.android.systemui.media.controls.ui.controller.MediaLocation +import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager +import com.android.systemui.media.controls.ui.view.MediaHost +import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.shared.flag.DualShade +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(ParameterizedAndroidJunit4::class) +@SmallTest +class MediaInRowInLandscapeViewModelTest(private val testData: TestData) : SysuiTestCase() { + + private val kosmos = testKosmos().apply { usingMediaInComposeFragment = testData.usingMedia } + + private val underTest by lazy { + kosmos.mediaInRowInLandscapeViewModelFactory.create(TESTED_MEDIA_LOCATION) + } + + @Before + fun setUp() { + mSetFlagsRule.setFlagValue(DualShade.FLAG_NAME, testData.shadeMode == ShadeMode.Dual) + } + + @Test + fun shouldMediaShowInRow() = + with(kosmos) { + testScope.runTest { + underTest.activateIn(testScope) + + shadeRepository.setShadeLayoutWide(testData.shadeMode != ShadeMode.Single) + val config = + Configuration(mainResources.configuration).apply { + orientation = testData.orientation + screenLayout = testData.screenLayoutLong + } + fakeConfigurationRepository.onConfigurationChange(config) + mainResources.configuration.updateFrom(config) + mediaHostStatesManager.updateHostState( + testData.mediaLocation, + MediaHost.MediaHostStateHolder().apply { visible = testData.mediaVisible }, + ) + runCurrent() + + assertThat(underTest.shouldMediaShowInRow).isEqualTo(testData.mediaInRowExpected) + } + } + + data class TestData( + val usingMedia: Boolean, + val shadeMode: ShadeMode, + val orientation: Int, + val screenLayoutLong: Int, + val mediaVisible: Boolean, + @MediaLocation val mediaLocation: Int, + ) { + val mediaInRowExpected: Boolean + get() = + usingMedia && + shadeMode == ShadeMode.Single && + orientation == Configuration.ORIENTATION_LANDSCAPE && + screenLayoutLong == Configuration.SCREENLAYOUT_LONG_YES && + mediaVisible && + mediaLocation == TESTED_MEDIA_LOCATION + } + + companion object { + private const val TESTED_MEDIA_LOCATION = LOCATION_QS + + @JvmStatic + @Parameters(name = "{0}") + fun data(): Collection<TestData> { + val usingMediaValues = setOf(true, false) + val shadeModeValues = setOf(ShadeMode.Single, ShadeMode.Split, ShadeMode.Dual) + val orientationValues = + setOf(Configuration.ORIENTATION_LANDSCAPE, Configuration.ORIENTATION_PORTRAIT) + val screenLayoutLongValues = + setOf(Configuration.SCREENLAYOUT_LONG_YES, Configuration.SCREENLAYOUT_LONG_NO) + val mediaVisibleValues = setOf(true, false) + val mediaLocationsValues = setOf(LOCATION_QS, LOCATION_QQS) + + return usingMediaValues.flatMap { usingMedia -> + shadeModeValues.flatMap { shadeMode -> + orientationValues.flatMap { orientation -> + screenLayoutLongValues.flatMap { screenLayoutLong -> + mediaVisibleValues.flatMap { mediaVisible -> + mediaLocationsValues.map { mediaLocation -> + TestData( + usingMedia, + shadeMode, + orientation, + screenLayoutLong, + mediaVisible, + mediaLocation, + ) + } + } + } + } + } + } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt new file mode 100644 index 000000000000..4ae8589de87b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelTest.kt @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.mainResources +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS +import com.android.systemui.media.controls.ui.controller.MediaLocation +import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager +import com.android.systemui.media.controls.ui.view.MediaHost +import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment +import com.android.systemui.qs.panels.data.repository.QSColumnsRepository +import com.android.systemui.qs.panels.data.repository.qsColumnsRepository +import com.android.systemui.res.R +import com.android.systemui.shade.shared.flag.DualShade +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class QSColumnsViewModelTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + usingMediaInComposeFragment = true + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_infinite_grid_num_columns, + SINGLE_SPLIT_SHADE_COLUMNS, + ) + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_dual_shade_num_columns, + DUAL_SHADE_COLUMNS, + ) + testCase.context.orCreateTestableResources.addOverride( + R.integer.quick_settings_split_shade_num_columns, + SINGLE_SPLIT_SHADE_COLUMNS, + ) + qsColumnsRepository = QSColumnsRepository(mainResources, configurationRepository) + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun mediaLocationNull_singleOrSplit_alwaysSingleShadeColumns() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(null) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + + makeMediaVisible(LOCATION_QQS, visible = true) + makeMediaVisible(LOCATION_QS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + } + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun mediaLocationNull_dualShade_alwaysDualShadeColumns() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(null) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + + makeMediaVisible(LOCATION_QQS, visible = true) + makeMediaVisible(LOCATION_QS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + } + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun mediaLocationQS_dualShade_alwaysDualShadeColumns() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(LOCATION_QS) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + + makeMediaVisible(LOCATION_QS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + } + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun mediaLocationQQS_dualShade_alwaysDualShadeColumns() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + + makeMediaVisible(LOCATION_QQS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(DUAL_SHADE_COLUMNS) + } + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun mediaLocationQS_singleOrSplit_halfColumnsOnCorrectConfigurationAndVisible() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(LOCATION_QS) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + makeMediaVisible(LOCATION_QS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS / 2) + } + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + fun mediaLocationQQS_singleOrSplit_halfColumnsOnCorrectConfigurationAndVisible() = + with(kosmos) { + testScope.runTest { + val underTest = qsColumnsViewModelFactory.create(LOCATION_QQS) + underTest.activateIn(testScope) + + setConfigurationForMediaInRow(mediaInRow = false) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS) + + makeMediaVisible(LOCATION_QQS, visible = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(SINGLE_SPLIT_SHADE_COLUMNS / 2) + } + } + + companion object { + private const val SINGLE_SPLIT_SHADE_COLUMNS = 4 + private const val DUAL_SHADE_COLUMNS = 2 + + private fun Kosmos.makeMediaVisible(@MediaLocation location: Int, visible: Boolean) { + mediaHostStatesManager.updateHostState( + location, + MediaHost.MediaHostStateHolder().apply { this.visible = visible }, + ) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt index ab5a049c91f1..3d1265aec45e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt @@ -24,6 +24,11 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS +import com.android.systemui.media.controls.ui.controller.MediaLocation +import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager +import com.android.systemui.media.controls.ui.view.MediaHost +import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment import com.android.systemui.qs.panels.domain.interactor.qsPreferencesInteractor import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec @@ -67,6 +72,7 @@ class QuickQuickSettingsViewModelTest : SysuiTestCase() { 4, ) fakeConfigurationRepository.onConfigurationChange() + usingMediaInComposeFragment = true } private val underTest = @@ -145,6 +151,33 @@ class QuickQuickSettingsViewModelTest : SysuiTestCase() { } } + @Test + fun mediaVisibleInLandscape_doubleRows_halfColumns() = + with(kosmos) { + testScope.runTest { + setRows(1) + assertThat(underTest.columns).isEqualTo(4) + // All tiles in 4 columns (but we only show the first 3 tiles) + // [1] [2] [3 3] + // [4] [5 5] + // [6 6] [7] [8] + // [9 9] + + runCurrent() + assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(3)) + + makeMediaVisible(LOCATION_QQS, visible = true) + setConfigurationForMediaInRow(mediaInRow = true) + runCurrent() + + assertThat(underTest.columns).isEqualTo(2) + // Tiles in 4 columns + // [1] [2] + // [3 3] + assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(3)) + } + } + private fun Kosmos.setTiles(tiles: List<TileSpec>) { currentTilesInteractor.setTiles(tiles) } @@ -163,5 +196,12 @@ class QuickQuickSettingsViewModelTest : SysuiTestCase() { private companion object { const val PREFIX_SMALL = "small" const val PREFIX_LARGE = "large" + + private fun Kosmos.makeMediaVisible(@MediaLocation location: Int, visible: Boolean) { + mediaHostStatesManager.updateHostState( + location, + MediaHost.MediaHostStateHolder().apply { this.visible = visible }, + ) + } } } 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 0d12483bad0a..53708fd417e1 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 @@ -23,7 +23,6 @@ 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; @@ -160,9 +159,7 @@ public class ScreenRecordTileTest extends SysuiTestCase { mTestableLooper.processAllMessages(); ArgumentCaptor<Runnable> onStartRecordingClicked = ArgumentCaptor.forClass(Runnable.class); - verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags), - eq(mDialogTransitionAnimator), eq(mActivityStarter), - onStartRecordingClicked.capture()); + verify(mController).createScreenRecordDialog(onStartRecordingClicked.capture()); // When starting the recording, we collapse the shade and disable the dialog animation. assertNotNull(onStartRecordingClicked.getValue()); @@ -298,14 +295,13 @@ public class ScreenRecordTileTest extends SysuiTestCase { public void showingDialogPrompt_logsMediaProjectionPermissionRequested() { when(mController.isStarting()).thenReturn(false); when(mController.isRecording()).thenReturn(false); - when(mController.createScreenRecordDialog(any(), any(), any(), any(), any())) + when(mController.createScreenRecordDialog(any())) .thenReturn(mPermissionDialogPrompt); mTile.handleClick(null /* view */); mTestableLooper.processAllMessages(); - verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags), - eq(mDialogTransitionAnimator), eq(mActivityStarter), any()); + verify(mController).createScreenRecordDialog(any()); var onDismissAction = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction.class); verify(mKeyguardDismissUtil).executeWhenUnlocked( onDismissAction.capture(), anyBoolean(), anyBoolean()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt new file mode 100644 index 000000000000..2ac3e081b8f4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.notes.domain + +import android.graphics.drawable.TestStubDrawable +import android.widget.Button +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel +import com.android.systemui.qs.tiles.impl.notes.qsNotesTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import kotlin.test.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotesTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val qsTileConfig = kosmos.qsNotesTileConfig + + private val mapper by lazy { + NotesTileMapper( + context.orCreateTestableResources + .apply { addOverride(R.drawable.ic_qs_notes, TestStubDrawable()) } + .resources, + context.theme, + ) + } + + @Test + fun mappedStateMatchesModel() { + val inputModel = NotesTileModel + + val outputState = mapper.map(qsTileConfig, inputModel) + + val expectedState = createNotesTileState() + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createNotesTileState(): QSTileState = + QSTileState( + Icon.Loaded(context.getDrawable(R.drawable.ic_qs_notes)!!, null), + R.drawable.ic_qs_notes, + context.getString(R.string.quick_settings_notes_label), + QSTileState.ActivationState.INACTIVE, + /* secondaryLabel= */ null, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + context.getString(R.string.quick_settings_notes_label), + /* stateDescription= */ null, + QSTileState.SideViewIcon.Chevron, + QSTileState.EnabledState.ENABLED, + Button::class.qualifiedName, + ) +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt new file mode 100644 index 000000000000..35d6d5adc3f4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractorTest.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.notes.domain.interactor + +import android.os.UserHandle +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotesTileDataInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val testUser = UserHandle.of(1) + private lateinit var underTest: NotesTileDataInteractor + + + @EnableFlags(Flags.FLAG_NOTES_ROLE_QS_TILE) + @Test + fun availability_qsFlagEnabled_notesRoleEnabled_returnTrue() = + testScope.runTest { + underTest = NotesTileDataInteractor(isNoteTaskEnabled = true) + + val availability = underTest.availability(testUser).toCollection(mutableListOf()) + + assertThat(availability).containsExactly(true) + } + + @DisableFlags(Flags.FLAG_NOTES_ROLE_QS_TILE) + @Test + fun availability_qsFlagDisabled_notesRoleEnabled_returnFalse() = + testScope.runTest { + underTest = NotesTileDataInteractor(isNoteTaskEnabled = true) + + val availability = underTest.availability(testUser).toCollection(mutableListOf()) + + assertThat(availability).containsExactly(false) + } + + @EnableFlags(Flags.FLAG_NOTES_ROLE_QS_TILE) + @Test + fun availability_qsFlagEnabled_notesRoleDisabled_returnFalse() = + testScope.runTest { + underTest = NotesTileDataInteractor(isNoteTaskEnabled = false) + + val availability = underTest.availability(testUser).toCollection(mutableListOf()) + + assertThat(availability).containsExactly(false) + } + + @DisableFlags(Flags.FLAG_NOTES_ROLE_QS_TILE) + @Test + fun availability_qsFlagDisabled_notesRoleDisabled_returnFalse() = + testScope.runTest { + underTest = NotesTileDataInteractor(isNoteTaskEnabled = false) + + val availability = underTest.availability(testUser).toCollection(mutableListOf()) + + assertThat(availability).containsExactly(false) + } + + @Test + fun tileData_notEmpty() = runTest { + underTest = NotesTileDataInteractor(isNoteTaskEnabled = true) + val flowValue by + collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))) + + runCurrent() + + assertThat(flowValue).isNotNull() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..54911e612291 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractorTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.notes.domain.interactor + +import android.content.Intent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.notetask.NoteTaskController +import com.android.systemui.notetask.NoteTaskEntryPoint +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotesTileUserActionInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val inputHandler = FakeQSTileIntentUserInputHandler() + private val panelInteractor = mock<PanelInteractor>() + private val noteTaskController = mock<NoteTaskController>() + + private lateinit var underTest: NotesTileUserActionInteractor + + @Before + fun setUp() { + underTest = NotesTileUserActionInteractor(inputHandler, panelInteractor, noteTaskController) + } + + @Test + fun handleClick_launchDefaultNotesApp() = + testScope.runTest { + underTest.handleInput(QSTileInputTestKtx.click(NotesTileModel)) + + verify(noteTaskController).showNoteTask(NoteTaskEntryPoint.QS_NOTES_TILE) + } + + @Test + fun handleLongClick_launchSettings() = + testScope.runTest { + underTest.handleInput(QSTileInputTestKtx.longClick(NotesTileModel)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Intent.ACTION_MANAGE_DEFAULT_APP) + } + } +} 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 899122d4dd45..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 @@ -23,29 +23,27 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable -import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.plugins.ActivityStarter.OnDismissAction -import com.android.systemui.plugins.activityStarter import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.screenrecord.RecordingController import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.ScreenRecordRepositoryImpl import com.android.systemui.statusbar.phone.KeyguardDismissUtil -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.whenever import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock @SmallTest @RunWith(AndroidJUnit4::class) @@ -54,24 +52,11 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { private val testScope = kosmos.testScope private val keyguardInteractor = kosmos.keyguardInteractor private val dialogTransitionAnimator = mock<DialogTransitionAnimator>() - private val featureFlags = kosmos.featureFlagsClassic - private val activityStarter = kosmos.activityStarter private val keyguardDismissUtil = mock<KeyguardDismissUtil>() private val panelInteractor = mock<PanelInteractor>() private val dialog = mock<Dialog>() private val recordingController = - mock<RecordingController> { - whenever( - createScreenRecordDialog( - eq(context), - eq(featureFlags), - eq(dialogTransitionAnimator), - eq(activityStarter), - any() - ) - ) - .thenReturn(dialog) - } + mock<RecordingController> { on { createScreenRecordDialog(any()) } doReturn dialog } private val screenRecordRepository = ScreenRecordRepositoryImpl( @@ -81,7 +66,6 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { private val underTest = ScreenRecordTileUserActionInteractor( - context, testScope.testScheduler, testScope.testScheduler, screenRecordRepository, @@ -91,8 +75,6 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { dialogTransitionAnimator, panelInteractor, mock<MediaProjectionMetricsLogger>(), - featureFlags, - activityStarter, ) @Test @@ -120,22 +102,16 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { underTest.handleInput(QSTileInputTestKtx.click(recordingModel)) val onStartRecordingClickedCaptor = argumentCaptor<Runnable>() verify(recordingController) - .createScreenRecordDialog( - eq(context), - eq(featureFlags), - eq(dialogTransitionAnimator), - eq(activityStarter), - onStartRecordingClickedCaptor.capture() - ) + .createScreenRecordDialog(onStartRecordingClickedCaptor.capture()) val onDismissActionCaptor = argumentCaptor<OnDismissAction>() verify(keyguardDismissUtil) .executeWhenUnlocked(onDismissActionCaptor.capture(), eq(false), eq(true)) - onDismissActionCaptor.value.onDismiss() + onDismissActionCaptor.lastValue.onDismiss() verify(dialog).show() // because the view was null // When starting the recording, we collapse the shade and disable the dialog animation. - onStartRecordingClickedCaptor.value.run() + onStartRecordingClickedCaptor.lastValue.run() verify(dialogTransitionAnimator).disableAllCurrentDialogsExitAnimations() verify(panelInteractor).collapsePanels() } @@ -145,9 +121,9 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { */ @Test fun handleClickFromView_whenDoingNothing_whenKeyguardNotShowing_showDialogFromView() = runTest { - val expandable = mock<Expandable>() val controller = mock<DialogTransitionAnimator.Controller>() - whenever(expandable.dialogTransitionController(any())).thenReturn(controller) + val expandable = + mock<Expandable> { on { dialogTransitionController(any()) } doReturn controller } kosmos.fakeKeyguardRepository.setKeyguardShowing(false) @@ -158,18 +134,12 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { ) val onStartRecordingClickedCaptor = argumentCaptor<Runnable>() verify(recordingController) - .createScreenRecordDialog( - eq(context), - eq(featureFlags), - eq(dialogTransitionAnimator), - eq(activityStarter), - onStartRecordingClickedCaptor.capture() - ) + .createScreenRecordDialog(onStartRecordingClickedCaptor.capture()) val onDismissActionCaptor = argumentCaptor<OnDismissAction>() verify(keyguardDismissUtil) .executeWhenUnlocked(onDismissActionCaptor.capture(), eq(false), eq(true)) - onDismissActionCaptor.value.onDismiss() + onDismissActionCaptor.lastValue.onDismiss() verify(dialogTransitionAnimator).show(eq(dialog), eq(controller), eq(true)) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt index c3a777cc3e44..939644594d31 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt @@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -89,12 +88,8 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() { underTest.activateIn(this) assertThat( - (actions?.get( - Swipe( - direction = SwipeDirection.Down, - fromSource = SceneContainerEdge.TopLeft, - ) - ) as? UserActionResult.ReplaceByOverlay) + (actions?.get(Swipe.Down(fromSource = SceneContainerEdge.TopLeft)) + as? UserActionResult.ReplaceByOverlay) ?.overlay ) .isEqualTo(Overlays.NotificationsShade) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt index f32894dfd383..24f6b6d8566b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt @@ -31,6 +31,7 @@ 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 import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.sceneContainerStartable import com.android.systemui.scene.shared.model.Overlays @@ -55,7 +56,10 @@ import org.junit.runner.RunWith @EnableFlags(DualShade.FLAG_NAME) class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = + testKosmos().apply { + usingMediaInComposeFragment = false // This is not for the compose fragment + } private val testScope = kosmos.testScope private val sceneInteractor = kosmos.sceneInteractor diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt index 62b6391ca54c..d5fbe49a153b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt @@ -22,7 +22,6 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -98,9 +97,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { .isEqualTo( mapOf( Back to UserActionResult(Scenes.Shade), - Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), - Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to - UserActionResult(SceneFamilies.Home), + Swipe.Up to UserActionResult(Scenes.Shade), + Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Gone) @@ -125,9 +123,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { .isEqualTo( mapOf( Back to UserActionResult(Scenes.Lockscreen), - Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Lockscreen), - Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to - UserActionResult(SceneFamilies.Home), + Swipe.Up to UserActionResult(Scenes.Lockscreen), + Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Lockscreen) @@ -154,9 +151,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { .isEqualTo( mapOf( Back to UserActionResult(Scenes.Shade), - Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), - Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to - UserActionResult(SceneFamilies.Home), + Swipe.Up to UserActionResult(Scenes.Shade), + Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Gone) @@ -178,9 +174,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { .isEqualTo( mapOf( Back to UserActionResult(Scenes.Shade), - Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), - Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to - UserActionResult(SceneFamilies.Home), + Swipe.Up to UserActionResult(Scenes.Shade), + Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Lockscreen) @@ -214,9 +209,8 @@ class QuickSettingsUserActionsViewModelTest : SysuiTestCase() { .isEqualTo( mapOf( Back to UserActionResult(Scenes.Shade), - Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade), - Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up) to - UserActionResult(SceneFamilies.Home), + Swipe.Up to UserActionResult(Scenes.Shade), + Swipe.Up(fromSource = Edge.Bottom) to UserActionResult(SceneFamilies.Home), ) ) assertThat(homeScene).isEqualTo(Scenes.Gone) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 2c8f7cf47723..55f88cc5b7a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -2119,40 +2119,6 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test - fun switchToGone_whenSurfaceBehindLockscreenVisibleMidTransition() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) - val transitionStateFlow = - prepareState(authenticationMethod = AuthenticationMethodModel.None) - underTest.start() - assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - // Swipe to Gone, more than halfway - transitionStateFlow.value = - ObservableTransitionState.Transition( - fromScene = Scenes.Lockscreen, - toScene = Scenes.Gone, - currentScene = flowOf(Scenes.Gone), - progress = flowOf(0.51f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(true), - ) - runCurrent() - // Lift finger - transitionStateFlow.value = - ObservableTransitionState.Transition( - fromScene = Scenes.Lockscreen, - toScene = Scenes.Gone, - currentScene = flowOf(Scenes.Gone), - progress = flowOf(0.51f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), - ) - runCurrent() - - assertThat(currentScene).isEqualTo(Scenes.Gone) - } - - @Test fun switchToGone_extendUnlock() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt index 47fae9f0629f..bb2e9417ecef 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt @@ -23,7 +23,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase @@ -71,8 +70,7 @@ class GoneUserActionsViewModelTest : SysuiTestCase() { shadeRepository.setShadeLayoutWide(true) runCurrent() - assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey) - .isEqualTo(ToSplitShade) + assertThat(userActions?.get(Swipe.Down)?.transitionKey).isEqualTo(ToSplitShade) } @Test @@ -83,7 +81,7 @@ class GoneUserActionsViewModelTest : SysuiTestCase() { shadeRepository.setShadeLayoutWide(false) runCurrent() - assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull() + assertThat(userActions?.get(Swipe.Down)?.transitionKey).isNull() } @Test @@ -94,7 +92,7 @@ class GoneUserActionsViewModelTest : SysuiTestCase() { shadeRepository.setShadeLayoutWide(true) runCurrent() - assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull() + assertThat(userActions?.get(Swipe.Down)?.transitionKey).isNull() } @Test @@ -132,6 +130,6 @@ class GoneUserActionsViewModelTest : SysuiTestCase() { } private fun swipeDownFromTopWithTwoFingers(): UserAction { - return Swipe(direction = SwipeDirection.Down, pointerCount = 2, fromSource = Edge.Top) + return Swipe.Down(pointerCount = 2, fromSource = Edge.Top) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt index 972afb58352d..d5f57da40e5a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt @@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.SysuiTestCase @@ -77,7 +76,7 @@ class UserActionsViewModelTest : SysuiTestCase() { val expected1 = mapOf( Back to UserActionResult(toScene = Scenes.Gone), - Swipe(SwipeDirection.Up) to UserActionResult(toScene = Scenes.Shade) + Swipe.Up to UserActionResult(toScene = Scenes.Shade), ) underTest.upstream.value = expected1 runCurrent() @@ -86,7 +85,7 @@ class UserActionsViewModelTest : SysuiTestCase() { val expected2 = mapOf( Back to UserActionResult(toScene = Scenes.Lockscreen), - Swipe(SwipeDirection.Down) to UserActionResult(toScene = Scenes.Shade) + Swipe.Down to UserActionResult(toScene = Scenes.Shade), ) underTest.upstream.value = expected2 runCurrent() @@ -104,7 +103,7 @@ class UserActionsViewModelTest : SysuiTestCase() { val expected = mapOf( Back to UserActionResult(toScene = Scenes.Lockscreen), - Swipe(SwipeDirection.Down) to UserActionResult(toScene = Scenes.Shade) + Swipe.Down to UserActionResult(toScene = Scenes.Shade), ) underTest.upstream.value = expected runCurrent() @@ -120,7 +119,7 @@ class UserActionsViewModelTest : SysuiTestCase() { val upstream = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap()) override suspend fun hydrateActions( - setActions: (Map<UserAction, UserActionResult>) -> Unit, + setActions: (Map<UserAction, UserActionResult>) -> Unit ) { upstream.collect { setActions(it) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt index fcb366bfd8ee..bbfc66a8d8e5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt @@ -92,10 +92,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pin ) - assertThat( - (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene) - ?.toScene - ) + assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Lockscreen) } @@ -110,10 +107,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() { ) setDeviceEntered(true) - assertThat( - (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene) - ?.toScene - ) + assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Gone) } @@ -128,10 +122,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() { ) kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false) - assertThat( - (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene) - ?.toScene - ) + assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Gone) } @@ -147,10 +138,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() { ) sceneInteractor.changeScene(Scenes.Lockscreen, "reason") - assertThat( - (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene) - ?.toScene - ) + assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Lockscreen) } @@ -167,10 +155,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() { runCurrent() sceneInteractor.changeScene(Scenes.Gone, "reason") - assertThat( - (actions?.get(Swipe(SwipeDirection.Up)) as? UserActionResult.ChangeScene) - ?.toScene - ) + assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Gone) } @@ -182,8 +167,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() { shadeRepository.setShadeLayoutWide(true) runCurrent() - assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey) - .isEqualTo(ToSplitShade) + assertThat(actions?.get(Swipe.Up)?.transitionKey).isEqualTo(ToSplitShade) } @Test @@ -193,7 +177,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() { shadeRepository.setShadeLayoutWide(false) runCurrent() - assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull() + assertThat(actions?.get(Swipe.Up)?.transitionKey).isNull() } @Test @@ -202,10 +186,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, true) kosmos.shadeStartable.start() val actions by collectLastValue(underTest.actions) - assertThat( - (actions?.get(Swipe(SwipeDirection.Down)) as? UserActionResult.ChangeScene) - ?.toScene - ) + assertThat((actions?.get(Swipe.Down) as? UserActionResult.ChangeScene)?.toScene) .isNull() } @@ -215,10 +196,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() { overrideResource(R.bool.config_use_split_notification_shade, false) kosmos.shadeStartable.start() val actions by collectLastValue(underTest.actions) - assertThat( - (actions?.get(Swipe(SwipeDirection.Down)) as? UserActionResult.ChangeScene) - ?.toScene - ) + assertThat((actions?.get(Swipe.Down) as? UserActionResult.ChangeScene)?.toScene) .isEqualTo(Scenes.QuickSettings) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt index d6b3b919913f..72a2ce50ed9c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt @@ -85,7 +85,6 @@ class OperatorNameViewControllerTest : SysuiTestCase() { underTest = OperatorNameViewController.Factory( - darkIconDispatcher, tunerService, telephonyManager, keyguardUpdateMonitor, @@ -94,7 +93,7 @@ class OperatorNameViewControllerTest : SysuiTestCase() { subscriptionManagerProxy, javaAdapter, ) - .create(view) + .create(view, darkIconDispatcher) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index 60a185537b0d..fb7252b24295 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static android.app.Notification.CATEGORY_CALL; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -197,6 +199,19 @@ public class StatusBarIconViewTest extends SysuiTestCase { } @Test + public void testContentDescForNotification_noNotifContent() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(0) + .setContentTitle("hello") + .setCategory(CATEGORY_CALL) + .build(); + assertThat(NotificationContentDescription.contentDescForNotification(mContext, n) + .toString()).startsWith("com.android.systemui.tests notification"); + assertThat(NotificationContentDescription.contentDescForNotification(mContext, n) + .toString()).doesNotContain("hello"); + } + + @Test @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS}) public void setIcon_withPreloaded_usesPreloaded() { Icon mockIcon = mock(Icon.class); 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 32f4164d509f..1b4132910555 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 @@ -97,7 +97,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.IconOnly::class.java) assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon)) } @@ -168,8 +168,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { companion object { fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) { - assertThat(latest) - .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) assertThat((latest as OngoingActivityChipModel.Shown).icon) .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon)) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt index 5be5fb4aa9ba..c06da4bc5080 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt @@ -23,9 +23,11 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.data.repository.fakeLightBarControllerStore import com.android.systemui.statusbar.data.repository.fakePrivacyDotWindowControllerStore import com.android.systemui.testKosmos import com.google.common.truth.Expect +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -48,6 +50,7 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() { private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore + private val fakeLightBarStore = kosmos.fakeLightBarControllerStore // Lazy, so that @EnableFlags is set before initializer is instantiated. private val underTest by lazy { kosmos.multiDisplayStatusBarStarter } @@ -95,6 +98,31 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() { } @Test + fun start_doesNotStartLightBarControllerForCurrentDisplays() = + testScope.runTest { + fakeDisplayRepository.addDisplay(displayId = 1) + fakeDisplayRepository.addDisplay(displayId = 2) + + underTest.start() + runCurrent() + + verify(fakeLightBarStore.forDisplay(displayId = 1), never()).start() + verify(fakeLightBarStore.forDisplay(displayId = 2), never()).start() + } + + @Test + fun start_createsLightBarControllerForCurrentDisplays() = + testScope.runTest { + fakeDisplayRepository.addDisplay(displayId = 1) + fakeDisplayRepository.addDisplay(displayId = 2) + + underTest.start() + runCurrent() + + assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, 2) + } + + @Test fun start_doesNotStartPrivacyDotForDefaultDisplay() = testScope.runTest { fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY) @@ -145,6 +173,30 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() { } @Test + fun displayAdded_lightBarForNewDisplayIsCreated() = + testScope.runTest { + underTest.start() + runCurrent() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3) + } + + @Test + fun displayAdded_lightBarForNewDisplayIsNotStarted() = + testScope.runTest { + underTest.start() + runCurrent() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start() + } + + @Test fun displayAddedDuringStart_initializerForNewDisplayIsStarted() = testScope.runTest { underTest.start() @@ -178,4 +230,26 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() { verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start() } + + @Test + fun displayAddedDuringStart_lightBarForNewDisplayIsCreated() = + testScope.runTest { + underTest.start() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3) + } + + @Test + fun displayAddedDuringStart_lightBarForNewDisplayIsNotStarted() = + testScope.runTest { + underTest.start() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt new file mode 100644 index 000000000000..a2c3c66f4448 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt @@ -0,0 +1,73 @@ +/* + * 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.data.repository + +import android.platform.test.annotations.EnableFlags +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.testKosmos +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) +class MultiDisplayDarkIconDispatcherStoreTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val testScope = kosmos.testScope + private val fakeDisplayRepository = kosmos.displayRepository + + // Lazy so that @EnableFlags has time to run before underTest is instantiated. + private val underTest by lazy { kosmos.multiDisplayDarkIconDispatcherStore } + + @Before + fun start() { + underTest.start() + } + + @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) } + + @Test + fun beforeDisplayRemoved_doesNotStopInstances() = + testScope.runTest { + val instance = underTest.forDisplay(DEFAULT_DISPLAY) + + verify(instance, never()).stop() + } + + @Test + fun displayRemoved_stopsInstance() = + testScope.runTest { + val instance = underTest.forDisplay(DEFAULT_DISPLAY) + + fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY) + + verify(instance).stop() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt index 12473cb46793..896f940f8a60 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt @@ -34,31 +34,9 @@ class NotificationContentDescriptionTest : SysuiTestCase() { private val TICKER = "this is a ticker" @Test - fun notificationWithAllDifferentFields_descriptionIsTitle() { + fun notificationWithAllDifferentFields_descriptionIsAppName() { val n = createNotification(TITLE, TEXT, TICKER) val description = contentDescForNotification(context, n) - assertThat(description).isEqualTo(createDescriptionText(n, TITLE)) - } - - @Test - fun notificationWithAllDifferentFields_titleMatchesAppName_descriptionIsText() { - val n = createNotification(getTestAppName(), TEXT, TICKER) - val description = contentDescForNotification(context, n) - assertThat(description).isEqualTo(createDescriptionText(n, TEXT)) - } - - @Test - fun notificationWithAllDifferentFields_titleMatchesAppNameNoText_descriptionIsTicker() { - val n = createNotification(getTestAppName(), null, TICKER) - val description = contentDescForNotification(context, n) - assertThat(description).isEqualTo(createDescriptionText(n, TICKER)) - } - - @Test - fun notificationWithAllDifferentFields_titleMatchesAppNameNoTextNoTicker_descriptionEmpty() { - val appName = getTestAppName() - val n = createNotification(appName, null, null) - val description = contentDescForNotification(context, n) assertThat(description).isEqualTo(createDescriptionText(n, "")) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index 9367a93a2890..46c360aecd48 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -110,14 +110,10 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati lastSleepReason = WakeSleepReason.OTHER, ) keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, - ) + TransitionStep(transitionState = TransitionState.STARTED) ) keyguardRepository.setDozeTransitionModel( - DozeTransitionModel( - to = DozeStateModel.DOZE_AOD, - ) + DozeTransitionModel(to = DozeStateModel.DOZE_AOD) ) val animationsEnabled by collectLastValue(underTest.animationsEnabled) runCurrent() @@ -133,14 +129,10 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati lastSleepReason = WakeSleepReason.OTHER, ) keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, - ) + TransitionStep(transitionState = TransitionState.STARTED) ) keyguardRepository.setDozeTransitionModel( - DozeTransitionModel( - to = DozeStateModel.DOZE_PULSING, - ) + DozeTransitionModel(to = DozeStateModel.DOZE_PULSING) ) val animationsEnabled by collectLastValue(underTest.animationsEnabled) runCurrent() @@ -201,9 +193,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati lastSleepReason = WakeSleepReason.OTHER, ) keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, - ) + TransitionStep(transitionState = TransitionState.STARTED) ) val animationsEnabled by collectLastValue(underTest.animationsEnabled) runCurrent() @@ -216,9 +206,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati val animationsEnabled by collectLastValue(underTest.animationsEnabled) keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, - ) + TransitionStep(transitionState = TransitionState.STARTED) ) keyguardRepository.setKeyguardShowing(true) runCurrent() @@ -234,13 +222,10 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati @Test fun iconColors_testsDarkBounds() = testScope.runTest { - darkIconRepository.darkState.value = - SysuiDarkIconDispatcher.DarkChange( - emptyList(), - 0f, - 0xAABBCC, - ) - val iconColorsLookup by collectLastValue(underTest.iconColors) + val displayId = 123 + darkIconRepository.darkState(displayId).value = + SysuiDarkIconDispatcher.DarkChange(emptyList(), 0f, 0xAABBCC) + val iconColorsLookup by collectLastValue(underTest.iconColors(displayId)) assertThat(iconColorsLookup).isNotNull() val iconColors = iconColorsLookup?.iconColors(Rect()) @@ -257,13 +242,10 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati @Test fun iconColors_staticDrawableColor_notInDarkTintArea() = testScope.runTest { - darkIconRepository.darkState.value = - SysuiDarkIconDispatcher.DarkChange( - listOf(Rect(0, 0, 5, 5)), - 0f, - 0xAABBCC, - ) - val iconColorsLookup by collectLastValue(underTest.iconColors) + val displayId = 321 + darkIconRepository.darkState(displayId).value = + SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC) + val iconColorsLookup by collectLastValue(underTest.iconColors(displayId)) val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4)) val staticDrawableColor = iconColors?.staticDrawableColor(Rect(6, 6, 7, 7)) assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT) @@ -272,13 +254,10 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati @Test fun iconColors_notInDarkTintArea() = testScope.runTest { - darkIconRepository.darkState.value = - SysuiDarkIconDispatcher.DarkChange( - listOf(Rect(0, 0, 5, 5)), - 0f, - 0xAABBCC, - ) - val iconColorsLookup by collectLastValue(underTest.iconColors) + val displayId = 987 + darkIconRepository.darkState(displayId).value = + SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC) + val iconColorsLookup by collectLastValue(underTest.iconColors(displayId)) val iconColors = iconColorsLookup?.iconColors(Rect(6, 6, 7, 7)) assertThat(iconColors).isNull() } @@ -295,7 +274,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati activeNotificationModel( key = "notif1", groupKey = "group", - statusBarIcon = icon + statusBarIcon = icon, ) ) } @@ -322,7 +301,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati activeNotificationModel( key = "notif1", groupKey = "group", - statusBarIcon = icon + statusBarIcon = icon, ) ) } @@ -354,7 +333,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati activeNotificationModel( key = "notif1", groupKey = "group", - statusBarIcon = icon + statusBarIcon = icon, ) ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt new file mode 100644 index 000000000000..a1b63b159277 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt @@ -0,0 +1,648 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.row + +import android.R +import android.app.AppOpsManager +import android.app.INotificationManager +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Intent +import android.content.pm.LauncherApps +import android.content.pm.PackageManager +import android.content.pm.ShortcutManager +import android.graphics.Color +import android.os.Binder +import android.os.UserManager +import android.os.fakeExecutorHandler +import android.platform.test.flag.junit.FlagsParameterization +import android.provider.Settings +import android.service.notification.NotificationListenerService +import android.testing.TestableLooper.RunWithLooper +import android.util.ArraySet +import android.view.View +import android.view.accessibility.AccessibilityManager +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.flags.andSceneContainer +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.people.widget.PeopleSpaceWidgetManager +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.settings.UserContextProvider +import com.android.systemui.shade.ShadeController +import com.android.systemui.statusbar.NotificationEntryHelper +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.AssistantFeedbackController +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.testKosmos +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.wmshell.BubblesManager +import java.util.Optional +import kotlin.test.assertNotNull +import kotlin.test.fail +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.invocation.InvocationOnMock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.doNothing +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 +import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +/** Tests for [NotificationGutsManager]. */ +@SmallTest +@RunWith(ParameterizedAndroidJunit4::class) +@RunWithLooper +class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase() { + private val testNotificationChannel = + NotificationChannel( + TEST_CHANNEL_ID, + TEST_CHANNEL_ID, + NotificationManager.IMPORTANCE_DEFAULT, + ) + + private val kosmos = testKosmos() + + private val testScope = kosmos.testScope + private val javaAdapter = JavaAdapter(testScope.backgroundScope) + private val executor = kosmos.fakeExecutor + private val handler = kosmos.fakeExecutorHandler + private lateinit var helper: NotificationTestHelper + private lateinit var gutsManager: NotificationGutsManager + + @get:Rule val rule: MockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback + @Mock private lateinit var presenter: NotificationPresenter + @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter + @Mock private lateinit var notificationListContainer: NotificationListContainer + @Mock + private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener + @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController + @Mock private lateinit var accessibilityManager: AccessibilityManager + @Mock private lateinit var highPriorityProvider: HighPriorityProvider + @Mock private lateinit var iNotificationManager: INotificationManager + @Mock private lateinit var barService: IStatusBarService + @Mock private lateinit var launcherApps: LauncherApps + @Mock private lateinit var shortcutManager: ShortcutManager + @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController + @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier + @Mock private lateinit var contextTracker: UserContextProvider + @Mock private lateinit var bubblesManager: BubblesManager + @Mock private lateinit var shadeController: ShadeController + @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager + @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController + @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var headsUpManager: HeadsUpManager + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var userManager: UserManager + + private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor + + companion object { + private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId" + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setUp() { + allowTestableLooperAsMainThread() + helper = NotificationTestHelper(mContext, mDependency) + whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(false) + + windowRootViewVisibilityInteractor = + WindowRootViewVisibilityInteractor( + testScope.backgroundScope, + WindowRootViewVisibilityRepository(barService, executor), + FakeKeyguardRepository(), + headsUpManager, + create().powerInteractor, + kosmos.activeNotificationsInteractor, + ) { + kosmos.sceneInteractor + } + + gutsManager = + NotificationGutsManager( + mContext, + handler, + handler, + javaAdapter, + accessibilityManager, + highPriorityProvider, + iNotificationManager, + userManager, + peopleSpaceWidgetManager, + launcherApps, + shortcutManager, + channelEditorDialogController, + contextTracker, + assistantFeedbackController, + Optional.of(bubblesManager), + UiEventLoggerFake(), + onUserInteractionCallback, + shadeController, + windowRootViewVisibilityInteractor, + notificationLockscreenUserManager, + statusBarStateController, + barService, + deviceProvisionedController, + metricsLogger, + headsUpManager, + activityStarter, + ) + gutsManager.setUpWithPresenter( + presenter, + notificationListContainer, + onSettingsClickListener, + ) + gutsManager.setNotificationActivityStarter(notificationActivityStarter) + gutsManager.start() + } + + @Test + fun testOpenAndCloseGuts() { + val guts = spy(NotificationGuts(mContext)) + whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock -> + handler.post(((invocation.arguments[0] as Runnable))) + null + } + + // Test doesn't support animation since the guts view is not attached. + doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any()) + + val realRow = createTestNotificationRow() + val menuItem = createTestMenuItem(realRow) + + val row = spy(realRow) + whenever(row.windowToken).thenReturn(Binder()) + whenever(row.guts).thenReturn(guts) + + assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) + assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong()) + executor.runAllReady() + verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) + verify(headsUpManager).setGutsShown(realRow.entry, true) + + assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong()) + gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false) + + verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()) + verify(row, times(1)).setGutsView(any<MenuItem>()) + executor.runAllReady() + verify(headsUpManager).setGutsShown(realRow.entry, false) + } + + @Test + fun testLockscreenShadeVisible_visible_gutsNotClosed() = + testScope.runTest { + // First, start out lockscreen or shade as not visible + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false) + runCurrent() + + val guts: NotificationGuts = mock() + gutsManager.exposedGuts = guts + + // WHEN the lockscreen or shade becomes visible + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + runCurrent() + + // THEN the guts are not closed + verify(guts, never()).removeCallbacks(any()) + verify(guts, never()) + .closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()) + } + + @Test + @DisableSceneContainer + fun testLockscreenShadeVisible_notVisible_gutsClosed() = + testScope.runTest { + // First, start out lockscreen or shade as visible + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + runCurrent() + + val guts: NotificationGuts = mock() + gutsManager.exposedGuts = guts + + // WHEN the lockscreen or shade is no longer visible + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false) + runCurrent() + + // THEN the guts are closed + verify(guts).removeCallbacks(null) + verify(guts) + .closeControls( + /* leavebehinds = */ eq(true), + /* controls = */ eq(true), + /* x = */ anyInt(), + /* y = */ anyInt(), + /* force = */ eq(true), + ) + } + + @Test + @EnableSceneContainer + fun testShadeVisible_notVisible_gutsClosed() = + testScope.runTest { + // First, start with shade as visible + kosmos.setSceneTransition(Idle(Scenes.Shade)) + runCurrent() + + val guts: NotificationGuts = mock() + gutsManager.exposedGuts = guts + + // WHEN the shade is no longer visible + kosmos.setSceneTransition(Idle(Scenes.Gone)) + runCurrent() + + // THEN the guts are closed + verify(guts).removeCallbacks(null) + verify(guts) + .closeControls( + /* leavebehinds = */ eq(true), + /* controls = */ eq(true), + /* x = */ anyInt(), + /* y = */ anyInt(), + /* force = */ eq(true), + ) + } + + @Test + @DisableSceneContainer + fun testLockscreenShadeVisible_notVisible_listContainerReset() = + testScope.runTest { + // First, start out lockscreen or shade as visible + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true) + runCurrent() + clearInvocations(notificationListContainer) + + // WHEN the lockscreen or shade is no longer visible + windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false) + runCurrent() + + // THEN the list container is reset + verify(notificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean()) + } + + @Test + @EnableSceneContainer + fun testShadeVisible_notVisible_listContainerReset() = + testScope.runTest { + // First, start with shade as visible + kosmos.setSceneTransition(Idle(Scenes.Shade)) + runCurrent() + clearInvocations(notificationListContainer) + + // WHEN the shade is no longer visible + kosmos.setSceneTransition(Idle(Scenes.Gone)) + runCurrent() + + // THEN the list container is reset + verify(notificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean()) + } + + @Test + fun testChangeDensityOrFontScale() { + val guts = spy(NotificationGuts(mContext)) + whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock -> + handler.post(((invocation.arguments[0] as Runnable))) + null + } + + // Test doesn't support animation since the guts view is not attached. + doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) + + val realRow = createTestNotificationRow() + val menuItem = createTestMenuItem(realRow) + + val row = spy(realRow) + + whenever(row.windowToken).thenReturn(Binder()) + whenever(row.guts).thenReturn(guts) + doNothing().whenever(row).ensureGutsInflated() + + val realEntry = realRow.entry + val entry = spy(realEntry) + + whenever(entry.row).thenReturn(row) + whenever(entry.guts).thenReturn(guts) + + assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) + executor.runAllReady() + verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) + + // called once by mGutsManager.bindGuts() in mGutsManager.openGuts() + verify(row).setGutsView(any<MenuItem>()) + + row.onDensityOrFontScaleChanged() + gutsManager.onDensityOrFontScaleChanged(entry) + + executor.runAllReady() + + gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false) + + verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()) + + // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged() + verify(row, times(2)).setGutsView(any<MenuItem>()) + } + + @Test + fun testAppOpsSettingsIntent_camera() { + val row = createTestNotificationRow() + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_CAMERA) + gutsManager.startAppOpsSettingsActivity("", 0, ops, row) + val captor = argumentCaptor<Intent>() + verify(notificationActivityStarter, times(1)) + .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row)) + assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action) + } + + @Test + fun testAppOpsSettingsIntent_mic() { + val row = createTestNotificationRow() + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_RECORD_AUDIO) + gutsManager.startAppOpsSettingsActivity("", 0, ops, row) + val captor = argumentCaptor<Intent>() + verify(notificationActivityStarter, times(1)) + .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row)) + assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action) + } + + @Test + fun testAppOpsSettingsIntent_camera_mic() { + val row = createTestNotificationRow() + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_CAMERA) + ops.add(AppOpsManager.OP_RECORD_AUDIO) + gutsManager.startAppOpsSettingsActivity("", 0, ops, row) + val captor = argumentCaptor<Intent>() + verify(notificationActivityStarter, times(1)) + .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row)) + assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action) + } + + @Test + fun testAppOpsSettingsIntent_overlay() { + val row = createTestNotificationRow() + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + gutsManager.startAppOpsSettingsActivity("", 0, ops, row) + val captor = argumentCaptor<Intent>() + verify(notificationActivityStarter, times(1)) + .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row)) + assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.lastValue.action) + } + + @Test + fun testAppOpsSettingsIntent_camera_mic_overlay() { + val row = createTestNotificationRow() + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_CAMERA) + ops.add(AppOpsManager.OP_RECORD_AUDIO) + ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + gutsManager.startAppOpsSettingsActivity("", 0, ops, row) + val captor = argumentCaptor<Intent>() + verify(notificationActivityStarter, times(1)) + .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row)) + assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action) + } + + @Test + fun testAppOpsSettingsIntent_camera_overlay() { + val row = createTestNotificationRow() + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_CAMERA) + ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + gutsManager.startAppOpsSettingsActivity("", 0, ops, row) + val captor = argumentCaptor<Intent>() + verify(notificationActivityStarter, times(1)) + .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row)) + assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action) + } + + @Test + fun testAppOpsSettingsIntent_mic_overlay() { + val row = createTestNotificationRow() + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_RECORD_AUDIO) + ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + gutsManager.startAppOpsSettingsActivity("", 0, ops, row) + val captor = argumentCaptor<Intent>() + verify(notificationActivityStarter, times(1)) + .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row)) + assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action) + } + + @Test + @Throws(Exception::class) + fun testInitializeNotificationInfoView_highPriority() { + val notificationInfoView: NotificationInfo = mock() + val row = spy(helper.createRow()) + val entry = row.entry + NotificationEntryHelper.modifyRanking(entry) + .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) + .setImportance(NotificationManager.IMPORTANCE_HIGH) + .build() + + whenever(row.isNonblockable).thenReturn(false) + whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true) + val statusBarNotification = entry.sbn + gutsManager.initializeNotificationInfo(row, notificationInfoView) + + verify(notificationInfoView) + .bindNotification( + any<PackageManager>(), + any<INotificationManager>(), + eq(onUserInteractionCallback), + eq(channelEditorDialogController), + eq(statusBarNotification.packageName), + any<NotificationChannel>(), + eq(entry), + any<NotificationInfo.OnSettingsClickListener>(), + any<NotificationInfo.OnAppSettingsClickListener>(), + any<UiEventLogger>(), + /* isDeviceProvisioned = */ eq(false), + /* isNonblockable = */ eq(false), + /* wasShownHighPriority = */ eq(true), + eq(assistantFeedbackController), + eq(metricsLogger), + ) + } + + @Test + @Throws(Exception::class) + fun testInitializeNotificationInfoView_PassesAlongProvisionedState() { + val notificationInfoView: NotificationInfo = mock() + val row = spy(helper.createRow()) + NotificationEntryHelper.modifyRanking(row.entry) + .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) + .build() + whenever(row.isNonblockable).thenReturn(false) + val statusBarNotification = row.entry.sbn + val entry = row.entry + + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + + gutsManager.initializeNotificationInfo(row, notificationInfoView) + + verify(notificationInfoView) + .bindNotification( + any<PackageManager>(), + any<INotificationManager>(), + eq(onUserInteractionCallback), + eq(channelEditorDialogController), + eq(statusBarNotification.packageName), + any<NotificationChannel>(), + eq(entry), + any<NotificationInfo.OnSettingsClickListener>(), + any<NotificationInfo.OnAppSettingsClickListener>(), + any<UiEventLogger>(), + /* isDeviceProvisioned = */ eq(true), + /* isNonblockable = */ eq(false), + /* wasShownHighPriority = */ eq(false), + eq(assistantFeedbackController), + eq(metricsLogger), + ) + } + + @Test + @Throws(Exception::class) + fun testInitializeNotificationInfoView_withInitialAction() { + val notificationInfoView: NotificationInfo = mock() + val row = spy(helper.createRow()) + NotificationEntryHelper.modifyRanking(row.entry) + .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE) + .build() + whenever(row.isNonblockable).thenReturn(false) + val statusBarNotification = row.entry.sbn + val entry = row.entry + + gutsManager.initializeNotificationInfo(row, notificationInfoView) + + verify(notificationInfoView) + .bindNotification( + any<PackageManager>(), + any<INotificationManager>(), + eq(onUserInteractionCallback), + eq(channelEditorDialogController), + eq(statusBarNotification.packageName), + any<NotificationChannel>(), + eq(entry), + any<NotificationInfo.OnSettingsClickListener>(), + any<NotificationInfo.OnAppSettingsClickListener>(), + any<UiEventLogger>(), + /* isDeviceProvisioned = */ eq(false), + /* isNonblockable = */ eq(false), + /* wasShownHighPriority = */ eq(false), + eq(assistantFeedbackController), + eq(metricsLogger), + ) + } + + private fun createTestNotificationRow(): ExpandableNotificationRow { + val nb = + Notification.Builder(mContext, testNotificationChannel.id) + .setContentTitle("foo") + .setColorized(true) + .setColor(Color.RED) + .setFlag(Notification.FLAG_CAN_COLORIZE, true) + .setSmallIcon(R.drawable.sym_def_app_icon) + + try { + val row = helper.createRow(nb.build()) + NotificationEntryHelper.modifyRanking(row.entry) + .setChannel(testNotificationChannel) + .build() + return row + } catch (e: Exception) { + fail() + } + } + + private fun createTestMenuItem( + row: ExpandableNotificationRow + ): NotificationMenuRowPlugin.MenuItem { + val menuRow: NotificationMenuRowPlugin = + NotificationMenuRow(mContext, peopleNotificationIdentifier) + menuRow.createMenu(row, row.entry.sbn) + + val menuItem = menuRow.getLongpressMenuItem(mContext) + assertNotNull(menuItem) + return menuItem + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt index 11dd587a04ad..cae990769444 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt @@ -31,10 +31,13 @@ import android.widget.LinearLayout import androidx.annotation.ColorInt import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R +import com.android.systemui.statusbar.data.repository.statusBarConfigurationControllerStore +import com.android.systemui.statusbar.data.repository.sysUiDarkIconDispatcherStore import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -50,16 +53,18 @@ import org.mockito.Mockito.verify @SmallTest class StatusOverlayHoverListenerTest : SysuiTestCase() { + private val kosmos = testKosmos() private val viewOverlay = mock<ViewGroupOverlay>() private val overlayCaptor = argumentCaptor<Drawable>() - private val darkDispatcher = mock<SysuiDarkIconDispatcher>() private val darkChange: MutableStateFlow<DarkChange> = MutableStateFlow(DarkChange.EMPTY) + private val darkDispatcher = kosmos.sysUiDarkIconDispatcherStore.forDisplay(context.displayId) private val factory = StatusOverlayHoverListenerFactory( context.resources, FakeConfigurationController(), - darkDispatcher + kosmos.sysUiDarkIconDispatcherStore, + kosmos.statusBarConfigurationControllerStore, ) private val view = TestableStatusContainer(context, viewOverlay) @@ -186,7 +191,7 @@ class StatusOverlayHoverListenerTest : SysuiTestCase() { /* action= */ action, /* x= */ 0f, /* y= */ 0f, - /* metaState= */ 0 + /* metaState= */ 0, ) } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java index 403c7c500426..43185fd08613 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java @@ -36,6 +36,9 @@ import java.util.Collection; public interface DarkIconDispatcher { int VERSION = 2; + /** Called when work should stop and resources should be cleaned up. */ + default void stop() {} + /** * Sets the dark area so {@link #applyDark} only affects the icons in the specified area. * diff --git a/packages/SystemUI/res/drawable/ic_qs_notes.xml b/packages/SystemUI/res/drawable/ic_qs_notes.xml new file mode 100644 index 000000000000..6c1d2e49369c --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_notes.xml @@ -0,0 +1,23 @@ +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the Lice/packages/SystemUI/res/drawable/ic_qs_notes.xmlnse. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path android:fillColor="@android:color/white" android:pathData="M499,673L834,338Q834,338 834,338Q834,338 834,338L782,286Q782,286 782,286Q782,286 782,286L447,621L499,673ZM238,760Q138,755 89,718Q40,681 40,611Q40,546 93.5,505.5Q147,465 242,457Q281,454 300.5,444.5Q320,435 320,418Q320,392 290.5,379Q261,366 193,360L200,280Q303,288 351.5,321.5Q400,355 400,418Q400,471 361.5,501Q323,531 248,537Q184,542 152,560.5Q120,579 120,611Q120,646 148,661.5Q176,677 242,680L238,760ZM518,767L353,602L735,220Q755,200 782.5,200Q810,200 830,220L900,290Q920,310 920,337.5Q920,365 900,385L518,767ZM359,800Q342,804 329,791Q316,778 320,761L353,602L518,767L359,800Z"/> +</vector> diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 65005f840598..572f063c20c4 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -32,6 +32,34 @@ android:id="@+id/min_edge_guideline" app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing" android:orientation="vertical"/> + <!-- This toast-like indication layout was forked from text_toast.xml and will have the same + appearance as system toast. --> + <FrameLayout + android:id="@+id/indication_container" + android:visibility="gone" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:maxWidth="@*android:dimen/toast_width" + android:background="@android:drawable/toast_frame" + android:elevation="@*android:dimen/toast_elevation" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toBottomOf="parent"> + <TextView + android:id="@+id/indication_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="2" + android:paddingTop="12dp" + android:paddingBottom="12dp" + android:textAppearance="@*android:style/TextAppearance.Toast"/> + </FrameLayout> <!-- Negative horizontal margin because this container background must render beyond the thing it's constrained by (the actions themselves). --> <FrameLayout @@ -47,7 +75,7 @@ app:layout_constraintStart_toStartOf="@id/min_edge_guideline" app:layout_constraintTop_toTopOf="@id/actions_container" app:layout_constraintEnd_toEndOf="@id/actions_container" - app:layout_constraintBottom_toBottomOf="parent"/> + app:layout_constraintBottom_toTopOf="@id/indication_container"/> <HorizontalScrollView android:id="@+id/actions_container" android:layout_width="0dp" @@ -144,7 +172,7 @@ android:visibility="gone" android:elevation="7dp" android:padding="8dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/indication_container" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml deleted file mode 100644 index dc560bf2fab7..000000000000 --- a/packages/SystemUI/res/layout/screen_record_dialog.xml +++ /dev/null @@ -1,164 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <!-- Scrollview is necessary to fit everything in landscape layout --> - <ScrollView - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingStart="@dimen/dialog_side_padding" - android:paddingEnd="@dimen/dialog_side_padding" - android:paddingTop="@dimen/dialog_top_padding" - android:paddingBottom="@dimen/dialog_bottom_padding" - android:orientation="vertical"> - - <!-- Header --> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:gravity="center"> - <ImageView - android:layout_width="@dimen/screenrecord_logo_size" - android:layout_height="@dimen/screenrecord_logo_size" - android:src="@drawable/ic_screenrecord" - android:tint="@color/screenrecord_icon_color"/> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.Dialog.Title" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:text="@string/screenrecord_permission_dialog_title" - android:layout_marginTop="22dp" - android:layout_marginBottom="15dp"/> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/screenrecord_permission_dialog_warning_entire_screen" - android:textAppearance="@style/TextAppearance.Dialog.Body.Message" - android:gravity="center" - android:layout_marginBottom="20dp"/> - - <!-- Options --> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <ImageView - android:layout_width="@dimen/screenrecord_option_icon_size" - android:layout_height="@dimen/screenrecord_option_icon_size" - android:src="@drawable/ic_mic_26dp" - android:tint="?android:attr/textColorSecondary" - android:layout_gravity="center" - android:layout_weight="0" - android:layout_marginEnd="@dimen/screenrecord_option_padding"/> - <Spinner - android:id="@+id/screen_recording_options" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:minHeight="48dp" - android:layout_weight="1" - android:popupBackground="@drawable/screenrecord_spinner_background" - android:textColor="?androidprv:attr/materialColorOnSurface" - android:dropDownWidth="274dp" - android:prompt="@string/screenrecord_audio_label"/> - <Switch - android:layout_width="wrap_content" - android:minWidth="48dp" - android:layout_height="48dp" - android:layout_weight="0" - android:layout_gravity="end" - android:contentDescription="@string/screenrecord_audio_label" - android:id="@+id/screenrecord_audio_switch" - style="@style/ScreenRecord.Switch"/> - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_marginTop="@dimen/screenrecord_option_padding"> - <ImageView - android:layout_width="@dimen/screenrecord_option_icon_size" - android:layout_height="@dimen/screenrecord_option_icon_size" - android:layout_weight="0" - android:src="@drawable/ic_touch" - android:tint="?android:attr/textColorSecondary" - android:layout_gravity="center" - android:layout_marginEnd="@dimen/screenrecord_option_padding"/> - <TextView - android:layout_width="0dp" - android:layout_height="wrap_content" - android:minHeight="48dp" - android:layout_weight="1" - android:layout_gravity="fill_vertical" - android:gravity="center_vertical" - android:text="@string/screenrecord_taps_label" - android:textAppearance="?android:attr/textAppearanceMedium" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:textColor="?androidprv:attr/materialColorOnSurface" - android:importantForAccessibility="no"/> - <Switch - android:layout_width="wrap_content" - android:minWidth="48dp" - android:layout_height="48dp" - android:layout_weight="0" - android:id="@+id/screenrecord_taps_switch" - android:contentDescription="@string/screenrecord_taps_label" - style="@style/ScreenRecord.Switch"/> - </LinearLayout> - </LinearLayout> - - <!-- Buttons --> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_marginTop="36dp"> - <TextView - android:id="@+id/button_cancel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="0" - android:layout_gravity="start" - android:text="@string/cancel" - style="@style/Widget.Dialog.Button.BorderButton" /> - <Space - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1"/> - - <TextView - android:id="@+id/button_start" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="0" - android:layout_gravity="end" - android:text="@string/screenrecord_continue" - style="@style/Widget.Dialog.Button" /> - </LinearLayout> - </LinearLayout> - </ScrollView> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml index 9b3af52e9704..7c266e60b503 100644 --- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -65,7 +65,7 @@ android:background="@drawable/volume_drawer_selection_bg" android:contentDescription="@string/volume_ringer_change" android:gravity="center" - android:padding="10dp" + android:padding="@dimen/volume_dialog_ringer_horizontal_padding" android:src="@drawable/ic_volume_media" android:tint="?androidprv:attr/materialColorOnPrimary" /> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index c8ef093c40db..82c8c44f1efe 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -116,7 +116,7 @@ <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices + internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices,notes </string> <!-- The tiles to display in QuickSettings --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1766cdf8c804..af6eacbe499f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1004,6 +1004,9 @@ <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]--> <string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string> + <!-- QuickSettings: Notes tile. The label of a quick settings tile for launching the default notes taking app. [CHAR LIMIT=NONE] --> + <string name="quick_settings_notes_label">Note</string> + <!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] --> <string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string> <!--- Title of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=150] --> @@ -1518,7 +1521,7 @@ <string name="no_unseen_notif_text">No new notifications</string> <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=60] --> - <string name="adaptive_notification_edu_hun_title">Notification cooldown is on</string> + <string name="adaptive_notification_edu_hun_title">Notification cooldown is now on</string> <!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] --> <string name="adaptive_notification_edu_hun_text">Your device volume and alerts are reduced automatically for up to 2 minutes when you get too many notifications at once.</string> diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml index ad09b466dd3e..d885e00fbe82 100644 --- a/packages/SystemUI/res/values/tiles_states_strings.xml +++ b/packages/SystemUI/res/values/tiles_states_strings.xml @@ -348,4 +348,14 @@ <item>Off</item> <item>On</item> </string-array> + + <!-- State names for notes tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_notes"> + <item>Unavailable</item> + <item>Off</item> + <item>On</item> + </string-array> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 283e4556d05c..83ca496dbef2 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -115,23 +115,23 @@ oneway interface IOverviewProxy { /** * Sent when {@link TaskbarDelegate#checkNavBarModes} is called. */ - void checkNavBarModes() = 30; + void checkNavBarModes(int displayId) = 30; /** * Sent when {@link TaskbarDelegate#finishBarAnimations} is called. */ - void finishBarAnimations() = 31; + void finishBarAnimations(int displayId) = 31; /** * Sent when {@link TaskbarDelegate#touchAutoDim} is called. {@param reset} is true, when auto * dim is reset after a timeout. */ - void touchAutoDim(boolean reset) = 32; + void touchAutoDim(int displayid, boolean reset) = 32; /** * Sent when {@link TaskbarDelegate#transitionTo} is called. */ - void transitionTo(int barMode, boolean animate) = 33; + void transitionTo(int displayId, int barMode, boolean animate) = 33; /** * Sent when {@link TaskbarDelegate#appTransitionPending} is called. diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 7ec977a8d6aa..9e8cabf141ed 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -243,10 +243,6 @@ public class Task { public Rect appBounds; - // Last snapshot data, only used for recent tasks - public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData = - new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData(); - @ViewDebug.ExportedProperty(category="recents") public boolean isVisible; @@ -283,7 +279,6 @@ public class Task { public Task(Task other) { this(other.key, other.colorPrimary, other.colorBackground, other.isDockable, other.isLocked, other.taskDescription, other.topActivity); - lastSnapshotData.set(other.lastSnapshotData); positionInParent = other.positionInParent; appBounds = other.appBounds; isVisible = other.isVisible; @@ -315,33 +310,10 @@ public class Task { : key.baseIntent.getComponent(); } - public void setLastSnapshotData(ActivityManager.RecentTaskInfo rawTask) { - lastSnapshotData.set(rawTask.lastSnapshotData); - } - public TaskKey getKey() { return key; } - /** - * Returns the visible width to height ratio. Returns 0f if snapshot data is not available. - */ - public float getVisibleThumbnailRatio(boolean clipInsets) { - if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) { - return 0f; - } - - float availableWidth = lastSnapshotData.taskSize.x; - float availableHeight = lastSnapshotData.taskSize.y; - if (clipInsets) { - availableWidth -= - (lastSnapshotData.contentInsets.left + lastSnapshotData.contentInsets.right); - availableHeight -= - (lastSnapshotData.contentInsets.top + lastSnapshotData.contentInsets.bottom); - } - return availableWidth / availableHeight; - } - @Override public boolean equals(Object o) { if (o == this) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8b593701540b..f98890ec9c5d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -121,6 +121,7 @@ import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; +import com.android.systemui.Flags; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -473,6 +474,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + @Deprecated private final SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray(); private final SparseBooleanArray mUserHasTrust = new SparseBooleanArray(); private final SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray(); @@ -2688,7 +2690,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @see Intent#ACTION_USER_UNLOCKED */ public boolean isUserUnlocked(int userId) { - return mUserIsUnlocked.get(userId); + if (Flags.userEncryptedSource()) { + return mUserManager.isUserUnlocked(userId); + } else { + return mUserIsUnlocked.get(userId); + } } /** @@ -4213,7 +4219,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println("ActiveUnlockRunning=" + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId())); - pw.println("userUnlockedCache[userid=" + userId + "]=" + isUserUnlocked(userId)); + pw.println("userUnlockedCache[userid=" + userId + "]=" + mUserIsUnlocked.get(userId)); pw.println("actualUserUnlocked[userid=" + userId + "]=" + mUserManager.isUserUnlocked(userId)); new DumpsysTableLogger( diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index bebfd859f9ed..cd19aaac6831 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -116,7 +116,7 @@ constructor( fun logUpdateLockScreenUserLockedMsg( userId: Int, - userUnlocked: Boolean, + userStorageUnlocked: Boolean, encryptedOrLockdown: Boolean, ) { buffer.log( @@ -124,12 +124,12 @@ constructor( LogLevel.DEBUG, { int1 = userId - bool1 = userUnlocked + bool1 = userStorageUnlocked bool2 = encryptedOrLockdown }, { "updateLockScreenUserLockedMsg userId=$int1 " + - "userUnlocked:$bool1 encryptedOrLockdown:$bool2" + "userStorageUnlocked:$bool1 encryptedOrLockdown:$bool2" } ) } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java index f8b445ba069e..3cf400aa5c16 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java @@ -38,7 +38,6 @@ import android.view.IWindowManager; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; @@ -138,10 +137,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks mWindowMagnifierCallback, mSysUiState, mSecureSettings, - scvhSupplier, - new SfVsyncFrameCallbackProvider(), - WindowManagerGlobal::getWindowSession, - mViewCaptureAwareWindowManager); + scvhSupplier); } } @@ -407,7 +403,8 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks } } - boolean isMagnificationSettingsPanelShowing(int displayId) { + @MainThread + private boolean isMagnificationSettingsPanelShowing(int displayId) { final MagnificationSettingsController magnificationSettingsController = mMagnificationSettingsSupplier.get(displayId); if (magnificationSettingsController != null) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 0883a0611d15..7d5cf232bcb9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -54,11 +54,8 @@ import android.util.Range; import android.util.Size; import android.util.SparseArray; import android.util.TypedValue; -import android.view.Choreographer; import android.view.Display; import android.view.Gravity; -import android.view.IWindow; -import android.view.IWindowSession; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -80,10 +77,8 @@ import android.widget.ImageView; import androidx.annotation.UiThread; import androidx.core.math.MathUtils; -import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.accessibility.common.MagnificationConstants; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.Flags; import com.android.systemui.model.SysUiState; import com.android.systemui.res.R; @@ -127,7 +122,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private final SurfaceControl.Transaction mTransaction; private final WindowManager mWm; - private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; private float mScale; private int mSettingsButtonIndex = MagnificationSize.DEFAULT; @@ -219,11 +213,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private int mMinWindowSize; private final WindowMagnificationAnimationController mAnimationController; - private final Supplier<IWindowSession> mGlobalWindowSessionSupplier; - private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; private final MagnificationGestureDetector mGestureDetector; private int mBounceEffectDuration; - private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback; private Locale mLocale; private NumberFormat mPercentFormat; private float mBounceEffectAnimationScale; @@ -258,18 +249,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold @NonNull WindowMagnifierCallback callback, SysUiState sysUiState, SecureSettings secureSettings, - Supplier<SurfaceControlViewHost> scvhSupplier, - SfVsyncFrameCallbackProvider sfVsyncFrameProvider, - Supplier<IWindowSession> globalWindowSessionSupplier, - ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) { + Supplier<SurfaceControlViewHost> scvhSupplier) { mContext = context; mHandler = handler; mAnimationController = animationController; - mAnimationController.setOnAnimationEndRunnable(() -> { - if (Flags.createWindowlessWindowMagnifier()) { - notifySourceBoundsChanged(); - } - }); + mAnimationController.setOnAnimationEndRunnable(this::notifySourceBoundsChanged); mAnimationController.setWindowMagnificationController(this); mWindowMagnifierCallback = callback; mSysUiState = sysUiState; @@ -283,7 +267,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mWm = context.getSystemService(WindowManager.class); mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds()); - mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager; mResources = mContext.getResources(); mScale = secureSettings.getFloatForUser( @@ -313,76 +296,31 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mGestureDetector = new MagnificationGestureDetector(mContext, handler, this); mWindowInsetChangeRunnable = this::onWindowInsetChanged; - mGlobalWindowSessionSupplier = globalWindowSessionSupplier; - mSfVsyncFrameProvider = sfVsyncFrameProvider; // Initialize listeners. - if (Flags.createWindowlessWindowMagnifier()) { - mMirrorViewRunnable = new Runnable() { - final Rect mPreviousBounds = new Rect(); - - @Override - public void run() { - if (mMirrorView != null) { - if (mPreviousBounds.width() != mMirrorViewBounds.width() - || mPreviousBounds.height() != mMirrorViewBounds.height()) { - mMirrorView.setSystemGestureExclusionRects(Collections.singletonList( - new Rect(0, 0, mMirrorViewBounds.width(), - mMirrorViewBounds.height()))); - mPreviousBounds.set(mMirrorViewBounds); - } - updateSystemUIStateIfNeeded(); - mWindowMagnifierCallback.onWindowMagnifierBoundsChanged( - mDisplayId, mMirrorViewBounds); - } - } - }; - - mMirrorSurfaceViewLayoutChangeListener = - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> - mMirrorView.post(this::applyTapExcludeRegion); + mMirrorViewRunnable = new Runnable() { + final Rect mPreviousBounds = new Rect(); - mMirrorViewGeometryVsyncCallback = null; - } else { - mMirrorViewRunnable = () -> { + @Override + public void run() { if (mMirrorView != null) { - final Rect oldViewBounds = new Rect(mMirrorViewBounds); - mMirrorView.getBoundsOnScreen(mMirrorViewBounds); - if (oldViewBounds.width() != mMirrorViewBounds.width() - || oldViewBounds.height() != mMirrorViewBounds.height()) { + if (mPreviousBounds.width() != mMirrorViewBounds.width() + || mPreviousBounds.height() != mMirrorViewBounds.height()) { mMirrorView.setSystemGestureExclusionRects(Collections.singletonList( - new Rect(0, 0, - mMirrorViewBounds.width(), mMirrorViewBounds.height()))); + new Rect(0, 0, mMirrorViewBounds.width(), + mMirrorViewBounds.height()))); + mPreviousBounds.set(mMirrorViewBounds); } updateSystemUIStateIfNeeded(); mWindowMagnifierCallback.onWindowMagnifierBoundsChanged( mDisplayId, mMirrorViewBounds); } - }; - - mMirrorSurfaceViewLayoutChangeListener = - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> - mMirrorView.post(this::applyTapExcludeRegion); - - mMirrorViewGeometryVsyncCallback = - l -> { - if (isActivated() && mMirrorSurface != null && calculateSourceBounds( - mMagnificationFrame, mScale)) { - // The final destination for the magnification surface should be at 0,0 - // since the ViewRootImpl's position will change - mTmpRect.set(0, 0, mMagnificationFrame.width(), - mMagnificationFrame.height()); - mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect, - Surface.ROTATION_0).apply(); - - // Notify source bounds change when the magnifier is not animating. - if (!mAnimationController.isAnimating()) { - mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, - mSourceBounds); - } - } - }; - } + } + }; + + mMirrorSurfaceViewLayoutChangeListener = + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> + mMirrorView.post(this::applyTouchableRegion); mMirrorViewLayoutChangeListener = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { @@ -463,7 +401,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (isActivated()) { updateDimensions(); - applyTapExcludeRegion(); + applyTouchableRegion(); } if (!enable) { @@ -513,9 +451,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (mMirrorView != null) { mHandler.removeCallbacks(mMirrorViewRunnable); mMirrorView.removeOnLayoutChangeListener(mMirrorViewLayoutChangeListener); - if (!Flags.createWindowlessWindowMagnifier()) { - mViewCaptureAwareWindowManager.removeView(mMirrorView); - } mMirrorView = null; } @@ -624,11 +559,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (!isActivated()) return; LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams(); params.accessibilityTitle = getAccessibilityWindowTitle(); - if (Flags.createWindowlessWindowMagnifier()) { - mSurfaceControlViewHost.relayout(params); - } else { - mWm.updateViewLayout(mMirrorView, params); - } + mSurfaceControlViewHost.relayout(params); } /** @@ -678,62 +609,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return (oldRotation - newRotation + 4) % 4 * 90; } - private void createMirrorWindow() { - if (Flags.createWindowlessWindowMagnifier()) { - createWindowlessMirrorWindow(); - return; - } - - // The window should be the size the mirrored surface will be but also add room for the - // border and the drag handle. - int windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin; - int windowHeight = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin; - - LayoutParams params = new LayoutParams( - windowWidth, windowHeight, - LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, - LayoutParams.FLAG_NOT_TOUCH_MODAL - | LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSPARENT); - params.gravity = Gravity.TOP | Gravity.LEFT; - params.x = mMagnificationFrame.left - mMirrorSurfaceMargin; - params.y = mMagnificationFrame.top - mMirrorSurfaceMargin; - params.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - params.receiveInsetsIgnoringZOrder = true; - params.setTitle(mContext.getString(R.string.magnification_window_title)); - params.accessibilityTitle = getAccessibilityWindowTitle(); - - mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null); - mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view); - - mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border); - - // Allow taps to go through to the mirror SurfaceView below. - mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener); - - mMirrorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener); - mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate()); - mMirrorView.setOnApplyWindowInsetsListener((v, insets) -> { - if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) { - mHandler.post(mWindowInsetChangeRunnable); - } - return v.onApplyWindowInsets(insets); - }); - - mViewCaptureAwareWindowManager.addView(mMirrorView, params); - - SurfaceHolder holder = mMirrorSurfaceView.getHolder(); - holder.addCallback(this); - holder.setFormat(PixelFormat.RGBA_8888); - addDragTouchListeners(); - } - private void createWindowlessMirrorWindow() { // The window should be the size the mirrored surface will be but also add room for the // border and the drag handle. @@ -802,62 +677,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } } - private void applyTapExcludeRegion() { - if (Flags.createWindowlessWindowMagnifier()) { - applyTouchableRegion(); - return; - } - - // Sometimes this can get posted and run after deleteWindowMagnification() is called. - if (mMirrorView == null) return; - - final Region tapExcludeRegion = calculateTapExclude(); - final IWindow window = IWindow.Stub.asInterface(mMirrorView.getWindowToken()); - try { - IWindowSession session = mGlobalWindowSessionSupplier.get(); - session.updateTapExcludeRegion(window, tapExcludeRegion); - } catch (RemoteException e) { - } - } - - private Region calculateTapExclude() { - Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize, - mMirrorView.getWidth() - mBorderDragSize, - mMirrorView.getHeight() - mBorderDragSize); - - Region tapExcludeRegion = new Region(); - - Rect dragArea = new Rect(); - mDragView.getHitRect(dragArea); - - Rect topLeftArea = new Rect(); - mTopLeftCornerView.getHitRect(topLeftArea); - - Rect topRightArea = new Rect(); - mTopRightCornerView.getHitRect(topRightArea); - - Rect bottomLeftArea = new Rect(); - mBottomLeftCornerView.getHitRect(bottomLeftArea); - - Rect bottomRightArea = new Rect(); - mBottomRightCornerView.getHitRect(bottomRightArea); - - Rect closeArea = new Rect(); - mCloseView.getHitRect(closeArea); - - // add tapExcludeRegion for Drag or close - tapExcludeRegion.op(dragArea, Region.Op.UNION); - tapExcludeRegion.op(topLeftArea, Region.Op.UNION); - tapExcludeRegion.op(topRightArea, Region.Op.UNION); - tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION); - tapExcludeRegion.op(bottomRightArea, Region.Op.UNION); - tapExcludeRegion.op(closeArea, Region.Op.UNION); - - regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE); - - return regionInsideDragBorder; - } - private void applyTouchableRegion() { // Sometimes this can get posted and run after deleteWindowMagnification() is called. if (mMirrorView == null) return; @@ -1085,13 +904,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold * {@link #mMagnificationFrame}. */ private void modifyWindowMagnification(boolean computeWindowSize) { - if (Flags.createWindowlessWindowMagnifier()) { - updateMirrorSurfaceGeometry(); - updateWindowlessMirrorViewLayout(computeWindowSize); - } else { - mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback); - updateMirrorViewLayout(computeWindowSize); - } + updateMirrorSurfaceGeometry(); + updateWindowlessMirrorViewLayout(computeWindowSize); } /** @@ -1169,58 +983,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mMirrorViewRunnable.run(); } - /** - * Updates the layout params of MirrorView based on the size of {@link #mMagnificationFrame} - * and translates MirrorView position when the view is moved close to the screen edges; - * - * @param computeWindowSize set to {@code true} to compute window size with - * {@link #mMagnificationFrame}. - */ - private void updateMirrorViewLayout(boolean computeWindowSize) { - if (!isActivated()) { - return; - } - final int maxMirrorViewX = mWindowBounds.width() - mMirrorView.getWidth(); - final int maxMirrorViewY = mWindowBounds.height() - mMirrorView.getHeight(); - - LayoutParams params = - (LayoutParams) mMirrorView.getLayoutParams(); - params.x = mMagnificationFrame.left - mMirrorSurfaceMargin; - params.y = mMagnificationFrame.top - mMirrorSurfaceMargin; - if (computeWindowSize) { - params.width = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin; - params.height = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin; - } - - // Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView - // able to move close to the screen edges. - final float translationX; - final float translationY; - if (params.x < 0) { - translationX = Math.max(params.x, -mOuterBorderSize); - } else if (params.x > maxMirrorViewX) { - translationX = Math.min(params.x - maxMirrorViewX, mOuterBorderSize); - } else { - translationX = 0; - } - if (params.y < 0) { - translationY = Math.max(params.y, -mOuterBorderSize); - } else if (params.y > maxMirrorViewY) { - translationY = Math.min(params.y - maxMirrorViewY, mOuterBorderSize); - } else { - translationY = 0; - } - mMirrorView.setTranslationX(translationX); - mMirrorView.setTranslationY(translationY); - mWm.updateViewLayout(mMirrorView, params); - - // If they are not dragging the handle, we can move the drag handle immediately without - // disruption. But if they are dragging it, we avoid moving until the end of the drag. - if (!mIsDragging) { - mMirrorView.post(this::maybeRepositionButton); - } - } - @Override public boolean onTouch(View v, MotionEvent event) { if (v == mDragView @@ -1474,7 +1236,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold calculateMagnificationFrameBoundary(); updateMagnificationFramePosition((int) offsetX, (int) offsetY); if (!isActivated()) { - createMirrorWindow(); + createWindowlessMirrorWindow(); showControls(); applyResourcesValues(); } else { @@ -1766,7 +1528,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (newGravity != layoutParams.gravity) { layoutParams.gravity = newGravity; mDragView.setLayoutParams(layoutParams); - mDragView.post(this::applyTapExcludeRegion); + mDragView.post(this::applyTouchableRegion); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt index b8ff3bb43203..178e1112fa6f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt @@ -24,6 +24,7 @@ import com.android.systemui.biometrics.shared.model.SensorLocation import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -42,7 +43,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Application private val context: Context, repository: FingerprintPropertyRepository, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, displayStateInteractor: DisplayStateInteractor, udfpsOverlayInteractor: UdfpsOverlayInteractor, ) { @@ -73,15 +74,12 @@ constructor( * - device's natural orientation */ private val unscaledSensorLocation: Flow<SensorLocationInternal> = - combine( - repository.sensorLocations, - uniqueDisplayId, - ) { locations, displayId -> + combine(repository.sensorLocations, uniqueDisplayId) { locations, displayId -> // Devices without multiple physical displays do not use the display id as the key; // instead, the key is an empty string. locations.getOrDefault( displayId, - locations.getOrDefault("", SensorLocationInternal.DEFAULT) + locations.getOrDefault("", SensorLocationInternal.DEFAULT), ) } @@ -92,16 +90,15 @@ constructor( * - device's natural orientation */ val sensorLocation: Flow<SensorLocation> = - combine( + combine(unscaledSensorLocation, configurationInteractor.scaleForResolution) { unscaledSensorLocation, - configurationInteractor.scaleForResolution, - ) { unscaledSensorLocation, scale -> + scale -> val sensorLocation = SensorLocation( naturalCenterX = unscaledSensorLocation.sensorLocationX, naturalCenterY = unscaledSensorLocation.sensorLocationY, naturalRadius = unscaledSensorLocation.sensorRadius, - scale = scale + scale = scale, ) sensorLocation } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index e178c093b746..7039d5e7d9b0 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.bouncer.domain.interactor import android.app.StatusBarManager.SESSION_KEYGUARD +import com.android.app.tracing.coroutines.asyncTraced as async import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.UiEventLogger import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor @@ -39,9 +40,9 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneBackInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.asyncTraced as async import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow @@ -65,7 +66,7 @@ constructor( private val uiEventLogger: UiEventLogger, private val sessionTracker: SessionTracker, sceneBackInteractor: SceneBackInteractor, - private val configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, ) { private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>() val onIncorrectBouncerInput: SharedFlow<Unit> = _onIncorrectBouncerInput diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt index 4fe6fc69e8be..f50a2ab1d803 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.bouncer.ui.viewmodel import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.bouncer.domain.interactor.BouncerInteractor @@ -33,16 +32,14 @@ import kotlinx.coroutines.flow.map */ class BouncerUserActionsViewModel @AssistedInject -constructor( - private val bouncerInteractor: BouncerInteractor, -) : UserActionsViewModel() { +constructor(private val bouncerInteractor: BouncerInteractor) : UserActionsViewModel() { override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) { bouncerInteractor.dismissDestination .map { prevScene -> mapOf( Back to UserActionResult(prevScene), - Swipe(SwipeDirection.Down) to UserActionResult(prevScene), + Swipe.Down to UserActionResult(prevScene), ) } .collect { actions -> setActions(actions) } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt new file mode 100644 index 000000000000..ddd6bc9ef16b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +/** Interface for listening to indication text changed from [ClipboardIndicationProvider]. */ +interface ClipboardIndicationCallback { + + /** Notifies the indication text changed. */ + fun onIndicationTextChanged(text: CharSequence) +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt new file mode 100644 index 000000000000..be3272369d46 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +/** Interface to provide the clipboard indication to be shown under the overlay. */ +interface ClipboardIndicationProvider { + + /** + * Gets the indication text async. + * + * @param callback callback for getting the indication text. + */ + fun getIndicationText(callback: ClipboardIndicationCallback) +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt index 03bc9350674b..da94d5b518b7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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,16 +14,13 @@ * limitations under the License. */ -package com.android.systemui.media.taptotransfer +package com.android.systemui.clipboardoverlay import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import javax.inject.Inject -/** Flags related to media tap-to-transfer. */ @SysUISingleton -class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) { - /** */ - fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER) +open class ClipboardIndicationProviderImpl @Inject constructor() : ClipboardIndicationProvider { + + override fun getIndicationText(callback: ClipboardIndicationCallback) {} } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 65c01ed9eecd..ac747845267c 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -21,6 +21,7 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS; import static com.android.systemui.Flags.clipboardImageTimeout; import static com.android.systemui.Flags.clipboardSharedTransitions; +import static com.android.systemui.Flags.showClipboardIndication; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER; @@ -99,6 +100,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private final ClipboardTransitionExecutor mTransitionExecutor; private final ClipboardOverlayView mView; + private final ClipboardIndicationProvider mClipboardIndicationProvider; private Runnable mOnSessionCompleteListener; private Runnable mOnRemoteCopyTapped; @@ -173,6 +175,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv } }; + private ClipboardIndicationCallback mIndicationCallback = new ClipboardIndicationCallback() { + @Override + public void onIndicationTextChanged(@NonNull CharSequence text) { + mView.setIndicationText(text); + } + }; + @Inject public ClipboardOverlayController(@OverlayWindowContext Context context, ClipboardOverlayView clipboardOverlayView, @@ -185,11 +194,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @Background Executor bgExecutor, ClipboardImageLoader clipboardImageLoader, ClipboardTransitionExecutor transitionExecutor, + ClipboardIndicationProvider clipboardIndicationProvider, UiEventLogger uiEventLogger) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mClipboardImageLoader = clipboardImageLoader; mTransitionExecutor = transitionExecutor; + mClipboardIndicationProvider = clipboardIndicationProvider; mClipboardLogger = new ClipboardLogger(uiEventLogger); @@ -288,6 +299,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting; mClipboardModel = model; mClipboardLogger.setClipSource(mClipboardModel.getSource()); + if (showClipboardIndication()) { + mClipboardIndicationProvider.getIndicationText(mIndicationCallback); + } if (clipboardImageTimeout()) { if (shouldAnimate) { reset(); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java index 1762d82b3237..7e4d76280909 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java @@ -18,6 +18,8 @@ package com.android.systemui.clipboardoverlay; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static com.android.systemui.Flags.showClipboardIndication; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -53,6 +55,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.view.ViewCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; @@ -103,6 +106,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { private View mShareChip; private View mRemoteCopyChip; private View mActionContainerBackground; + private View mIndicationContainer; + private TextView mIndicationText; private View mDismissButton; private LinearLayout mActionContainer; private ClipboardOverlayCallbacks mClipboardCallbacks; @@ -136,6 +141,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { mShareChip = requireViewById(R.id.share_chip); mRemoteCopyChip = requireViewById(R.id.remote_copy_chip); mDismissButton = requireViewById(R.id.dismiss_button); + mIndicationContainer = requireViewById(R.id.indication_container); + mIndicationText = mIndicationContainer.findViewById(R.id.indication_text); bindDefaultActionChips(); @@ -208,6 +215,14 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { } } + void setIndicationText(CharSequence text) { + mIndicationText.setText(text); + + // Set the visibility of clipboard indication based on the text is empty or not. + int visibility = text.isEmpty() ? View.GONE : View.VISIBLE; + mIndicationContainer.setVisibility(visibility); + } + void setMinimized(boolean minimized) { if (minimized) { mMinimizedPreview.setVisibility(View.VISIBLE); @@ -221,6 +236,18 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { mPreviewBorder.setVisibility(View.VISIBLE); mActionContainer.setVisibility(View.VISIBLE); } + + if (showClipboardIndication()) { + // Adjust the margin of clipboard indication based on the minimized state. + int marginStart = minimized ? getResources().getDimensionPixelSize( + R.dimen.overlay_action_container_margin_horizontal) + : getResources().getDimensionPixelSize( + R.dimen.overlay_action_container_minimum_edge_spacing); + ConstraintLayout.LayoutParams params = + (ConstraintLayout.LayoutParams) mIndicationContainer.getLayoutParams(); + params.setMarginStart(marginStart); + mIndicationContainer.setLayoutParams(params); + } } void setInsets(WindowInsets insets, int orientation) { @@ -313,6 +340,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { setTranslationX(0); setAlpha(0); mActionContainerBackground.setVisibility(View.GONE); + mIndicationContainer.setVisibility(View.GONE); mDismissButton.setVisibility(View.GONE); mShareChip.setVisibility(View.GONE); mRemoteCopyChip.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt index 527819c73e2f..c81f0d98f3ad 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt @@ -15,18 +15,26 @@ */ package com.android.systemui.clipboardoverlay.dagger +import com.android.systemui.clipboardoverlay.ClipboardIndicationProvider +import com.android.systemui.clipboardoverlay.ClipboardIndicationProviderImpl import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionController import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionControllerImpl import dagger.Binds import dagger.Module -/** Dagger Module for code in the clipboard overlay package. */ +/** Dagger Module to provide default implementations which could be overridden. */ @Module -interface ClipboardOverlaySuppressionModule { +interface ClipboardOverlayOverrideModule { /** Provides implementation for [ClipboardOverlaySuppressionController]. */ @Binds fun provideClipboardOverlaySuppressionController( clipboardOverlaySuppressionControllerImpl: ClipboardOverlaySuppressionControllerImpl ): ClipboardOverlaySuppressionController + + /** Provides implementation for [ClipboardIndicationProvider]. */ + @Binds + fun provideClipboardIndicationProvider( + clipboardIndicationProviderImpl: ClipboardIndicationProviderImpl + ): ClipboardIndicationProvider } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index ca4bbc0ae3bd..00c62092421d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.theme.PlatformTheme import com.android.internal.logging.UiEventLogger import com.android.systemui.Flags.communalEditWidgetsActivityFinishFix @@ -44,6 +45,7 @@ import com.android.systemui.communal.ui.compose.CommunalHub import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger @@ -52,13 +54,13 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.flow.first -import com.android.app.tracing.coroutines.launchTraced as launch /** An Activity for editing the widgets that appear in hub mode. */ class EditWidgetsActivity @Inject constructor( private val communalViewModel: CommunalEditModeViewModel, + private val keyguardInteractor: KeyguardInteractor, private var windowManagerService: IWindowManager? = null, private val uiEventLogger: UiEventLogger, private val widgetConfiguratorFactory: WidgetConfigurationController.Factory, @@ -223,6 +225,9 @@ constructor( communalViewModel.currentScene.first { it == CommunalScenes.Blank } } + // Wait for dream to exit, if we were previously dreaming. + keyguardInteractor.isDreaming.first { !it } + communalViewModel.setEditModeState(EditModeState.SHOWING) // Inform the ActivityController that we are now fully visible. diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java index d727a70de377..769d7faad2b0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java @@ -16,6 +16,7 @@ package com.android.systemui.dagger; + import com.android.systemui.classifier.FalsingManagerProxy; import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.globalactions.GlobalActionsImpl; @@ -26,18 +27,20 @@ import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; +import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore; +import com.android.systemui.statusbar.data.repository.SysuiDarkIconDispatcherStore; import com.android.systemui.statusbar.phone.ActivityStarterImpl; -import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher; import com.android.systemui.volume.VolumeDialogControllerImpl; import dagger.Binds; import dagger.Module; +import dagger.Provides; /** * Module for binding Plugin implementations. * - * TODO(b/166258224): Many of these should be moved closer to their implementations. + * <p>TODO(b/166258224): Many of these should be moved closer to their implementations. */ @Module public abstract class PluginModule { @@ -47,12 +50,18 @@ public abstract class PluginModule { abstract ActivityStarter provideActivityStarter(ActivityStarterImpl activityStarterImpl); /** */ - @Binds - abstract DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl); + @Provides + @SysUISingleton + static DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherStore store) { + return store.getDefaultDisplay(); + } - @Binds - abstract SysuiDarkIconDispatcher provideSysuiDarkIconDispatcher( - DarkIconDispatcherImpl controllerImpl); + @Provides + @SysUISingleton + static SysuiDarkIconDispatcher provideSysuiDarkIconDispatcher( + SysuiDarkIconDispatcherStore store) { + return store.getDefaultDisplay(); + } /** */ @Binds diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 609b7330b600..2e323d40edcd 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -29,7 +29,7 @@ import com.android.systemui.accessibility.AccessibilityModule; import com.android.systemui.accessibility.SystemActionsModule; import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule; import com.android.systemui.battery.BatterySaverModule; -import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlaySuppressionModule; +import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayOverrideModule; import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; @@ -125,7 +125,7 @@ import javax.inject.Named; AospPolicyModule.class, BatterySaverModule.class, CentralSurfacesModule.class, - ClipboardOverlaySuppressionModule.class, + ClipboardOverlayOverrideModule.class, CollapsedStatusBarFragmentStartableModule.class, ConnectingDisplayViewModel.StartableModule.class, DefaultBlueprintModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java index 303f91688575..911331b8bff1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java @@ -42,7 +42,7 @@ import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.GlobalSettings; import java.io.PrintWriter; @@ -51,6 +51,7 @@ import java.util.Map; import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.inject.Inject; @@ -74,7 +75,6 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { static final String TAG = "SysUIFlags"; private final FlagManager mFlagManager; - private final Context mContext; private final GlobalSettings mGlobalSettings; private final Resources mResources; private final SystemPropertiesHelper mSystemProperties; @@ -84,6 +84,8 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>(); private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>(); private final Restarter mRestarter; + private final UserTracker mUserTracker; + private final Executor mMainExecutor; private final ServerFlagReader.ChangeListener mOnPropertiesChanged = new ServerFlagReader.ChangeListener() { @@ -118,6 +120,18 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { } }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mContext.unregisterReceiver(mReceiver); + mContext = userContext; + registerReceiver(); + } + }; + + private Context mContext; + @Inject public FeatureFlagsClassicDebug( FlagManager flagManager, @@ -128,10 +142,11 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { ServerFlagReader serverFlagReader, @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags, Restarter restarter, - UserContextProvider userContextProvider) { + UserTracker userTracker, + @Main Executor executor) { mFlagManager = flagManager; if (classicFlagsMultiUser()) { - mContext = userContextProvider.createCurrentUserContext(context); + mContext = userTracker.createCurrentUserContext(context); } else { mContext = context; } @@ -141,19 +156,28 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { mServerFlagReader = serverFlagReader; mAllFlags = allFlags; mRestarter = restarter; + mUserTracker = userTracker; + mMainExecutor = executor; } /** Call after construction to setup listeners. */ void init() { - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_SET_FLAG); - filter.addAction(ACTION_GET_FLAGS); mFlagManager.setOnSettingsChangedAction( suppressRestart -> restartSystemUI(suppressRestart, "Settings changed")); mFlagManager.setClearCacheAction(this::removeFromCache); + registerReceiver(); + if (classicFlagsMultiUser()) { + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); + } + mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged); + } + + void registerReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_SET_FLAG); + filter.addAction(ACTION_GET_FLAGS); mContext.registerReceiver(mReceiver, filter, null, null, Context.RECEIVER_EXPORTED_UNAUDITED); - mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index b431636b0e8b..c62593063020 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.flags -import android.provider.DeviceConfig import com.android.internal.annotations.Keep import com.android.systemui.flags.FlagsFactory.releasedFlag import com.android.systemui.flags.FlagsFactory.resourceBooleanFlag @@ -220,10 +219,6 @@ object Flags { // TODO(b/293380347): Tracking Bug @JvmField val COLOR_FIDELITY = unreleasedFlag("color_fidelity") - // 900 - media - // TODO(b/254512697): Tracking Bug - val MEDIA_TAP_TO_TRANSFER = releasedFlag("media_tap_to_transfer") - // TODO(b/254512654): Tracking Bug @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag("dream_media_complication") @@ -255,15 +250,6 @@ object Flags { val WM_ENABLE_SHELL_TRANSITIONS = sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true) - // TODO(b/254513207): Tracking Bug to delete - @Keep - @JvmField - val WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES = - releasedFlag( - name = "enable_screen_record_enterprise_policies", - namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER, - ) - // TODO(b/293252410) : Tracking Bug @JvmField val LOCKSCREEN_ENABLE_LANDSCAPE = unreleasedFlag("lockscreen.enable_landscape") diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt index f0b2b86d67a0..38fc2a80ad02 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt @@ -19,17 +19,18 @@ package com.android.systemui.keyboard.docking.ui.viewmodel import android.content.Context import android.view.Surface import android.view.WindowManager +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.settingslib.Utils import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxConfig import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton class KeyboardDockingIndicationViewModel @@ -38,7 +39,7 @@ constructor( private val windowManager: WindowManager, private val context: Context, keyboardDockingIndicationInteractor: KeyboardDockingIndicationInteractor, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, @Background private val backgroundScope: CoroutineScope, ) { @@ -128,7 +129,7 @@ constructor( blurAmount = BLUR_AMOUNT, duration = DURATION, easeInDuration = EASE_DURATION, - easeOutDuration = EASE_DURATION + easeOutDuration = EASE_DURATION, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index e79f5902575f..2d056001b669 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -60,6 +60,7 @@ 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 import com.android.systemui.statusbar.LightRevealScrim @@ -93,7 +94,7 @@ constructor( private val chipbarCoordinator: ChipbarCoordinator, private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel, private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, - private val configuration: ConfigurationState, + @ShadeDisplayAware private val configuration: ConfigurationState, private val context: Context, private val keyguardIndicationController: KeyguardIndicationController, private val shadeInteractor: ShadeInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt index ca862896efaa..73a4cc3ad9df 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt @@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -47,7 +48,7 @@ constructor( private val context: Context, private val burnInHelperWrapper: BurnInHelperWrapper, @Application private val scope: CoroutineScope, - private val configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, private val keyguardInteractor: KeyguardInteractor, ) { val deviceEntryIconXOffset: StateFlow<Int> = @@ -62,7 +63,7 @@ constructor( .stateIn( scope, SharingStarted.WhileSubscribed(), - burnInHelperWrapper.burnInProgressOffset() + burnInHelperWrapper.burnInProgressOffset(), ) /** Given the max x,y dimens, determine the current translation shifts. */ @@ -71,7 +72,7 @@ constructor( burnInOffset(xDimenResourceId, isXAxis = true), burnInOffset(yDimenResourceId, isXAxis = false).map { it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId) - } + }, ) { translationX, translationY -> BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale()) } @@ -117,7 +118,7 @@ constructor( private fun calculateOffset( maxBurnInOffsetPixels: Int, isXAxis: Boolean, - scale: Float = 1f + scale: Float = 1f, ): Int { return (burnInHelperWrapper.burnInOffset(maxBurnInOffsetPixels, isXAxis) * scale).toInt() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 6ac0a3f8443f..021cce6d1e23 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -20,6 +20,7 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.DreamManager import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -41,7 +42,6 @@ import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.debounce -import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton class FromDozingTransitionInteractor @@ -135,11 +135,22 @@ constructor( if (!deviceEntryInteractor.isLockscreenEnabled()) { if (!SceneContainerFlag.isEnabled) { - startTransitionTo(KeyguardState.GONE) + startTransitionTo( + KeyguardState.GONE, + ownerReason = "lockscreen not enabled", + ) } } else if (canDismissLockscreen() || isKeyguardGoingAway) { if (!SceneContainerFlag.isEnabled) { - startTransitionTo(KeyguardState.GONE) + startTransitionTo( + KeyguardState.GONE, + ownerReason = + if (canDismissLockscreen()) { + "canDismissLockscreen()" + } else { + "isKeyguardGoingAway" + }, + ) } } else if (primaryBouncerShowing) { if (!SceneContainerFlag.isEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index 6385b3cffbd4..2aaec8797cbe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor @@ -32,6 +33,7 @@ import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.Intra import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -39,7 +41,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton class KeyguardBlueprintInteractor @@ -48,7 +49,7 @@ constructor( private val keyguardBlueprintRepository: KeyguardBlueprintRepository, @Application private val applicationScope: CoroutineScope, shadeInteractor: ShadeInteractor, - private val configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, private val fingerprintPropertyInteractor: FingerprintPropertyInteractor, private val smartspaceSection: SmartspaceSection, ) : CoreStartable { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 2e0a160bfd16..26c286df01d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -49,6 +49,7 @@ import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.systemui.util.kotlin.sample @@ -85,7 +86,7 @@ constructor( private val repository: KeyguardRepository, powerInteractor: PowerInteractor, bouncerRepository: KeyguardBouncerRepository, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, shadeRepository: ShadeRepository, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, sceneInteractorProvider: Provider<SceneInteractor>, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 0dae17c594c8..cd62d5f3b6e1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -16,9 +16,11 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.logging.KeyguardLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.log.core.LogLevel.VERBOSE @@ -29,7 +31,6 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNoti import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.debounce -import com.android.app.tracing.coroutines.launchTraced as launch private val TAG = KeyguardTransitionAuditLogger::class.simpleName!! @@ -48,6 +49,7 @@ constructor( private val aodBurnInViewModel: AodBurnInViewModel, private val shadeInteractor: ShadeInteractor, private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, ) { fun start() { @@ -84,6 +86,18 @@ constructor( } scope.launch { + deviceEntryInteractor.isUnlocked.collect { + logger.log(TAG, VERBOSE, "DeviceEntry isUnlocked", it) + } + } + + scope.launch { + deviceEntryInteractor.isLockscreenEnabled.collect { + logger.log(TAG, VERBOSE, "DeviceEntry isLockscreenEnabled", it) + } + } + + scope.launch { keyguardInteractor.primaryBouncerShowing.collect { logger.log(TAG, VERBOSE, "Primary bouncer showing", it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index abd7f90bbf22..7d4d377c768a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.Log +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -33,7 +34,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch /** * Each TransitionInteractor is responsible for determining under which conditions to notify @@ -201,9 +201,18 @@ sealed class TransitionInteractor( scope.launch { keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect { if (!maybeHandleInsecurePowerGesture()) { + val lastStep = transitionInteractor.transitionState.value + val modeOnCanceled = + if (lastStep.to == KeyguardState.AOD) { + // Enabled smooth transition when double-tap camera cancels + // transition to AOD + TransitionModeOnCanceled.REVERSE + } else { + TransitionModeOnCanceled.RESET + } startTransitionTo( toState = KeyguardState.OCCLUDED, - modeOnCanceled = TransitionModeOnCanceled.RESET, + modeOnCanceled = modeOnCanceled, ownerReason = "keyguardInteractor.onCameraLaunchDetected", ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index 2c9884a63990..f473a82138e6 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 @@ -126,14 +126,7 @@ constructor( sceneInteractor.get().transitionState.flatMapLatestConflated { state -> when { state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) -> - (state as Transition).isUserInputOngoing.flatMapLatestConflated { - isUserInputOngoing -> - if (isUserInputOngoing) { - isDeviceEntered - } else { - flowOf(true) - } - } + isDeviceEntered state.isTransitioning(from = Scenes.Bouncer, to = Scenes.Gone) -> (state as Transition).progress.map { progress -> progress > diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index 36ef78e08c4f..faa497833e7b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -33,6 +33,7 @@ import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker @@ -47,7 +48,7 @@ class AodNotificationIconsSection @Inject constructor( private val context: Context, - private val configurationState: ConfigurationState, + @ShadeDisplayAware private val configurationState: ConfigurationState, private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, @@ -70,7 +71,7 @@ constructor( resources.getDimensionPixelSize(R.dimen.below_clock_padding_start_icons), 0, 0, - 0 + 0, ) setVisibility(View.INVISIBLE) } @@ -113,18 +114,18 @@ constructor( START, PARENT_ID, START, - context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal) + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal), ) connect( nicId, END, PARENT_ID, END, - context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal) + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal), ) constrainHeight( nicId, - context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height) + context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt index 4908dbdec61e..56e3125f7078 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shared.recents.utilities.Utilities.clamp import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -41,7 +42,7 @@ class AlternateBouncerUdfpsIconViewModel @Inject constructor( val context: Context, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel, fingerprintPropertyInteractor: FingerprintPropertyInteractor, @@ -85,10 +86,7 @@ constructor( } private val fgIconPadding: Flow<Int> = udfpsOverlayInteractor.iconPadding val fgViewModel: Flow<DeviceEntryForegroundViewModel.ForegroundIconViewModel> = - combine( - fgIconColor, - fgIconPadding, - ) { color, padding -> + combine(fgIconColor, fgIconPadding) { color, padding -> DeviceEntryForegroundViewModel.ForegroundIconViewModel( type = DeviceEntryIconView.IconType.FINGERPRINT, useAodVariant = false, @@ -100,12 +98,7 @@ constructor( val bgColor: Flow<Int> = deviceEntryBackgroundViewModel.color val bgAlpha: Flow<Float> = flowOf(1f) - data class IconLocation( - val left: Int, - val top: Int, - val right: Int, - val bottom: Int, - ) { + data class IconLocation(val left: Int, val top: Int, val right: Int, val bottom: Int) { val width = right - left val height = bottom - top } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index c78e0c9f5266..1c897237fe89 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -30,9 +30,11 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.ClockSize +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlin.math.max import kotlinx.coroutines.CoroutineScope @@ -42,8 +44,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -57,7 +61,7 @@ class AodBurnInViewModel constructor( @Application private val applicationScope: CoroutineScope, private val burnInInteractor: BurnInInteractor, - private val configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, @@ -164,9 +168,17 @@ constructor( private fun burnIn(params: BurnInParameters): Flow<BurnInModel> { return combine( - keyguardTransitionInteractor.transitionValue(KeyguardState.AOD).map { - Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it) - }, + merge( + keyguardTransitionInteractor.transition(Edge.create(to = KeyguardState.AOD)), + keyguardTransitionInteractor + .transition(Edge.create(from = KeyguardState.AOD)) + .map { it.copy(value = 1f - it.value) }, + keyguardTransitionInteractor + .transition(Edge.create(to = KeyguardState.LOCKSCREEN)) + .filter { it.from != KeyguardState.AOD } + .map { it.copy(value = 0f) }, + ) + .map { Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) }, burnInInteractor.burnIn( xDimenResourceId = R.dimen.burn_in_prevention_offset_x, yDimenResourceId = R.dimen.burn_in_prevention_offset_y, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt index 4c667c1c702d..12f9467c0f96 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -42,7 +43,7 @@ constructor( val context: Context, val deviceEntryIconViewModel: DeviceEntryIconViewModel, keyguardTransitionInteractor: KeyguardTransitionInteractor, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel, alternateBouncerToDozingTransitionViewModel: AlternateBouncerToDozingTransitionViewModel, aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt index 87c32a54438e..749f19315409 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlin.math.roundToInt import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,7 +46,7 @@ class DeviceEntryForegroundViewModel @Inject constructor( val context: Context, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, transitionInteractor: KeyguardTransitionInteractor, deviceEntryIconViewModel: DeviceEntryIconViewModel, @@ -106,12 +107,11 @@ constructor( } val viewModel: Flow<ForegroundIconViewModel> = - combine( - deviceEntryIconViewModel.iconType, - useAodIconVariant, + combine(deviceEntryIconViewModel.iconType, useAodIconVariant, color, padding) { + iconType, + useAodVariant, color, - padding, - ) { iconType, useAodVariant, color, padding -> + padding -> ForegroundIconViewModel( type = iconType, useAodVariant = useAodVariant, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt index 11ed52ac35b7..c9fdf7a31458 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -41,7 +42,7 @@ class DreamingToGlanceableHubTransitionViewModel @Inject constructor( animationFlow: KeyguardTransitionAnimationFlow, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, ) : DeviceEntryIconTransition { private val transitionAnimation = animationFlow @@ -49,15 +50,13 @@ constructor( duration = TO_GLANCEABLE_HUB_DURATION, edge = Edge.create(from = DREAMING, to = Scenes.Communal), ) - .setupWithoutSceneContainer( - edge = Edge.create(from = DREAMING, to = GLANCEABLE_HUB), - ) + .setupWithoutSceneContainer(edge = Edge.create(from = DREAMING, to = GLANCEABLE_HUB)) val dreamOverlayTranslationX: Flow<Float> = configurationInteractor .directionalDimensionPixelSize( LayoutDirection.LTR, - R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x + R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x, ) .flatMapLatest { translatePx -> transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt index f69f9969ea37..723fba6d976e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -41,7 +42,7 @@ class GlanceableHubToDreamingTransitionViewModel @Inject constructor( animationFlow: KeyguardTransitionAnimationFlow, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, ) : DeviceEntryIconTransition { private val transitionAnimation = @@ -50,9 +51,7 @@ constructor( duration = FROM_GLANCEABLE_HUB_DURATION, edge = Edge.create(from = Scenes.Communal, to = DREAMING), ) - .setupWithoutSceneContainer( - edge = Edge.create(from = GLANCEABLE_HUB, to = DREAMING), - ) + .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = DREAMING)) val dreamOverlayAlpha: Flow<Float> = transitionAnimation.sharedFlow( @@ -66,7 +65,7 @@ constructor( configurationInteractor .directionalDimensionPixelSize( LayoutDirection.LTR, - R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x + R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x, ) .flatMapLatest { translatePx: Int -> transitionAnimation.sharedFlow( @@ -74,7 +73,7 @@ constructor( onStep = { value -> -translatePx + value * translatePx }, interpolator = Interpolators.EMPHASIZED, onCancel = { -translatePx.toFloat() }, - name = "GLANCEABLE_HUB->LOCKSCREEN: dreamOverlayTranslationX" + name = "GLANCEABLE_HUB->LOCKSCREEN: dreamOverlayTranslationX", ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt index 7f3ef61d02c0..5a4d0689d209 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,7 +46,7 @@ import kotlinx.coroutines.flow.map class GlanceableHubToLockscreenTransitionViewModel @Inject constructor( - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index 36f684ee4759..5c79c0b5c1bb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.ui.SystemBarUtilsProxy @@ -50,7 +51,8 @@ constructor( aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, @get:VisibleForTesting val shadeInteractor: ShadeInteractor, private val systemBarUtils: SystemBarUtilsProxy, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, + // TODO: b/374267505 - Use ShadeDisplayAware resources here. @Main private val resources: Resources, ) { var burnInLayer: Layer? = null 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 ceae1b5e9038..bc3ef02a0ec5 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 @@ -31,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import javax.inject.Inject import javax.inject.Named @@ -53,7 +54,7 @@ constructor( burnInInteractor: BurnInInteractor, @Named(KeyguardQuickAffordancesCombinedViewModelModule.Companion.LOCKSCREEN_INSTANCE) shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, communalSceneInteractor: CommunalSceneInteractor, @Background private val backgroundDispatcher: CoroutineDispatcher, @@ -70,7 +71,7 @@ constructor( val visible: Flow<Boolean> = anyOf( keyguardInteractor.statusBarState.map { state -> state == StatusBarState.KEYGUARD }, - communalSceneInteractor.isCommunalVisible + communalSceneInteractor.isCommunalVisible, ) /** An observable for whether the indication area should be padded. */ @@ -85,7 +86,7 @@ constructor( } else { combine( keyguardBottomAreaViewModel.startButton, - keyguardBottomAreaViewModel.endButton + keyguardBottomAreaViewModel.endButton, ) { startButtonModel, endButtonModel -> startButtonModel.isVisible || endButtonModel.isVisible } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt index dd8ff8c4a052..acaa9b918da8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,7 +46,7 @@ import kotlinx.coroutines.flow.map class LockscreenToGlanceableHubTransitionViewModel @Inject constructor( - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt index 88e8968501dd..6565e31c2c6f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -40,7 +41,7 @@ class LockscreenToOccludedTransitionViewModel @Inject constructor( shadeDependentFlows: ShadeDependentFlows, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index 737bd7ac218d..d10970f28995 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -49,7 +50,7 @@ class OccludedToLockscreenTransitionViewModel @Inject constructor( deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, animationFlow: KeyguardTransitionAnimationFlow, keyguardInteractor: KeyguardInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, @@ -104,7 +105,7 @@ constructor( !isOccluded && keyguardTransitionInteractor.getCurrentState() == OCCLUDED } - .map { 0f } + .map { 0f }, ) val deviceEntryBackgroundViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt index a33685b61237..d38e507082b9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt @@ -28,6 +28,7 @@ import androidx.annotation.WorkerThread import androidx.media.utils.MediaConstants import androidx.media3.common.Player import androidx.media3.session.CommandButton +import androidx.media3.session.MediaController as Media3Controller import androidx.media3.session.SessionCommand import androidx.media3.session.SessionToken import com.android.systemui.dagger.SysUISingleton @@ -82,10 +83,19 @@ constructor( // Build button info val buttons = suspendCancellableCoroutine { continuation -> // Media3Controller methods must always be called from a specific looper - handler.post { - val result = getMedia3Actions(packageName, m3controller, token) - m3controller.release() - continuation.resumeWith(Result.success(result)) + val runnable = Runnable { + try { + val result = getMedia3Actions(packageName, m3controller, token) + continuation.resumeWith(Result.success(result)) + } finally { + m3controller.release() + } + } + handler.post(runnable) + continuation.invokeOnCancellation { + // Ensure controller is released, even if loading was cancelled partway through + handler.post(m3controller::release) + handler.removeCallbacks(runnable) } } return buttons @@ -95,7 +105,7 @@ constructor( @WorkerThread private fun getMedia3Actions( packageName: String, - m3controller: androidx.media3.session.MediaController, + m3controller: Media3Controller, token: SessionToken, ): MediaButton? { Assert.isNotMainThread() @@ -197,7 +207,7 @@ constructor( * @return A [MediaAction] representing the first supported command, or null if not supported */ private fun getStandardAction( - controller: androidx.media3.session.MediaController, + controller: Media3Controller, token: SessionToken, vararg commands: @Player.Command Int, ): MediaAction? { @@ -304,37 +314,40 @@ constructor( bgScope.launch { val controller = controllerFactory.create(token, looper) handler.post { - when (command) { - Player.COMMAND_PLAY_PAUSE -> { - if (controller.isPlaying) controller.pause() else controller.play() - } + try { + when (command) { + Player.COMMAND_PLAY_PAUSE -> { + if (controller.isPlaying) controller.pause() else controller.play() + } - Player.COMMAND_SEEK_TO_PREVIOUS -> controller.seekToPrevious() - Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> - controller.seekToPreviousMediaItem() + Player.COMMAND_SEEK_TO_PREVIOUS -> controller.seekToPrevious() + Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> + controller.seekToPreviousMediaItem() - Player.COMMAND_SEEK_TO_NEXT -> controller.seekToNext() - Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> controller.seekToNextMediaItem() - Player.COMMAND_INVALID -> { - if ( - customAction != null && - customAction!!.sessionCommand != null && - controller.isSessionCommandAvailable( - customAction!!.sessionCommand!! - ) - ) { - controller.sendCustomCommand( - customAction!!.sessionCommand!!, - customAction!!.extras, - ) - } else { - logger.logMedia3UnsupportedCommand("$command, action $customAction") + Player.COMMAND_SEEK_TO_NEXT -> controller.seekToNext() + Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> controller.seekToNextMediaItem() + Player.COMMAND_INVALID -> { + if (customAction?.sessionCommand != null) { + val sessionCommand = customAction.sessionCommand!! + if (controller.isSessionCommandAvailable(sessionCommand)) { + controller.sendCustomCommand( + sessionCommand, + customAction.extras, + ) + } else { + logger.logMedia3UnsupportedCommand( + "$sessionCommand, action $customAction" + ) + } + } else { + logger.logMedia3UnsupportedCommand("$command, action $customAction") + } } + else -> logger.logMedia3UnsupportedCommand(command.toString()) } - - else -> logger.logMedia3UnsupportedCommand(command.toString()) + } finally { + controller.release() } - controller.release() } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt index 741f52998782..d815852b790f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt @@ -35,7 +35,12 @@ open class MediaControllerFactory @Inject constructor(private val context: Conte return MediaController(context, token) } - /** Creates a new [Media3Controller] from a [SessionToken] */ + /** + * Creates a new [Media3Controller] from the media3 [SessionToken]. + * + * @param token The token for the session + * @param looper The looper that will be used for this controller's operations + */ open suspend fun create(token: SessionToken, looper: Looper): Media3Controller { return Media3Controller.Builder(context, token) .setApplicationLooper(looper) diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 36a9fb3eb753..45a3a8ce60c4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -27,17 +27,12 @@ import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager; import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager; import com.android.systemui.media.controls.ui.view.MediaHost; import com.android.systemui.media.dream.dagger.MediaComplicationComponent; -import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; -import com.android.systemui.media.taptotransfer.MediaTttFlags; import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogBuffer; import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogBuffer; -import dagger.Lazy; import dagger.Module; import dagger.Provides; -import java.util.Optional; - import javax.inject.Named; /** Dagger module for the media package. */ @@ -132,16 +127,4 @@ public interface MediaModule { static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) { return factory.create("MediaTttReceiver", 20); } - - /** */ - @Provides - @SysUISingleton - static Optional<MediaTttCommandLineHelper> providesMediaTttCommandLineHelper( - MediaTttFlags mediaTttFlags, - Lazy<MediaTttCommandLineHelper> helperLazy) { - if (!mediaTttFlags.isMediaTttEnabled()) { - return Optional.empty(); - } - return Optional.of(helperLazy.get()); - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 92db804d2730..1204cde19c76 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -43,7 +43,6 @@ import com.android.systemui.common.ui.binder.TintedIconViewBinder import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager -import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.media.taptotransfer.common.MediaTttIcon import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.res.R @@ -68,25 +67,27 @@ import javax.inject.Inject * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator. */ @SysUISingleton -open class MediaTttChipControllerReceiver @Inject constructor( - private val commandQueue: CommandQueue, - context: Context, - logger: MediaTttReceiverLogger, - viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, - @Main mainExecutor: DelayableExecutor, - accessibilityManager: AccessibilityManager, - configurationController: ConfigurationController, - dumpManager: DumpManager, - powerManager: PowerManager, - @Main private val mainHandler: Handler, - private val mediaTttFlags: MediaTttFlags, - private val uiEventLogger: MediaTttReceiverUiEventLogger, - private val viewUtil: ViewUtil, - wakeLockBuilder: WakeLock.Builder, - systemClock: SystemClock, - private val rippleController: MediaTttReceiverRippleController, - private val temporaryViewUiEventLogger: TemporaryViewUiEventLogger, -) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttReceiverLogger>( +open class MediaTttChipControllerReceiver +@Inject +constructor( + private val commandQueue: CommandQueue, + context: Context, + logger: MediaTttReceiverLogger, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, + @Main mainExecutor: DelayableExecutor, + accessibilityManager: AccessibilityManager, + configurationController: ConfigurationController, + dumpManager: DumpManager, + powerManager: PowerManager, + @Main private val mainHandler: Handler, + private val uiEventLogger: MediaTttReceiverUiEventLogger, + private val viewUtil: ViewUtil, + wakeLockBuilder: WakeLock.Builder, + systemClock: SystemClock, + private val rippleController: MediaTttReceiverRippleController, + private val temporaryViewUiEventLogger: TemporaryViewUiEventLogger, +) : + TemporaryViewDisplayController<ChipReceiverInfo, MediaTttReceiverLogger>( context, logger, viewCaptureAwareWindowManager, @@ -99,36 +100,43 @@ open class MediaTttChipControllerReceiver @Inject constructor( wakeLockBuilder, systemClock, temporaryViewUiEventLogger, -) { + ) { @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - override val windowLayoutParams = commonWindowLayoutParams.apply { - gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL) - // Params below are needed for the ripple to work correctly - width = WindowManager.LayoutParams.MATCH_PARENT - height = WindowManager.LayoutParams.MATCH_PARENT - layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - fitInsetsTypes = 0 // Ignore insets from all system bars - } + override val windowLayoutParams = + commonWindowLayoutParams.apply { + gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL) + // Params below are needed for the ripple to work correctly + width = WindowManager.LayoutParams.MATCH_PARENT + height = WindowManager.LayoutParams.MATCH_PARENT + layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + fitInsetsTypes = 0 // Ignore insets from all system bars + } // Value animator that controls the bouncing animation of views. - private val bounceAnimator = ValueAnimator.ofFloat(0f, 1f).apply { - repeatCount = ValueAnimator.INFINITE - repeatMode = ValueAnimator.REVERSE - duration = ICON_BOUNCE_ANIM_DURATION - } + private val bounceAnimator = + ValueAnimator.ofFloat(0f, 1f).apply { + repeatCount = ValueAnimator.INFINITE + repeatMode = ValueAnimator.REVERSE + duration = ICON_BOUNCE_ANIM_DURATION + } - private val commandQueueCallbacks = object : CommandQueue.Callbacks { - override fun updateMediaTapToTransferReceiverDisplay( - @StatusBarManager.MediaTransferReceiverState displayState: Int, - routeInfo: MediaRoute2Info, - appIcon: Icon?, - appName: CharSequence? - ) { - this@MediaTttChipControllerReceiver.updateMediaTapToTransferReceiverDisplay( - displayState, routeInfo, appIcon, appName - ) + private val commandQueueCallbacks = + object : CommandQueue.Callbacks { + override fun updateMediaTapToTransferReceiverDisplay( + @StatusBarManager.MediaTransferReceiverState displayState: Int, + routeInfo: MediaRoute2Info, + appIcon: Icon?, + appName: CharSequence?, + ) { + this@MediaTttChipControllerReceiver.updateMediaTapToTransferReceiverDisplay( + displayState, + routeInfo, + appIcon, + appName, + ) + } } - } // A map to store instance id per route info id. private var instanceMap: MutableMap<String, InstanceId> = mutableMapOf() @@ -139,7 +147,7 @@ open class MediaTttChipControllerReceiver @Inject constructor( @StatusBarManager.MediaTransferReceiverState displayState: Int, routeInfo: MediaRoute2Info, appIcon: Icon?, - appName: CharSequence? + appName: CharSequence?, ) { val chipState: ChipStateReceiver? = ChipStateReceiver.getReceiverStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" @@ -150,8 +158,8 @@ open class MediaTttChipControllerReceiver @Inject constructor( return } - val instanceId: InstanceId = instanceMap[routeInfo.id] - ?: temporaryViewUiEventLogger.getNewInstanceId() + val instanceId: InstanceId = + instanceMap[routeInfo.id] ?: temporaryViewUiEventLogger.getNewInstanceId() uiEventLogger.logReceiverStateChange(chipState, instanceId) if (chipState != ChipStateReceiver.CLOSE_TO_SENDER) { @@ -175,53 +183,51 @@ open class MediaTttChipControllerReceiver @Inject constructor( } appIcon.loadDrawableAsync( - context, - Icon.OnDrawableLoadedListener { drawable -> - displayView( - ChipReceiverInfo( - routeInfo, - drawable, - appName, - id = routeInfo.id, - instanceId = instanceId, - ) + context, + Icon.OnDrawableLoadedListener { drawable -> + displayView( + ChipReceiverInfo( + routeInfo, + drawable, + appName, + id = routeInfo.id, + instanceId = instanceId, ) - }, - // Notify the listener on the main handler since the listener will update - // the UI. - mainHandler + ) + }, + // Notify the listener on the main handler since the listener will update + // the UI. + mainHandler, ) } override fun start() { super.start() - if (mediaTttFlags.isMediaTttEnabled()) { - commandQueue.addCallback(commandQueueCallbacks) - } + commandQueue.addCallback(commandQueueCallbacks) registerListener(displayListener) } override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) { val packageName: String? = newInfo.routeInfo.clientPackageName - var iconInfo = MediaTttUtils.getIconInfoFromPackageName( - context, - packageName, - isReceiver = true, - ) { - packageName?.let { logger.logPackageNotFound(it) } - } + var iconInfo = + MediaTttUtils.getIconInfoFromPackageName(context, packageName, isReceiver = true) { + packageName?.let { logger.logPackageNotFound(it) } + } if (newInfo.appNameOverride != null) { - iconInfo = iconInfo.copy( - contentDescription = ContentDescription.Loaded(newInfo.appNameOverride.toString()) - ) + iconInfo = + iconInfo.copy( + contentDescription = + ContentDescription.Loaded(newInfo.appNameOverride.toString()) + ) } if (newInfo.appIconDrawableOverride != null) { - iconInfo = iconInfo.copy( - icon = MediaTttIcon.Loaded(newInfo.appIconDrawableOverride), - isAppIcon = true, - ) + iconInfo = + iconInfo.copy( + icon = MediaTttIcon.Loaded(newInfo.appIconDrawableOverride), + isAppIcon = true, + ) } val iconPadding = @@ -298,16 +304,14 @@ open class MediaTttChipControllerReceiver @Inject constructor( alphaDuration: Long = ICON_ALPHA_ANIM_DURATION, onAnimationEnd: Runnable? = null, ) { - view.animate() + view + .animate() .translationYBy(translationYBy) .setInterpolator(interpolator) .setDuration(translationDuration) .withEndAction { onAnimationEnd?.run() } .start() - view.animate() - .alpha(alphaEndValue) - .setDuration(alphaDuration) - .start() + view.animate().alpha(alphaEndValue).setDuration(alphaDuration).start() } /** Returns the amount that the chip will be translated by in its intro animation. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index 3e6d46c00df9..6ca04710d74c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -28,7 +28,6 @@ import com.android.systemui.Dumpable import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager -import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.res.R import com.android.systemui.statusbar.CommandQueue @@ -53,7 +52,6 @@ constructor( private val context: Context, private val dumpManager: DumpManager, private val logger: MediaTttSenderLogger, - private val mediaTttFlags: MediaTttFlags, private val uiEventLogger: MediaTttSenderUiEventLogger, ) : CoreStartable, Dumpable { @@ -68,27 +66,25 @@ constructor( override fun updateMediaTapToTransferSenderDisplay( @StatusBarManager.MediaTransferSenderState displayState: Int, routeInfo: MediaRoute2Info, - undoCallback: IUndoMediaTransferCallback? + undoCallback: IUndoMediaTransferCallback?, ) { this@MediaTttSenderCoordinator.updateMediaTapToTransferSenderDisplay( displayState, routeInfo, - undoCallback + undoCallback, ) } } override fun start() { - if (mediaTttFlags.isMediaTttEnabled()) { - commandQueue.addCallback(commandQueueCallbacks) - dumpManager.registerNormalDumpable(this) - } + commandQueue.addCallback(commandQueueCallbacks) + dumpManager.registerNormalDumpable(this) } private fun updateMediaTapToTransferSenderDisplay( @StatusBarManager.MediaTransferSenderState displayState: Int, routeInfo: MediaRoute2Info, - undoCallback: IUndoMediaTransferCallback? + undoCallback: IUndoMediaTransferCallback?, ) { val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" @@ -107,7 +103,7 @@ constructor( // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state. logger.logInvalidStateTransitionError( currentState = currentStateForId?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name, - chipState.name + chipState.name, ) return } @@ -126,7 +122,7 @@ constructor( // still be able to see the status of the transfer. logger.logRemovalBypass( removalReason, - bypassReason = "transferStatus=${currentStateForId.transferStatus.name}" + bypassReason = "transferStatus=${currentStateForId.transferStatus.name}", ) return } @@ -139,14 +135,7 @@ constructor( logger.logStateMap(stateMap) chipbarCoordinator.registerListener(displayListener) chipbarCoordinator.displayView( - createChipbarInfo( - chipState, - routeInfo, - undoCallback, - context, - logger, - instanceId, - ) + createChipbarInfo(chipState, routeInfo, undoCallback, context, logger, instanceId) ) } } @@ -245,10 +234,7 @@ constructor( ) } - return ChipbarEndItem.Button( - Text.Resource(R.string.media_transfer_undo), - onClickListener, - ) + return ChipbarEndItem.Button(Text.Resource(R.string.media_transfer_undo), onClickListener) } private val displayListener = diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index bf2aa7efc0c4..56885c3eea9f 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -18,7 +18,7 @@ package com.android.systemui.mediaprojection.appselector.data import android.annotation.ColorInt import android.annotation.UserIdInt -import android.app.ActivityManager.RecentTaskInfo +import android.app.TaskInfo import android.content.ComponentName import com.android.wm.shell.shared.split.SplitBounds @@ -34,7 +34,7 @@ data class RecentTask( val splitBounds: SplitBounds?, ) { constructor( - taskInfo: RecentTaskInfo, + taskInfo: TaskInfo, isForegroundTask: Boolean, userType: UserType, splitBounds: SplitBounds? = null diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index 82e58cc7f1d9..d94424c59376 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -23,7 +23,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.settings.UserTracker import com.android.systemui.util.kotlin.getOrNull import com.android.wm.shell.recents.RecentTasks -import com.android.wm.shell.shared.GroupedRecentTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo import java.util.Optional import java.util.concurrent.Executor import javax.inject.Inject @@ -51,7 +51,7 @@ constructor( override suspend fun loadRecentTasks(): List<RecentTask> = withContext(coroutineDispatcher) { - val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() + val groupedTasks: List<GroupedTaskInfo> = recents?.getTasks() ?: emptyList() // Note: the returned task list is from the most-recent to least-recent order. // When opening the app selector in full screen, index 0 will be just the app selector // activity and a null second task, so the foreground task will be index 1, but when @@ -86,7 +86,7 @@ constructor( } } - private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> = + private suspend fun RecentTasks.getTasks(): List<GroupedTaskInfo> = suspendCoroutine { continuation -> getRecentTasks( Integer.MAX_VALUE, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index 47dacae6e0a0..2fda2013d6f5 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -57,7 +57,6 @@ import android.view.Display; import android.view.Window; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; import com.android.systemui.mediaprojection.MediaProjectionServiceHelper; import com.android.systemui.mediaprojection.MediaProjectionUtils; @@ -187,11 +186,9 @@ public class MediaProjectionPermissionActivity extends Activity { return; } - if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) { - if (showScreenCaptureDisabledDialogIfNeeded()) { - finishAsCancelled(); - return; - } + if (showScreenCaptureDisabledDialogIfNeeded()) { + finishAsCancelled(); + return; } final String appName = extractAppName(aInfo, packageManager); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index b019c136b6ca..a3b7590117c1 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -406,7 +406,7 @@ public class NavigationBarControllerImpl implements if (navBar != null) { navBar.checkNavBarModes(); } else { - mTaskbarDelegate.checkNavBarModes(); + mTaskbarDelegate.checkNavBarModes(displayId); } } @@ -416,7 +416,7 @@ public class NavigationBarControllerImpl implements if (navBar != null) { navBar.finishBarAnimations(); } else { - mTaskbarDelegate.finishBarAnimations(); + mTaskbarDelegate.finishBarAnimations(displayId); } } @@ -426,7 +426,7 @@ public class NavigationBarControllerImpl implements if (navBar != null) { navBar.touchAutoDim(); } else { - mTaskbarDelegate.touchAutoDim(); + mTaskbarDelegate.touchAutoDim(displayId); } } @@ -436,7 +436,7 @@ public class NavigationBarControllerImpl implements if (navBar != null) { navBar.transitionTo(barMode, animate); } else { - mTaskbarDelegate.transitionTo(barMode, animate); + mTaskbarDelegate.transitionTo(displayId, barMode, animate); } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 1216a8879751..2a3aeae2a550 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -159,7 +159,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() { @Override public void synchronizeState() { - checkNavBarModes(); + checkNavBarModes(mDisplayId); } @Override @@ -220,6 +220,16 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler(); } + @Override + public void onDisplayReady(int displayId) { + CommandQueue.Callbacks.super.onDisplayReady(displayId); + } + + @Override + public void onDisplayRemoved(int displayId) { + CommandQueue.Callbacks.super.onDisplayRemoved(displayId); + } + // Separated into a method to keep setDependencies() clean/readable. private LightBarTransitionsController createLightBarTransitionsController() { @@ -349,31 +359,31 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } } - void checkNavBarModes() { + void checkNavBarModes(int displayId) { if (mOverviewProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().checkNavBarModes(); + mOverviewProxyService.getProxy().checkNavBarModes(displayId); } catch (RemoteException e) { Log.e(TAG, "checkNavBarModes() failed", e); } } - void finishBarAnimations() { + void finishBarAnimations(int displayId) { if (mOverviewProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().finishBarAnimations(); + mOverviewProxyService.getProxy().finishBarAnimations(displayId); } catch (RemoteException e) { Log.e(TAG, "finishBarAnimations() failed", e); } } - void touchAutoDim() { + void touchAutoDim(int displayId) { if (mOverviewProxyService.getProxy() == null) { return; } @@ -382,19 +392,19 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, int state = mStatusBarStateController.getState(); boolean shouldReset = state != StatusBarState.KEYGUARD && state != StatusBarState.SHADE_LOCKED; - mOverviewProxyService.getProxy().touchAutoDim(shouldReset); + mOverviewProxyService.getProxy().touchAutoDim(displayId, shouldReset); } catch (RemoteException e) { Log.e(TAG, "touchAutoDim() failed", e); } } - void transitionTo(@BarTransitions.TransitionMode int barMode, boolean animate) { + void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode, boolean animate) { if (mOverviewProxyService.getProxy() == null) { return; } try { - mOverviewProxyService.getProxy().transitionTo(barMode, animate); + mOverviewProxyService.getProxy().transitionTo(displayId, barMode, animate); } catch (RemoteException e) { Log.e(TAG, "transitionTo() failed, barMode: " + barMode, e); } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index d0f6f7961889..1fa5baaa21ae 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -118,9 +118,7 @@ constructor( getUserForHandlingNotesTaking(entryPoint) } activityContext.startActivityAsUser( - Intent(Intent.ACTION_MANAGE_DEFAULT_APP).apply { - putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES) - }, + createNotesRoleHolderSettingsIntent(), user ) } @@ -399,6 +397,10 @@ constructor( * @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE */ const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package" + + /** Returns notes role holder settings intent. */ + fun createNotesRoleHolderSettingsIntent() = Intent(Intent.ACTION_MANAGE_DEFAULT_APP). + putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES) } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt index 442000281862..2d62c1067401 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt @@ -44,4 +44,7 @@ enum class NoteTaskEntryPoint { /** @see [NoteTaskInitializer.callbacks] */ KEYBOARD_SHORTCUT, + + /** @see [NotesTile] */ + QS_NOTES_TILE, } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt index a79057e5464b..f152b01360df 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt @@ -19,6 +19,7 @@ import com.android.internal.logging.UiEvent import com.android.internal.logging.UiEventLogger import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT +import com.android.systemui.notetask.NoteTaskEntryPoint.QS_NOTES_TILE import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT @@ -43,45 +44,47 @@ class NoteTaskEventLogger @Inject constructor(private val uiEventLogger: UiEvent /** Logs a [NoteTaskInfo] as an **open** [NoteTaskUiEvent], including package name and uid. */ fun logNoteTaskOpened(info: NoteTaskInfo) { val event = - when (info.entryPoint) { - TAIL_BUTTON -> { - if (info.isKeyguardLocked) { - NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED - } else { - NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON - } + when (info.entryPoint) { + TAIL_BUTTON -> { + if (info.isKeyguardLocked) { + NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED + } else { + NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON } + } - WIDGET_PICKER_SHORTCUT, - WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE -> NOTE_OPENED_VIA_SHORTCUT + WIDGET_PICKER_SHORTCUT, + WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE -> NOTE_OPENED_VIA_SHORTCUT - QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE - APP_CLIPS, - KEYBOARD_SHORTCUT, - null -> return - } + QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE + APP_CLIPS, + KEYBOARD_SHORTCUT, + QS_NOTES_TILE, // TODO(b/376640872): Add logging for QS Tile entry point. + null -> return + } uiEventLogger.log(event, info.uid, info.packageName) } /** Logs a [NoteTaskInfo] as a **closed** [NoteTaskUiEvent], including package name and uid. */ fun logNoteTaskClosed(info: NoteTaskInfo) { val event = - when (info.entryPoint) { - TAIL_BUTTON -> { - if (info.isKeyguardLocked) { - NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED - } else { - NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON - } + when (info.entryPoint) { + TAIL_BUTTON -> { + if (info.isKeyguardLocked) { + NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED + } else { + NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON } - - WIDGET_PICKER_SHORTCUT, - WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE, - QUICK_AFFORDANCE, - APP_CLIPS, - KEYBOARD_SHORTCUT, - null -> return } + + WIDGET_PICKER_SHORTCUT, + WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE, + QUICK_AFFORDANCE, + APP_CLIPS, + KEYBOARD_SHORTCUT, + QS_NOTES_TILE, + null -> return + } uiEventLogger.log(event, info.uid, info.packageName) } @@ -90,19 +93,20 @@ class NoteTaskEventLogger @Inject constructor(private val uiEventLogger: UiEvent @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.") NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294), - - @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was unlocked.") // ktlint-disable max-line-length + @UiEvent( + doc = + "User opened a note by pressing the stylus tail button while the screen was unlocked." + ) NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295), - - @UiEvent(doc = "User opened a note by pressing the stylus tail button while the screen was locked.") // ktlint-disable max-line-length + @UiEvent( + doc = + "User opened a note by pressing the stylus tail button while the screen was locked." + ) NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296), - @UiEvent(doc = "User opened a note by tapping on an app shortcut.") NOTE_OPENED_VIA_SHORTCUT(1297), - @UiEvent(doc = "Note closed via a tail button while device is unlocked") NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON(1311), - @UiEvent(doc = "Note closed via a tail button while device is locked") NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1312); diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt index c7aae3ca788e..a1c5c9c682c3 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt @@ -27,11 +27,27 @@ import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesSe import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.NotesTile +import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.notes.domain.NotesTileMapper +import com.android.systemui.qs.tiles.impl.notes.domain.interactor.NotesTileDataInteractor +import com.android.systemui.qs.tiles.impl.notes.domain.interactor.NotesTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.res.R import dagger.Binds import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey /** Compose all dependencies required by Note Task feature. */ @Module(includes = [NoteTaskQuickAffordanceModule::class]) @@ -54,8 +70,22 @@ interface NoteTaskModule { @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)] fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity + @Binds + @IntoMap + @StringKey(NOTES_TILE_SPEC) + fun provideNotesAvailabilityInteractor( + impl: NotesTileDataInteractor + ): QSTileAvailabilityInteractor + + @Binds + @IntoMap + @StringKey(NotesTile.TILE_SPEC) + fun bindNotesTile(notesTile: NotesTile): QSTileImpl<*> + companion object { + const val NOTES_TILE_SPEC = "notes" + @[Provides NoteTaskEnabledKey] fun provideIsNoteTaskEnabled( featureFlags: FeatureFlags, @@ -65,5 +95,37 @@ interface NoteTaskModule { val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS) return isRoleAvailable && isFeatureEnabled } + + /** Inject NotesTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(NOTES_TILE_SPEC) + fun provideNotesTileViewModel( + factory: QSTileViewModelFactory.Static<NotesTileModel>, + mapper: NotesTileMapper, + stateInteractor: NotesTileDataInteractor, + userActionInteractor: NotesTileUserActionInteractor, + ): QSTileViewModel = + factory.create( + TileSpec.create(NOTES_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + + @Provides + @IntoMap + @StringKey(NOTES_TILE_SPEC) + fun provideNotesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(NOTES_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.ic_qs_notes, + labelRes = R.string.quick_settings_notes_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.UTILITIES, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt index 63bfbd1dc1ba..195b0cebe2eb 100644 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.notifications.ui.viewmodel import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.UserActionResult.HideOverlay @@ -38,7 +37,7 @@ class NotificationsShadeOverlayActionsViewModel @AssistedInject constructor() : mapOf( Swipe.Up to HideOverlay(Overlays.NotificationsShade), Back to HideOverlay(Overlays.NotificationsShade), - Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to + Swipe.Down(fromSource = SceneContainerEdge.TopRight) to ReplaceByOverlay(Overlays.QuickSettingsShade), ) ) diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt index 7178d095d230..4fe63379aed4 100644 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt @@ -16,19 +16,23 @@ package com.android.systemui.notifications.ui.viewmodel +import androidx.compose.runtime.getValue +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import com.android.app.tracing.coroutines.launchTraced as launch /** * Models UI state used to render the content of the notifications shade overlay. @@ -43,10 +47,32 @@ constructor( val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, val sceneInteractor: SceneInteractor, private val shadeInteractor: ShadeInteractor, + activeNotificationsInteractor: ActiveNotificationsInteractor, ) : ExclusiveActivatable() { + private val hydrator = Hydrator("NotificationsShadeOverlayContentViewModel.hydrator") + + val showHeader: Boolean by + hydrator.hydratedStateOf( + traceName = "showHeader", + initialValue = + shouldShowHeader( + isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value, + areAnyNotificationsPresent = + activeNotificationsInteractor.areAnyNotificationsPresentValue, + ), + source = + combine( + shadeInteractor.isShadeLayoutWide, + activeNotificationsInteractor.areAnyNotificationsPresent, + this::shouldShowHeader, + ), + ) + override suspend fun onActivated(): Nothing { coroutineScope { + launch { hydrator.activate() } + launch { sceneInteractor.currentScene.collect { currentScene -> when (currentScene) { @@ -77,6 +103,13 @@ constructor( shadeInteractor.collapseNotificationsShade(loggingReason = "shade scrim clicked") } + private fun shouldShowHeader( + isShadeLayoutWide: Boolean, + areAnyNotificationsPresent: Boolean, + ): Boolean { + return !isShadeLayoutWide && areAnyNotificationsPresent + } + @AssistedFactory interface Factory { fun create(): NotificationsShadeOverlayContentViewModel diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt index 11854d9317c9..398ace4b67f4 100644 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.notifications.ui.viewmodel import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay @@ -40,7 +39,7 @@ class NotificationsShadeUserActionsViewModel @AssistedInject constructor() : mapOf( Back to SceneFamilies.Home, Swipe.Up to SceneFamilies.Home, - Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to + Swipe.Down(fromSource = SceneContainerEdge.TopRight) to ReplaceByOverlay(Overlays.QuickSettingsShade), ) ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index bacff99fe048..51ff598c580b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -31,6 +31,7 @@ import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner +import androidx.annotation.VisibleForTesting import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween @@ -41,14 +42,14 @@ import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -59,6 +60,7 @@ 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.graphics.graphicsLayer import androidx.compose.ui.input.pointer.PointerEventPass @@ -75,7 +77,6 @@ import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import androidx.compose.ui.util.fastRoundToInt import androidx.compose.ui.viewinterop.AndroidView @@ -97,6 +98,7 @@ import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf import com.android.compose.theme.PlatformTheme import com.android.systemui.Dumpable +import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dump.DumpManager import com.android.systemui.lifecycle.repeatWhenAttached @@ -107,6 +109,7 @@ import com.android.systemui.plugins.qs.QSContainerController import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings import com.android.systemui.qs.composefragment.SceneKeys.QuickSettings import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey +import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams import com.android.systemui.qs.composefragment.ui.notificationScrimClip import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings @@ -115,8 +118,8 @@ import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.footer.ui.compose.FooterActions import com.android.systemui.qs.panels.ui.compose.EditMode import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings +import com.android.systemui.qs.panels.ui.compose.TileGrid import com.android.systemui.qs.shared.ui.ElementKeys -import com.android.systemui.qs.ui.composable.QuickSettingsLayout import com.android.systemui.qs.ui.composable.QuickSettingsShade import com.android.systemui.qs.ui.composable.QuickSettingsTheme import com.android.systemui.res.R @@ -195,6 +198,7 @@ constructor( val context = inflater.context val composeView = ComposeView(context).apply { + id = R.id.quick_settings_container repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { setViewTreeOnBackPressedDispatcherOwner( @@ -239,7 +243,6 @@ constructor( visible = viewModel.isQsVisible, modifier = Modifier.graphicsLayer { alpha = viewModel.viewAlpha } - .windowInsetsPadding(WindowInsets.navigationBars) // Clipping before translation to match QSContainerImpl.onDraw .offset { IntOffset(x = 0, y = viewModel.viewTranslationY.fastRoundToInt()) @@ -299,7 +302,7 @@ constructor( transitions = transitions { from(QuickQuickSettings, QuickSettings) { - quickQuickSettingsToQuickSettings(viewModel::inFirstPage::get) + quickQuickSettingsToQuickSettings(viewModel::animateTilesExpansion::get) } }, ) @@ -596,8 +599,21 @@ constructor( } .padding(top = { qqsPadding }, bottom = { bottomPadding }) ) { + val Tiles = + @Composable { + QuickQuickSettings( + viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel + ) + } + val Media = + @Composable { + if (viewModel.qqsMediaVisible) { + MediaObject(mediaHost = viewModel.qqsMediaHost) + } + } + if (viewModel.isQsEnabled) { - Column( + Box( modifier = Modifier.collapseExpandSemanticAction( stringResource( @@ -608,16 +624,13 @@ constructor( horizontal = { QuickSettingsShade.Dimensions.Padding.roundToPx() } - ), - verticalArrangement = - spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), + ) ) { - QuickQuickSettings( - viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel + QuickQuickSettingsLayout( + tiles = Tiles, + media = Media, + mediaInRow = viewModel.qqsMediaInRow, ) - if (viewModel.qqsMediaVisible) { - MediaObject(mediaHost = viewModel.qqsMediaHost) - } } } } @@ -657,23 +670,58 @@ constructor( .verticalScroll(scrollState) .sysuiResTag(ResIdTags.qsScroll) ) { + val containerViewModel = viewModel.containerViewModel Spacer( modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() } ) - QuickSettingsLayout( - viewModel = viewModel.containerViewModel, - modifier = Modifier.sysuiResTag(ResIdTags.quickSettingsPanel), - ) - Spacer(modifier = Modifier.height(8.dp)) - if (viewModel.qsMediaVisible) { - MediaObject( - mediaHost = viewModel.qsMediaHost, - modifier = - Modifier.padding( - horizontal = { - QuickSettingsShade.Dimensions.Padding.roundToPx() - } - ), + val BrightnessSlider = + @Composable { + BrightnessSliderContainer( + viewModel = containerViewModel.brightnessSliderViewModel, + modifier = + Modifier.fillMaxWidth() + .height( + QuickSettingsShade.Dimensions.BrightnessSliderHeight + ), + ) + } + val TileGrid = + @Composable { + Box { + GridAnchor() + TileGrid( + viewModel = containerViewModel.tileGridViewModel, + modifier = + Modifier.fillMaxWidth() + .heightIn( + max = + QuickSettingsShade.Dimensions.GridMaxHeight + ), + containerViewModel.editModeViewModel::startEditing, + ) + } + } + val Media = + @Composable { + if (viewModel.qsMediaVisible) { + MediaObject(mediaHost = viewModel.qsMediaHost) + } + } + Box( + modifier = + Modifier.fillMaxWidth() + .sysuiResTag(ResIdTags.quickSettingsPanel) + .padding( + top = QuickSettingsShade.Dimensions.Padding, + start = QuickSettingsShade.Dimensions.Padding, + end = QuickSettingsShade.Dimensions.Padding, + ) + ) { + QuickSettingsLayout( + brightness = BrightnessSlider, + tiles = TileGrid, + media = Media, + mediaInRow = viewModel.qsMediaInRow, ) } } @@ -957,6 +1005,63 @@ private fun MediaObject(mediaHost: MediaHost, modifier: Modifier = Modifier) { } } +@Composable +@VisibleForTesting +fun QuickQuickSettingsLayout( + tiles: @Composable () -> Unit, + media: @Composable () -> Unit, + mediaInRow: Boolean, +) { + if (mediaInRow) { + Row( + horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), + verticalAlignment = Alignment.CenterVertically, + ) { + Box(modifier = Modifier.weight(1f)) { tiles() } + Box(modifier = Modifier.weight(1f)) { media() } + } + } else { + Column(verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical))) { + tiles() + media() + } + } +} + +@Composable +@VisibleForTesting +fun QuickSettingsLayout( + brightness: @Composable () -> Unit, + tiles: @Composable () -> Unit, + media: @Composable () -> Unit, + mediaInRow: Boolean, +) { + if (mediaInRow) { + Column( + verticalArrangement = spacedBy(QuickSettingsShade.Dimensions.Padding), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + brightness() + Row( + horizontalArrangement = spacedBy(QuickSettingsShade.Dimensions.Padding), + verticalAlignment = Alignment.CenterVertically, + ) { + Box(modifier = Modifier.weight(1f)) { tiles() } + Box(modifier = Modifier.weight(1f)) { media() } + } + } + } else { + Column( + verticalArrangement = spacedBy(QuickSettingsShade.Dimensions.Padding), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + brightness() + tiles() + media() + } + } +} + private object ResIdTags { const val quickSettingsPanel = "quick_settings_panel" const val quickQsPanel = "quick_qs_panel" diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt index 512732090036..676f6a426264 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.composefragment.dagger import android.content.Context import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.util.Utils import dagger.Module import dagger.Provides @@ -34,7 +35,7 @@ interface QSFragmentComposeModule { @SysUISingleton @Named(QS_USING_MEDIA_PLAYER) fun providesUsingMedia(@Application context: Context): Boolean { - return Utils.useQsMediaPlayer(context) + return QSComposeFragment.isEnabled && Utils.useQsMediaPlayer(context) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt index 9e3945ecba57..c1a417411975 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt @@ -20,7 +20,9 @@ import com.android.compose.animation.scene.TransitionBuilder import com.android.systemui.qs.composefragment.SceneKeys import com.android.systemui.qs.shared.ui.ElementKeys -fun TransitionBuilder.quickQuickSettingsToQuickSettings(inFirstPage: () -> Boolean = { true }) { +fun TransitionBuilder.quickQuickSettingsToQuickSettings( + animateTilesExpansion: () -> Boolean = { true } +) { fractionRange(start = 0.5f) { fade(ElementKeys.QuickSettingsContent) } @@ -28,7 +30,7 @@ fun TransitionBuilder.quickQuickSettingsToQuickSettings(inFirstPage: () -> Boole anchoredTranslate(ElementKeys.QuickSettingsContent, ElementKeys.GridAnchor) - sharedElement(ElementKeys.TileElementMatcher, enabled = inFirstPage()) + sharedElement(ElementKeys.TileElementMatcher, enabled = animateTilesExpansion()) // This will animate between 0f (QQS) and 0.6, fading in the QQS tiles when coming back // from non first page QS. The QS content ends fading out at 0.5f, so there's a brief diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt index 0ca621d7d2e2..624cf306a3b2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -39,6 +39,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS 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.QS_PANEL @@ -49,10 +51,12 @@ import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor import com.android.systemui.qs.panels.ui.viewmodel.InFirstPageViewModel +import com.android.systemui.qs.panels.ui.viewmodel.MediaInRowInLandscapeViewModel import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.LargeScreenHeaderHelper +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -90,10 +94,11 @@ constructor( disableFlagsRepository: DisableFlagsRepository, keyguardTransitionInteractor: KeyguardTransitionInteractor, private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, private val largeScreenHeaderHelper: LargeScreenHeaderHelper, private val squishinessInteractor: TileSquishinessInteractor, private val inFirstPageViewModel: InFirstPageViewModel, + mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory, @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost, @Named(QS_PANEL) val qsMediaHost: MediaHost, @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean, @@ -101,6 +106,8 @@ constructor( ) : Dumpable, ExclusiveActivatable() { val containerViewModel = containerViewModelFactory.create(true) + private val qqsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS) + private val qsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QS) private val hydrator = Hydrator("QSFragmentComposeViewModel.hydrator") @@ -195,7 +202,7 @@ constructor( } } - val isQsFullyExpanded by derivedStateOf { expansionState.progress >= 1f } + val isQsFullyExpanded by derivedStateOf { expansionState.progress >= 1f && isQsExpanded } /** * Accessibility action for collapsing/expanding QS. The provided runnable is responsible for @@ -203,9 +210,6 @@ constructor( */ var collapseExpandAccessibilityAction: Runnable? = null - val inFirstPage: Boolean - get() = inFirstPageViewModel.inFirstPage - var overScrollAmount by mutableStateOf(0) val viewTranslationY by derivedStateOf { @@ -252,6 +256,9 @@ constructor( }, ) + val qqsMediaInRow: Boolean + get() = qqsMediaInRowViewModel.shouldMediaShowInRow + val qsMediaVisible by hydrator.hydratedStateOf( traceName = "qsMediaVisible", @@ -259,6 +266,18 @@ constructor( source = if (usingMedia) mediaHostVisible(qsMediaHost) else flowOf(false), ) + val qsMediaInRow: Boolean + get() = qsMediaInRowViewModel.shouldMediaShowInRow + + val animateTilesExpansion: Boolean + get() = inFirstPage && !mediaSuddenlyAppearingInLandscape + + private val inFirstPage: Boolean + get() = inFirstPageViewModel.inFirstPage + + private val mediaSuddenlyAppearingInLandscape: Boolean + get() = !qqsMediaInRow && qsMediaInRow + private var qsBounds by mutableStateOf(Rect()) private val constrainedSquishinessFraction: Float @@ -362,6 +381,8 @@ constructor( launch { hydrateSquishinessInteractor() } launch { hydrator.activate() } launch { containerViewModel.activate() } + launch { qqsMediaInRowViewModel.activate() } + launch { qsMediaInRowViewModel.activate() } awaitCancellation() } } @@ -391,6 +412,7 @@ constructor( println("isQSVisible", isQsVisible) println("isQSEnabled", isQsEnabled) println("isCustomizing", containerViewModel.editModeViewModel.isEditing.value) + println("inFirstPage", inFirstPage) } printSection("Expansion state") { println("qsExpansion", qsExpansion) @@ -423,7 +445,9 @@ constructor( } printSection("Media") { println("qqsMediaVisible", qqsMediaVisible) + println("qqsMediaInRow", qqsMediaInRow) println("qsMediaVisible", qsMediaVisible) + println("qsMediaInRow", qsMediaInRow) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index 43fd0f5feec7..1f55ac777de5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt @@ -35,8 +35,6 @@ import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModelImpl -import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsSizeViewModelImpl -import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsViewModel import dagger.Binds import dagger.Module import dagger.Provides @@ -58,8 +56,6 @@ interface PanelsModule { @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel - @Binds fun bindQSColumnsViewModel(impl: QSColumnsSizeViewModelImpl): QSColumnsViewModel - @Binds @PaginatedBaseLayoutType fun bindPaginatedBaseGridLayout(impl: InfiniteGridLayout): PaginatableGridLayout diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 6cc2cbc63d09..2efe500912cd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -73,7 +73,7 @@ constructor( tiles.forEach { it.startListening(token) } onDispose { tiles.forEach { it.stopListening(token) } } } - val columns by viewModel.columns + val columns = viewModel.columns val rows = viewModel.rows val pages = diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 19ab29e6c796..5ac2ad02d671 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -30,6 +30,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout import com.android.systemui.qs.panels.ui.compose.bounceableInfo @@ -72,7 +73,12 @@ constructor( rememberViewModel(traceName = "InfiniteGridLayout.TileGrid") { viewModel.dynamicIconTilesViewModelFactory.create() } - val columns by viewModel.gridSizeViewModel.columns + val columnsWithMediaViewModel = + rememberViewModel(traceName = "InfiniteGridLAyout.TileGrid") { + viewModel.columnsWithMediaViewModelFactory.create(LOCATION_QS) + } + + val columns = columnsWithMediaViewModel.columns val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) } val bounceables = remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } @@ -118,7 +124,11 @@ constructor( rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") { viewModel.dynamicIconTilesViewModelFactory.create() } - val columns by viewModel.gridSizeViewModel.columns + val columnsViewModel = + rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") { + viewModel.columnsWithMediaViewModelFactory.createWithoutMediaTracking() + } + val columns = columnsViewModel.columns val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle() // Non-current tiles should always be displayed as icon tiles. diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt index 7fe856b871bd..4e34e73654fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import javax.inject.Named @@ -54,7 +55,7 @@ constructor( private val currentTilesInteractor: CurrentTilesInteractor, private val tilesAvailabilityInteractor: TilesAvailabilityInteractor, private val minTilesInteractor: MinimumTilesInteractor, - private val configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, @Application private val applicationContext: Context, @Named("Default") private val defaultGridLayout: GridLayout, @Application private val applicationScope: CoroutineScope, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt index d68710048e13..3327141d2bc5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.panels.ui.viewmodel -import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.qs.panels.ui.dialog.QSResetDialogDelegate import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -25,19 +24,15 @@ class InfiniteGridViewModel @AssistedInject constructor( val dynamicIconTilesViewModelFactory: DynamicIconTilesViewModel.Factory, - val gridSizeViewModel: QSColumnsViewModel, + val columnsWithMediaViewModelFactory: QSColumnsViewModel.Factory, val squishinessViewModel: TileSquishinessViewModel, private val resetDialogDelegate: QSResetDialogDelegate, -) : ExclusiveActivatable() { +) { fun showResetDialog() { resetDialogDelegate.showDialog() } - override suspend fun onActivated(): Nothing { - gridSizeViewModel.activate() - } - @AssistedFactory interface Factory { fun create(): InfiniteGridViewModel diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModel.kt new file mode 100644 index 000000000000..2ed8fd20df8f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModel.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.Configuration +import android.content.res.Resources +import androidx.compose.runtime.getValue +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator +import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager +import com.android.systemui.media.controls.ui.controller.MediaLocation +import com.android.systemui.media.controls.ui.view.MediaHostState +import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import javax.inject.Named +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** + * Indicates whether a particular UMO in [LOCATION_QQS] or [LOCATION_QS] should currently show in a + * row with the tiles, based on its visibility and device configuration. If the player is not + * visible, it will never indicate that media should show in row. + */ +class MediaInRowInLandscapeViewModel +@AssistedInject +constructor( + @Main resources: Resources, + configurationInteractor: ConfigurationInteractor, + shadeModeInteractor: ShadeModeInteractor, + private val mediaHostStatesManager: MediaHostStatesManager, + @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean, + @Assisted @MediaLocation private val inLocation: Int, +) : ExclusiveActivatable() { + + private val hydrator = Hydrator("MediaInRowInLanscapeViewModel - $inLocation") + + val shouldMediaShowInRow: Boolean + get() = usingMedia && inSingleShade && isLandscapeAndLong && isMediaVisible + + private val inSingleShade: Boolean by + hydrator.hydratedStateOf( + traceName = "inSingleShade", + initialValue = shadeModeInteractor.shadeMode.value == ShadeMode.Single, + source = shadeModeInteractor.shadeMode.map { it == ShadeMode.Single }, + ) + + private val isLandscapeAndLong: Boolean by + hydrator.hydratedStateOf( + traceName = "isLandscapeAndLong", + initialValue = resources.configuration.isLandscapeAndLong, + source = configurationInteractor.configurationValues.map { it.isLandscapeAndLong }, + ) + + private val isMediaVisible by + hydrator.hydratedStateOf( + traceName = "isMediaVisible", + initialValue = false, + source = + conflatedCallbackFlow { + val callback = + object : MediaHostStatesManager.Callback { + override fun onHostStateChanged( + location: Int, + mediaHostState: MediaHostState, + ) { + if (location == inLocation) { + trySend(mediaHostState.visible) + } + } + } + mediaHostStatesManager.addCallback(callback) + + awaitClose { mediaHostStatesManager.removeCallback(callback) } + } + .onStart { + emit( + mediaHostStatesManager.mediaHostStates.get(inLocation)?.visible ?: false + ) + }, + ) + + override suspend fun onActivated(): Nothing { + hydrator.activate() + } + + @AssistedFactory + interface Factory { + fun create(@MediaLocation inLocation: Int): MediaInRowInLandscapeViewModel + } +} + +private val Configuration.isLandscapeAndLong: Boolean + get() = + orientation == Configuration.ORIENTATION_LANDSCAPE && + (screenLayout and Configuration.SCREENLAYOUT_LONG_MASK) == + Configuration.SCREENLAYOUT_LONG_YES diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt index 8bd9ed05c12c..e5607eb6e620 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt @@ -16,10 +16,10 @@ package com.android.systemui.qs.panels.ui.viewmodel -import androidx.compose.runtime.State import androidx.compose.runtime.getValue import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -31,12 +31,13 @@ class PaginatedGridViewModel @AssistedInject constructor( iconTilesViewModel: IconTilesViewModel, - private val gridSizeViewModel: QSColumnsViewModel, + columnsWithMediaViewModelFactory: QSColumnsViewModel.Factory, paginatedGridInteractor: PaginatedGridInteractor, inFirstPageViewModel: InFirstPageViewModel, ) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() { private val hydrator = Hydrator("PaginatedGridViewModel") + private val columnsWithMediaViewModel = columnsWithMediaViewModelFactory.create(LOCATION_QS) val rows by hydrator.hydratedStateOf( @@ -47,13 +48,13 @@ constructor( var inFirstPage by inFirstPageViewModel::inFirstPage - val columns: State<Int> - get() = gridSizeViewModel.columns + val columns: Int + get() = columnsWithMediaViewModel.columns override suspend fun onActivated(): Nothing { coroutineScope { launch { hydrator.activate() } - launch { gridSizeViewModel.activate() } + launch { columnsWithMediaViewModel.activate() } awaitCancellation() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt index 8926d2ff107e..85b7831fb270 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt @@ -16,25 +16,61 @@ package com.android.systemui.qs.panels.ui.viewmodel -import androidx.compose.runtime.State -import com.android.systemui.lifecycle.Activatable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator +import com.android.systemui.media.controls.ui.controller.MediaLocation import com.android.systemui.qs.panels.domain.interactor.QSColumnsInteractor -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch -interface QSColumnsViewModel : Activatable { - val columns: State<Int> -} +/** + * View model for the number of columns that should be shown in a QS grid. + * * Create it with a [MediaLocation] to halve the number of columns when media should show in a row + * with the tiles. + * * Create it with a `null` [MediaLocation] to ignore media visibility (useful for edit mode). + */ +class QSColumnsViewModel +@AssistedInject +constructor( + interactor: QSColumnsInteractor, + mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory, + @Assisted @MediaLocation mediaLocation: Int?, +) : ExclusiveActivatable() { + + private val hydrator = Hydrator("QSColumnsViewModelWithMedia") + + val columns by derivedStateOf { + if (mediaInRowInLandscapeViewModel?.shouldMediaShowInRow == true) { + columnsWithoutMedia / 2 + } else { + columnsWithoutMedia + } + } -class QSColumnsSizeViewModelImpl @Inject constructor(interactor: QSColumnsInteractor) : - QSColumnsViewModel, ExclusiveActivatable() { - private val hydrator = Hydrator("QSColumnsSizeViewModelImpl") + private val mediaInRowInLandscapeViewModel = + mediaLocation?.let { mediaInRowInLandscapeViewModelFactory.create(it) } - override val columns = - hydrator.hydratedStateOf(traceName = "columns", source = interactor.columns) + private val columnsWithoutMedia by + hydrator.hydratedStateOf(traceName = "columnsWithoutMedia", source = interactor.columns) override suspend fun onActivated(): Nothing { - hydrator.activate() + coroutineScope { + launch { hydrator.activate() } + launch { mediaInRowInLandscapeViewModel?.activate() } + awaitCancellation() + } + } + + @AssistedFactory + interface Factory { + fun create(mediaLocation: Int?): QSColumnsViewModel + + fun createWithoutMediaTracking() = create(null) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt index 0859c86d74e1..33ce5519b68c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.getValue import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator +import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.shared.model.splitInRowsSequence @@ -36,23 +37,35 @@ class QuickQuickSettingsViewModel @AssistedInject constructor( tilesInteractor: CurrentTilesInteractor, - private val qsColumnsViewModel: QSColumnsViewModel, + qsColumnsViewModelFactory: QSColumnsViewModel.Factory, quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor, + mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory, val squishinessViewModel: TileSquishinessViewModel, iconTilesViewModel: IconTilesViewModel, val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider, ) : ExclusiveActivatable() { private val hydrator = Hydrator("QuickQuickSettingsViewModel") + private val qsColumnsViewModel = qsColumnsViewModelFactory.create(LOCATION_QQS) + private val mediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS) - val columns by qsColumnsViewModel.columns + val columns: Int + get() = qsColumnsViewModel.columns private val largeTiles by hydrator.hydratedStateOf(traceName = "largeTiles", source = iconTilesViewModel.largeTiles) - private val rows by + private val rows: Int + get() = + if (mediaInRowViewModel.shouldMediaShowInRow) { + rowsWithoutMedia * 2 + } else { + rowsWithoutMedia + } + + private val rowsWithoutMedia by hydrator.hydratedStateOf( - traceName = "rows", + traceName = "rowsWithoutMedia", initialValue = quickQuickSettingsRowInteractor.defaultRows, source = quickQuickSettingsRowInteractor.rows, ) @@ -73,6 +86,7 @@ constructor( coroutineScope { launch { hydrator.activate() } launch { qsColumnsViewModel.activate() } + launch { mediaInRowViewModel.activate() } awaitCancellation() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt index f702da46717a..c9a0635021da 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt @@ -54,6 +54,7 @@ object SubtitleArrayMapping { subtitleIdsMap["dream"] = R.array.tile_states_dream subtitleIdsMap["font_scaling"] = R.array.tile_states_font_scaling subtitleIdsMap["hearing_devices"] = R.array.tile_states_hearing_devices + subtitleIdsMap["notes"] = R.array.tile_states_notes } /** Get the subtitle resource id of the given tile */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt new file mode 100644 index 000000000000..69df0961bf66 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles + +import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.service.quicksettings.Tile +import com.android.internal.logging.MetricsLogger +import com.android.systemui.animation.Expandable +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.impl.notes.domain.NotesTileMapper +import com.android.systemui.qs.tiles.impl.notes.domain.interactor.NotesTileDataInteractor +import com.android.systemui.qs.tiles.impl.notes.domain.interactor.NotesTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.runBlocking + +/** Quick settings tile: Notes */ +class NotesTile +@Inject constructor( + private val host: QSHost, + private val uiEventLogger: QsEventLogger, + @Background private val backgroundLooper: Looper, + @Main private val mainHandler: Handler, + private val falsingManager: FalsingManager, + private val metricsLogger: MetricsLogger, + private val statusBarStateController: StatusBarStateController, + private val activityStarter: ActivityStarter, + private val qsLogger: QSLogger, + private val qsTileConfigProvider: QSTileConfigProvider, + private val dataInteractor: NotesTileDataInteractor, + private val tileMapper: NotesTileMapper, + private val userActionInteractor: NotesTileUserActionInteractor, +) : + QSTileImpl<QSTile.State?>( + host, + uiEventLogger, + backgroundLooper, + mainHandler, + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + ) { + + private lateinit var tileState: QSTileState + private val config = qsTileConfigProvider.getConfig(TILE_SPEC) + + override fun getTileLabel(): CharSequence = + mContext.getString(config.uiConfig.labelRes) + + override fun newTileState(): QSTile.State? { + return QSTile.State().apply { state = Tile.STATE_INACTIVE } + } + + override fun handleClick(expandable: Expandable?) { + userActionInteractor.handleClick() + } + + override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent + + override fun handleUpdateState(state: QSTile.State?, arg: Any?) { + val model = + if (arg is NotesTileModel) arg else dataInteractor.getCurrentTileModel() + tileState = tileMapper.map(config, model) + + state?.apply { + this.state = tileState.activationState.legacyState + icon = ResourceIcon.get(tileState.iconRes ?: R.drawable.ic_qs_notes) + label = tileState.label + contentDescription = tileState.contentDescription + expandedAccessibilityClassName = tileState.expandedAccessibilityClassName + } + } + + override fun isAvailable(): Boolean { + return dataInteractor.isAvailable() + } + + companion object { + const val TILE_SPEC = "notes" + } +} 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 284239ab1a78..f3be340f4951 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -191,8 +191,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> mPanelInteractor.collapsePanels(); }; - final Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags, - mDialogTransitionAnimator, mActivityStarter, onStartRecordingClicked); + final Dialog dialog = mController.createScreenRecordDialog(onStartRecordingClicked); ActivityStarter.OnDismissAction dismissAction = () -> { if (shouldAnimateFromExpandable) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt new file mode 100644 index 000000000000..ee1b9e5171b7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.notes.domain + +import android.content.res.Resources +import android.widget.Button +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +class NotesTileMapper +@Inject +constructor(@Main private val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<NotesTileModel> { + override fun map(config: QSTileConfig, data: NotesTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + iconRes = R.drawable.ic_qs_notes + icon = + Icon.Loaded( + resources.getDrawable( + iconRes!!, + theme), + contentDescription = null + ) + contentDescription = label + activationState = QSTileState.ActivationState.INACTIVE + sideViewIcon = QSTileState.SideViewIcon.Chevron + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + expandedAccessibilityClass = Button::class + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractor.kt new file mode 100644 index 000000000000..a501b8561330 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileDataInteractor.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.notes.domain.interactor + +import android.os.UserHandle +import com.android.systemui.Flags +import com.android.systemui.notetask.NoteTaskEnabledKey +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +class NotesTileDataInteractor +@Inject +constructor(@NoteTaskEnabledKey private val isNoteTaskEnabled: Boolean) : + QSTileDataInteractor<NotesTileModel> { + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger>, + ): Flow<NotesTileModel> = flowOf(NotesTileModel) + + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(isAvailable()) + + fun isAvailable(): Boolean { + return Flags.notesRoleQsTile() && isNoteTaskEnabled + } + + fun getCurrentTileModel(): NotesTileModel { + return NotesTileModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractor.kt new file mode 100644 index 000000000000..df01d99df0df --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/interactor/NotesTileUserActionInteractor.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.notes.domain.interactor + +import com.android.systemui.animation.Expandable +import com.android.systemui.notetask.NoteTaskController +import com.android.systemui.notetask.NoteTaskEntryPoint +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +class NotesTileUserActionInteractor +@Inject constructor( + private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler, + private val panelInteractor: PanelInteractor, + private val noteTaskController: NoteTaskController, +) : QSTileUserActionInteractor<NotesTileModel> { + val longClickIntent = NoteTaskController.createNotesRoleHolderSettingsIntent() + + override suspend fun handleInput(input: QSTileInput<NotesTileModel>) { + when (input.action) { + is QSTileUserAction.Click -> handleClick() + is QSTileUserAction.LongClick -> handleLongClick(input.action.expandable) + is QSTileUserAction.ToggleClick -> {} + } + } + + fun handleClick() { + noteTaskController.showNoteTask(NoteTaskEntryPoint.QS_NOTES_TILE) + panelInteractor.collapsePanels() + } + + fun handleLongClick(expandable: Expandable?) { + qsTileIntentUserInputHandler.handle(expandable, longClickIntent) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/model/NotesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/model/NotesTileModel.kt new file mode 100644 index 000000000000..b72aa60e16f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/model/NotesTileModel.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.notes.domain.model + +/** NotesTileModel tile model. */ +data object NotesTileModel 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 48b39ed25750..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,16 +16,13 @@ package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor -import android.content.Context import android.util.Log import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable -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.flags.FeatureFlagsClassic import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.plugins.ActivityStarter @@ -45,7 +42,6 @@ import kotlinx.coroutines.withContext class ScreenRecordTileUserActionInteractor @Inject constructor( - @Application private val context: Context, @Main private val mainContext: CoroutineContext, @Background private val backgroundContext: CoroutineContext, private val screenRecordRepository: ScreenRecordRepository, @@ -55,8 +51,6 @@ constructor( private val dialogTransitionAnimator: DialogTransitionAnimator, private val panelInteractor: PanelInteractor, private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, - private val featureFlags: FeatureFlagsClassic, - private val activityStarter: ActivityStarter, ) : QSTileUserActionInteractor<ScreenRecordModel> { override suspend fun handleInput(input: QSTileInput<ScreenRecordModel>): Unit = with(input) { @@ -89,14 +83,7 @@ constructor( panelInteractor.collapsePanels() } - val dialog = - recordingController.createScreenRecordDialog( - context, - featureFlags, - dialogTransitionAnimator, - activityStarter, - onStartRecordingClicked - ) + val dialog = recordingController.createScreenRecordDialog(onStartRecordingClicked) if (dialog == null) { Log.w(TAG, "showPrompt: dialog was null") @@ -115,7 +102,7 @@ constructor( expandable?.dialogTransitionController( DialogCuj( InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG + INTERACTION_JANK_TAG, ) ) controller?.let { @@ -135,7 +122,7 @@ constructor( keyguardDismissUtil.executeWhenUnlocked( dismissAction, false /* requiresShadeOpen */, - true /* afterKeyguardDone */ + true, /* afterKeyguardDone */ ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt index 9a416d1d3b3b..000f7f8a7d31 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs.ui.viewmodel import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.UserActionResult.HideOverlay @@ -47,7 +46,7 @@ constructor(private val editModeViewModel: EditModeViewModel) : UserActionsViewM put(Back, HideOverlay(Overlays.QuickSettingsShade)) } put( - Swipe(SwipeDirection.Down, fromSource = SceneContainerEdge.TopLeft), + Swipe.Down(fromSource = SceneContainerEdge.TopLeft), ReplaceByOverlay(Overlays.NotificationsShade), ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt index 54e5caca107c..f59541545de4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt @@ -20,7 +20,6 @@ import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.qs.ui.adapter.QSSceneAdapter @@ -44,10 +43,8 @@ import kotlinx.coroutines.flow.map */ class QuickSettingsUserActionsViewModel @AssistedInject -constructor( - private val qsSceneAdapter: QSSceneAdapter, - sceneBackInteractor: SceneBackInteractor, -) : UserActionsViewModel() { +constructor(private val qsSceneAdapter: QSSceneAdapter, sceneBackInteractor: SceneBackInteractor) : + UserActionsViewModel() { private val backScene: Flow<SceneKey> = sceneBackInteractor.backScene @@ -55,10 +52,7 @@ constructor( .map { it ?: Scenes.Shade } override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) { - combine( - qsSceneAdapter.isCustomizerShowing, - backScene, - ) { isCustomizing, backScene -> + combine(qsSceneAdapter.isCustomizerShowing, backScene) { isCustomizing, backScene -> buildMap<UserAction, UserActionResult> { if (isCustomizing) { // TODO(b/332749288) Empty map so there are no back handlers and back can @@ -69,9 +63,9 @@ constructor( // while customizing } else { put(Back, UserActionResult(backScene)) - put(Swipe(SwipeDirection.Up), UserActionResult(backScene)) + put(Swipe.Up, UserActionResult(backScene)) put( - Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up), + Swipe.Up(fromSource = Edge.Bottom), UserActionResult(SceneFamilies.Home), ) } diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt index 6758c3ba0767..02b2bb1585bd 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt @@ -35,7 +35,6 @@ import androidx.annotation.WorkerThread import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.SessionCreationSource import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver @@ -132,10 +131,9 @@ constructor( @WorkerThread private fun onScreenRecordSwitchClicked() { if ( - flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) && - devicePolicyResolver - .get() - .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId)) + devicePolicyResolver + .get() + .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId)) ) { mainExecutor.execute { screenCaptureDisabledDialogDelegate.createSysUIDialog().show() diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 1fbe8e2f21e5..580a51a3dc0a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -46,7 +46,6 @@ import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor import com.android.systemui.model.SceneContainerPlugin import com.android.systemui.model.SysUiState import com.android.systemui.model.updateFlags @@ -97,7 +96,6 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -138,7 +136,6 @@ constructor( private val uiEventLogger: UiEventLogger, private val sceneBackInteractor: SceneBackInteractor, private val shadeSessionStorage: SessionStorage, - private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor, private val keyguardEnabledInteractor: KeyguardEnabledInteractor, private val dismissCallbackRegistry: DismissCallbackRegistry, private val statusBarStateController: SysuiStatusBarStateController, @@ -270,27 +267,6 @@ constructor( handleDeviceUnlockStatus() handlePowerState() handleShadeTouchability() - handleSurfaceBehindKeyguardVisibility() - } - - private fun handleSurfaceBehindKeyguardVisibility() { - applicationScope.launch { - sceneInteractor.currentScene.collectLatest { currentScene -> - if (currentScene == Scenes.Lockscreen) { - // Wait for the screen to be on - powerInteractor.isAwake.first { it } - // Wait for surface to become visible - windowMgrLockscreenVisInteractor.surfaceBehindVisibility.first { it } - // Make sure the device is actually unlocked before force-changing the scene - deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked } - // Override the current transition, if any, by forcing the scene to Gone - sceneInteractor.changeScene( - toScene = Scenes.Gone, - loggingReason = "surface behind keyguard is visible", - ) - } - } - } } private fun handleBouncerImeVisibility() { diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index a8a78a91097c..d7463f8f0c36 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -32,17 +32,13 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.CallbackController; @@ -66,12 +62,10 @@ public class RecordingController private CountDownTimer mCountDownTimer = null; private final Executor mMainExecutor; private final BroadcastDispatcher mBroadcastDispatcher; - private final FeatureFlags mFlags; private final UserTracker mUserTracker; private final RecordingControllerLogger mRecordingControllerLogger; private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate; - private final ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory; private final ScreenRecordPermissionDialogDelegate.Factory mScreenRecordPermissionDialogDelegateFactory; @@ -116,24 +110,20 @@ public class RecordingController public RecordingController( @Main Executor mainExecutor, BroadcastDispatcher broadcastDispatcher, - FeatureFlags flags, Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver, UserTracker userTracker, RecordingControllerLogger recordingControllerLogger, MediaProjectionMetricsLogger mediaProjectionMetricsLogger, ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate, - ScreenRecordDialogDelegate.Factory screenRecordDialogFactory, ScreenRecordPermissionDialogDelegate.Factory screenRecordPermissionDialogDelegateFactory) { mMainExecutor = mainExecutor; - mFlags = flags; mDevicePolicyResolver = devicePolicyResolver; mBroadcastDispatcher = broadcastDispatcher; mUserTracker = userTracker; mRecordingControllerLogger = recordingControllerLogger; mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate; - mScreenRecordDialogFactory = screenRecordDialogFactory; mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory; BroadcastOptions options = BroadcastOptions.makeBasic(); @@ -158,12 +148,8 @@ public class RecordingController /** Create a dialog to show screen recording options to the user. * If screen capturing is currently not allowed it will return a dialog * that warns users about it. */ - public Dialog createScreenRecordDialog(Context context, FeatureFlags flags, - DialogTransitionAnimator dialogTransitionAnimator, - ActivityStarter activityStarter, - @Nullable Runnable onStartRecordingClicked) { - if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) - && mDevicePolicyResolver.get() + public Dialog createScreenRecordDialog(@Nullable Runnable onStartRecordingClicked) { + if (mDevicePolicyResolver.get() .isScreenCaptureCompletelyDisabled(getHostUserHandle())) { return mScreenCaptureDisabledDialogDelegate.createSysUIDialog(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java deleted file mode 100644 index 9f1447b1f509..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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.screenrecord; - -import static android.app.Activity.RESULT_OK; - -import static com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET; -import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL; -import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC; -import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL; -import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.NONE; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.ResultReceiver; -import android.view.Gravity; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.ArrayAdapter; -import android.widget.Spinner; -import android.widget.Switch; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget; -import com.android.systemui.res.R; -import com.android.systemui.settings.UserContextProvider; -import com.android.systemui.statusbar.phone.SystemUIDialog; - -import dagger.assisted.Assisted; -import dagger.assisted.AssistedFactory; -import dagger.assisted.AssistedInject; - -import java.util.Arrays; -import java.util.List; - -/** - * Dialog to select screen recording options - */ -public class ScreenRecordDialogDelegate implements SystemUIDialog.Delegate { - private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC, - MIC_AND_INTERNAL); - private static final long DELAY_MS = 3000; - private static final long INTERVAL_MS = 1000; - - private final SystemUIDialog.Factory mSystemUIDialogFactory; - private final UserContextProvider mUserContextProvider; - private final RecordingController mController; - private final Runnable mOnStartRecordingClicked; - private Switch mTapsSwitch; - private Switch mAudioSwitch; - private Spinner mOptions; - - @AssistedFactory - public interface Factory { - ScreenRecordDialogDelegate create( - RecordingController recordingController, - @Nullable Runnable onStartRecordingClicked - ); - } - - @AssistedInject - public ScreenRecordDialogDelegate( - SystemUIDialog.Factory systemUIDialogFactory, - UserContextProvider userContextProvider, - @Assisted RecordingController controller, - @Assisted @Nullable Runnable onStartRecordingClicked) { - mSystemUIDialogFactory = systemUIDialogFactory; - mUserContextProvider = userContextProvider; - mController = controller; - mOnStartRecordingClicked = onStartRecordingClicked; - } - - @Override - public SystemUIDialog createDialog() { - return mSystemUIDialogFactory.create(this); - } - - @Override - public void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) { - Window window = dialog.getWindow(); - - window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); - - window.setGravity(Gravity.CENTER); - dialog.setTitle(R.string.screenrecord_title); - - dialog.setContentView(R.layout.screen_record_dialog); - - TextView cancelBtn = dialog.findViewById(R.id.button_cancel); - cancelBtn.setOnClickListener(v -> dialog.dismiss()); - TextView startBtn = dialog.findViewById(R.id.button_start); - startBtn.setOnClickListener(v -> { - if (mOnStartRecordingClicked != null) { - // Note that it is important to run this callback before dismissing, so that the - // callback can disable the dialog exit animation if it wants to. - mOnStartRecordingClicked.run(); - } - - // Start full-screen recording - requestScreenCapture(/* captureTarget= */ null); - dialog.dismiss(); - }); - - mAudioSwitch = dialog.findViewById(R.id.screenrecord_audio_switch); - mTapsSwitch = dialog.findViewById(R.id.screenrecord_taps_switch); - mOptions = dialog.findViewById(R.id.screen_recording_options); - ArrayAdapter a = new ScreenRecordingAdapter(dialog.getContext().getApplicationContext(), - android.R.layout.simple_spinner_dropdown_item, - MODES); - a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mOptions.setAdapter(a); - mOptions.setOnItemClickListenerInt((parent, view, position, id) -> { - mAudioSwitch.setChecked(true); - }); - - // disable redundant Touch & Hold accessibility action for Switch Access - mOptions.setAccessibilityDelegate(new View.AccessibilityDelegate() { - @Override - public void onInitializeAccessibilityNodeInfo(@NonNull View host, - @NonNull AccessibilityNodeInfo info) { - info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); - super.onInitializeAccessibilityNodeInfo(host, info); - } - }); - mOptions.setLongClickable(false); - } - - /** - * Starts screen capture after some countdown - * @param captureTarget target to capture (could be e.g. a task) or - * null to record the whole screen - */ - private void requestScreenCapture(@Nullable MediaProjectionCaptureTarget captureTarget) { - Context userContext = mUserContextProvider.getUserContext(); - boolean showTaps = mTapsSwitch.isChecked(); - ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked() - ? (ScreenRecordingAudioSource) mOptions.getSelectedItem() - : NONE; - PendingIntent startIntent = PendingIntent.getForegroundService(userContext, - RecordingService.REQUEST_CODE, - RecordingService.getStartIntent( - userContext, Activity.RESULT_OK, - audioMode.ordinal(), showTaps, captureTarget), - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - PendingIntent stopIntent = PendingIntent.getService(userContext, - RecordingService.REQUEST_CODE, - RecordingService.getStopIntent(userContext), - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent); - } - - private class CaptureTargetResultReceiver extends ResultReceiver { - - CaptureTargetResultReceiver() { - super(new Handler(Looper.getMainLooper())); - } - - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - if (resultCode == RESULT_OK) { - MediaProjectionCaptureTarget captureTarget = resultData - .getParcelable(KEY_CAPTURE_TARGET, MediaProjectionCaptureTarget.class); - - // Start recording of the selected target - requestScreenCapture(captureTarget); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt index e5f684635ac7..b0777c9e20b9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt @@ -18,7 +18,6 @@ package com.android.systemui.shade.ui.viewmodel import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.scene.shared.model.Overlays @@ -35,7 +34,7 @@ fun singleShadeActions( return arrayOf( // Swiping down, not from the edge, always goes to shade. Swipe.Down to shadeUserActionResult, - swipeDown(pointerCount = 2) to shadeUserActionResult, + Swipe.Down(pointerCount = 2) to shadeUserActionResult, // Swiping down from the top edge. swipeDownFromTop(pointerCount = 1) to @@ -54,7 +53,7 @@ fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> { return arrayOf( // Swiping down, not from the edge, always goes to shade. Swipe.Down to shadeUserActionResult, - swipeDown(pointerCount = 2) to shadeUserActionResult, + Swipe.Down(pointerCount = 2) to shadeUserActionResult, // Swiping down from the top edge goes to QS. swipeDownFromTop(pointerCount = 1) to shadeUserActionResult, swipeDownFromTop(pointerCount = 2) to shadeUserActionResult, @@ -69,15 +68,10 @@ fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> { UserActionResult.ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true) return arrayOf( Swipe.Down to notifShadeUserActionResult, - Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to - qsShadeuserActionResult, + Swipe.Down(fromSource = SceneContainerEdge.TopRight) to qsShadeuserActionResult, ) } private fun swipeDownFromTop(pointerCount: Int): Swipe { - return Swipe(SwipeDirection.Down, fromSource = Edge.Top, pointerCount = pointerCount) -} - -private fun swipeDown(pointerCount: Int): Swipe { - return Swipe(SwipeDirection.Down, pointerCount = pointerCount) + return Swipe.Down(fromSource = Edge.Top, pointerCount = pointerCount) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt index 4bdd36773655..7d6b1a3126dc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.shade.ui.viewmodel import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.qs.ui.adapter.QSSceneAdapter @@ -58,7 +57,7 @@ constructor( buildMap<UserAction, UserActionResult> { if (!isCustomizerShowing) { set( - Swipe(SwipeDirection.Up), + Swipe.Up, UserActionResult( backScene, ToSplitShade.takeIf { shadeMode is ShadeMode.Split }, @@ -69,7 +68,7 @@ constructor( // TODO(b/330200163) Add an else to be able to collapse the shade while // customizing if (shadeMode is ShadeMode.Single) { - set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings)) + set(Swipe.Down, UserActionResult(Scenes.QuickSettings)) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 520cbf9d80d9..8c5a711d6a75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -619,10 +619,11 @@ public class KeyguardIndicationController { } private void updateLockScreenUserLockedMsg(int userId) { - boolean userUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId); + boolean userStorageUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId); boolean encryptedOrLockdown = mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId); - mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userUnlocked, encryptedOrLockdown); - if (!userUnlocked || encryptedOrLockdown) { + mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userStorageUnlocked, + encryptedOrLockdown); + if (!userStorageUnlocked || encryptedOrLockdown) { mRotateTextViewController.updateIndication( INDICATION_TYPE_USER_LOCKED, new KeyguardIndication.Builder() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java index 0d789c703d48..f5d443443838 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java @@ -119,7 +119,6 @@ public class OperatorNameViewController extends ViewController<OperatorNameView> /** Factory for constructing an {@link OperatorNameViewController}. */ public static class Factory { - private final DarkIconDispatcher mDarkIconDispatcher; private final TunerService mTunerService; private final TelephonyManager mTelephonyManager; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -129,7 +128,7 @@ public class OperatorNameViewController extends ViewController<OperatorNameView> private final JavaAdapter mJavaAdapter; @Inject - public Factory(DarkIconDispatcher darkIconDispatcher, + public Factory( TunerService tunerService, TelephonyManager telephonyManager, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -137,7 +136,6 @@ public class OperatorNameViewController extends ViewController<OperatorNameView> AirplaneModeInteractor airplaneModeInteractor, SubscriptionManagerProxy subscriptionManagerProxy, JavaAdapter javaAdapter) { - mDarkIconDispatcher = darkIconDispatcher; mTunerService = tunerService; mTelephonyManager = telephonyManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -148,9 +146,11 @@ public class OperatorNameViewController extends ViewController<OperatorNameView> } /** Create an {@link OperatorNameViewController}. */ - public OperatorNameViewController create(OperatorNameView view) { - return new OperatorNameViewController(view, - mDarkIconDispatcher, + public OperatorNameViewController create( + OperatorNameView view, DarkIconDispatcher darkIconDispatcher) { + return new OperatorNameViewController( + view, + darkIconDispatcher, mTunerService, mTelephonyManager, mKeyguardUpdateMonitor, 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 c8d3f339b3e9..752674854e2d 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 @@ -68,13 +68,8 @@ constructor( notifChipsInteractor.onPromotedNotificationChipTapped(this@toChipModel.key) } } - return OngoingActivityChipModel.Shown.ShortTimeDelta( - icon, - colors, - time = this.whenTime, - onClickListener, - ) - // TODO(b/364653005): If Notification.showWhen = false, don't show the time delta. + return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + // TODO(b/364653005): Use Notification.showWhen to determine if we should show the time. // TODO(b/364653005): If Notification.whenTime is in the past, show "ago" in the text. // 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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt index 9b3513e8a363..84c7ab200fad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.display.data.repository.DisplayScopeRepository +import com.android.systemui.statusbar.data.repository.LightBarControllerStore import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStore import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.window.StatusBarWindowControllerStore @@ -50,6 +51,7 @@ constructor( private val statusBarWindowControllerStore: StatusBarWindowControllerStore, private val statusBarInitializerStore: StatusBarInitializerStore, private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore, + private val lightBarControllerStore: LightBarControllerStore, ) : CoreStartable { init { @@ -74,6 +76,14 @@ constructor( createAndStartOrchestratorForDisplay(displayId) createAndStartInitializerForDisplay(displayId) startPrivacyDotForDisplay(displayId) + createLightBarControllerForDisplay(displayId) + } + + private fun createLightBarControllerForDisplay(displayId: Int) { + // Explicitly not calling `start()`, because the store is already calling `start()`. + // This is to maintain the legacy behavior with NavigationBar, that was already expecting + // LightBarController to start at construction time. + lightBarControllerStore.forDisplay(displayId) } private fun createAndStartOrchestratorForDisplay(displayId: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt index 39de28e7cb49..27d815190d85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.data +import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStoreModule import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule @@ -28,6 +29,7 @@ import dagger.Module @Module( includes = [ + DarkIconDispatcherStoreModule::class, KeyguardStatusBarRepositoryModule::class, LightBarControllerStoreModule::class, RemoteInputRepositoryModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt new file mode 100644 index 000000000000..8183a487cee2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt @@ -0,0 +1,142 @@ +/* + * 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.data.repository + +import android.content.Context +import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.display.data.repository.PerDisplayStore +import com.android.systemui.display.data.repository.PerDisplayStoreImpl +import com.android.systemui.display.data.repository.SingleDisplayStore +import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher +import dagger.Binds +import dagger.Lazy +import dagger.Module +import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope + +/** Provides per display instances of [DarkIconDispatcher]. */ +interface DarkIconDispatcherStore : PerDisplayStore<DarkIconDispatcher> + +/** Provides per display instances of [SysuiDarkIconDispatcher]. */ +interface SysuiDarkIconDispatcherStore : PerDisplayStore<SysuiDarkIconDispatcher> + +/** + * Multi display implementation that should be used when the [StatusBarConnectedDisplays] flag is + * enabled. + */ +@SysUISingleton +class MultiDisplayDarkIconDispatcherStore +@Inject +constructor( + @Background backgroundApplicationScope: CoroutineScope, + displayRepository: DisplayRepository, + private val factory: DarkIconDispatcherImpl.Factory, + private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, +) : + SysuiDarkIconDispatcherStore, + PerDisplayStoreImpl<SysuiDarkIconDispatcher>(backgroundApplicationScope, displayRepository) { + + init { + StatusBarConnectedDisplays.assertInNewMode() + } + + override fun createInstanceForDisplay(displayId: Int): SysuiDarkIconDispatcher { + val properties = displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR) + return factory.create(displayId, properties.context) + } + + override suspend fun onDisplayRemovalAction(instance: SysuiDarkIconDispatcher) { + instance.stop() + } + + override val instanceClass = SysuiDarkIconDispatcher::class.java +} + +/** + * Single display implementation that should be used when the [StatusBarConnectedDisplays] flag is + * disabled. + */ +@SysUISingleton +class SingleDisplayDarkIconDispatcherStore +@Inject +constructor(factory: DarkIconDispatcherImpl.Factory, context: Context) : + SysuiDarkIconDispatcherStore, + PerDisplayStore<SysuiDarkIconDispatcher> by SingleDisplayStore( + defaultInstance = factory.create(context.displayId, context) + ) { + + init { + StatusBarConnectedDisplays.assertInLegacyMode() + } +} + +/** Extra implementation that simply implements the [DarkIconDispatcherStore] interface. */ +@SysUISingleton +class DarkIconDispatcherStoreImpl +@Inject +constructor(private val store: SysuiDarkIconDispatcherStore) : DarkIconDispatcherStore { + override val defaultDisplay: DarkIconDispatcher + get() = store.defaultDisplay + + override fun forDisplay(displayId: Int): DarkIconDispatcher = store.forDisplay(displayId) +} + +@Module +interface DarkIconDispatcherStoreModule { + + @Binds fun store(impl: DarkIconDispatcherStoreImpl): DarkIconDispatcherStore + + companion object { + @Provides + @SysUISingleton + fun sysUiStore( + singleDisplayLazy: Lazy<SingleDisplayDarkIconDispatcherStore>, + multiDisplayLazy: Lazy<MultiDisplayDarkIconDispatcherStore>, + ): SysuiDarkIconDispatcherStore { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayLazy.get() + } else { + singleDisplayLazy.get() + } + } + + @Provides + @SysUISingleton + @IntoMap + @ClassKey(DarkIconDispatcherStore::class) + fun storeAsCoreStartable( + multiDisplayLazy: Lazy<MultiDisplayDarkIconDispatcherStore> + ): CoreStartable { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayLazy.get() + } else { + CoreStartable.NOP + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt index ff50e3100672..e4987555833b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt @@ -44,6 +44,7 @@ constructor( private val factory: LightBarControllerImpl.Factory, private val displayScopeRepository: DisplayScopeRepository, private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore, + private val darkIconDispatcherStore: DarkIconDispatcherStore, ) : LightBarControllerStore, PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) { @@ -53,6 +54,7 @@ constructor( .create( displayId, displayScopeRepository.scopeForDisplay(displayId), + darkIconDispatcherStore.forDisplay(displayId), statusBarModeRepositoryStore.forDisplay(displayId), ) .also { it.start() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt index bdd9fd032800..6b6920a3621a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt @@ -28,14 +28,5 @@ import com.android.systemui.res.R @MainThread fun contentDescForNotification(c: Context, n: Notification): CharSequence { val appName = n.loadHeaderAppName(c) ?: "" - val title = n.extras?.getCharSequence(Notification.EXTRA_TITLE) - val text = n.extras?.getCharSequence(Notification.EXTRA_TEXT) - val ticker = n.tickerText - - // Some apps just put the app name into the title - val titleOrText = if (TextUtils.equals(title, appName)) text else title - val desc = - if (!TextUtils.isEmpty(titleOrText)) titleOrText - else if (!TextUtils.isEmpty(ticker)) ticker else "" - return c.getString(R.string.accessibility_desc_notification_icon, appName, desc) + return c.getString(R.string.accessibility_desc_notification_icon, appName, "") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt index 663588c8f8c8..fc432ba973ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt @@ -17,11 +17,13 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import androidx.lifecycle.lifecycleScope +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.traceSection import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel @@ -30,7 +32,6 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.ui.SystemBarUtilsState import javax.inject.Inject import kotlinx.coroutines.DisposableHandle -import com.android.app.tracing.coroutines.launchTraced as launch /** Binds a [NotificationIconContainer] to a [NotificationIconContainerAlwaysOnDisplayViewModel]. */ class NotificationIconContainerAlwaysOnDisplayViewBinder @@ -38,7 +39,7 @@ class NotificationIconContainerAlwaysOnDisplayViewBinder constructor( private val viewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val keyguardRootViewModel: KeyguardRootViewModel, - private val configuration: ConfigurationState, + @ShadeDisplayAware private val configuration: ConfigurationState, private val failureTracker: StatusBarIconViewBindingFailureTracker, private val screenOffAnimationController: ScreenOffAnimationController, private val systemBarUtilsState: SystemBarUtilsState, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt index 4e40888ee88a..5432f1448902 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.bindIcons @@ -30,7 +31,7 @@ class NotificationIconContainerShelfViewBinder @Inject constructor( private val viewModel: NotificationIconContainerShelfViewModel, - private val configuration: ConfigurationState, + @ShadeDisplayAware private val configuration: ConfigurationState, private val systemBarUtilsState: SystemBarUtilsState, private val failureTracker: StatusBarIconViewBindingFailureTracker, private val viewStore: ShelfNotificationIconViewStore, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt index f0f529e2c615..a21dabb821d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt @@ -17,9 +17,11 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import androidx.lifecycle.lifecycleScope +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.traceSection import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel @@ -27,23 +29,23 @@ import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.ui.SystemBarUtilsState import javax.inject.Inject import kotlinx.coroutines.DisposableHandle -import com.android.app.tracing.coroutines.launchTraced as launch /** Binds a [NotificationIconContainer] to a [NotificationIconContainerStatusBarViewModel]. */ class NotificationIconContainerStatusBarViewBinder @Inject constructor( private val viewModel: NotificationIconContainerStatusBarViewModel, - private val configuration: ConfigurationState, + @ShadeDisplayAware private val configuration: ConfigurationState, private val systemBarUtilsState: SystemBarUtilsState, private val failureTracker: StatusBarIconViewBindingFailureTracker, private val viewStore: StatusBarNotificationIconViewStore, ) { - fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle { + fun bindWhileAttached(view: NotificationIconContainer, displayId: Int): DisposableHandle { return traceSection("NICStatusBar#bindWhileAttached") { view.repeatWhenAttached { lifecycleScope.launch { NotificationIconContainerViewBinder.bind( + displayId = displayId, view = view, viewModel = viewModel, configuration = configuration, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 063fe45cd199..6dbb71463602 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -24,6 +24,7 @@ import android.widget.FrameLayout import androidx.annotation.ColorInt import androidx.collection.ArrayMap import androidx.lifecycle.lifecycleScope +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.traceSection import com.android.internal.R as RInternal import com.android.internal.statusbar.StatusBarIcon @@ -54,12 +55,12 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch /** Binds a view-model to a [NotificationIconContainer]. */ object NotificationIconContainerViewBinder { suspend fun bind( + displayId: Int, view: NotificationIconContainer, viewModel: NotificationIconContainerStatusBarViewModel, configuration: ConfigurationState, @@ -70,7 +71,10 @@ object NotificationIconContainerViewBinder { launch { val contrastColorUtil = ContrastColorUtil.getInstance(view.context) val iconColors: StateFlow<NotificationIconColors> = - viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) }.stateIn(this) + viewModel + .iconColors(displayId) + .mapNotNull { it.iconColors(view.viewBounds) } + .stateIn(this) viewModel.icons.bindIcons( logTag = "statusbar", view = view, @@ -79,11 +83,7 @@ object NotificationIconContainerViewBinder { notifyBindingFailures = { failureTracker.statusBarFailures = it }, viewStore = viewStore, ) { _, sbiv -> - StatusBarIconViewBinder.bindIconColors( - sbiv, - iconColors, - contrastColorUtil, - ) + StatusBarIconViewBinder.bindIconColors(sbiv, iconColors, contrastColorUtil) } } launch { viewModel.bindIsolatedIcon(view, viewStore) } @@ -194,8 +194,7 @@ object NotificationIconContainerViewBinder { combine(iconSizeFlow, iconHorizontalPaddingFlow, systemBarUtilsState.statusBarHeight) { iconSize, iconHPadding, - statusBarHeight, - -> + statusBarHeight -> FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight) } .stateIn(this) @@ -251,10 +250,7 @@ object NotificationIconContainerViewBinder { traceSection("addIcon") { (sbiv.parent as? ViewGroup)?.run { if (this !== view) { - Log.wtf( - TAG, - "[$logTag] SBIV($notifKey) has an unexpected parent", - ) + Log.wtf(TAG, "[$logTag] SBIV($notifKey) has an unexpected parent") } // If the container was re-inflated and re-bound, then SBIVs might still // be attached to the prior view. @@ -271,7 +267,7 @@ object NotificationIconContainerViewBinder { launch { launch { layoutParams.collectTracingEach( - tag = { "[$logTag] SBIV#bindLayoutParams" }, + tag = { "[$logTag] SBIV#bindLayoutParams" } ) { if (it != sbiv.layoutParams) { sbiv.layoutParams = it @@ -344,7 +340,7 @@ object NotificationIconContainerViewBinder { // a single SBIV instance for the group. Then this whole concept can go away. private inline fun <R> NotificationIconContainer.withIconReplacements( replacements: ArrayMap<String, StatusBarIcon>, - block: () -> R + block: () -> R, ): R { setReplacingIcons(replacements) return block().also { setReplacingIcons(null) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index a64f888cb238..f0b03065e637 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt @@ -45,8 +45,8 @@ import kotlinx.coroutines.flow.map class NotificationIconContainerStatusBarViewModel @Inject constructor( - @Background bgContext: CoroutineContext, - darkIconInteractor: DarkIconInteractor, + @Background private val bgContext: CoroutineContext, + private val darkIconInteractor: DarkIconInteractor, iconsInteractor: StatusBarNotificationIconsInteractor, headsUpIconInteractor: HeadsUpNotificationIconInteractor, keyguardInteractor: KeyguardInteractor, @@ -58,10 +58,9 @@ constructor( /** Are changes to the icon container animated? */ val animationsEnabled: Flow<Boolean> = - combine( - shadeInteractor.isShadeTouchable, - keyguardInteractor.isKeyguardShowing, - ) { panelTouchesEnabled, isKeyguardShowing -> + combine(shadeInteractor.isShadeTouchable, keyguardInteractor.isKeyguardShowing) { + panelTouchesEnabled, + isKeyguardShowing -> panelTouchesEnabled && !isKeyguardShowing } .flowOn(bgContext) @@ -69,8 +68,9 @@ constructor( .distinctUntilChanged() /** The colors with which to display the notification icons. */ - val iconColors: Flow<NotificationIconColorLookup> = - darkIconInteractor.darkState + fun iconColors(displayId: Int): Flow<NotificationIconColorLookup> = + darkIconInteractor + .darkState(displayId) .map { (areas: Collection<Rect>, tint: Int) -> NotificationIconColorLookup { viewBounds: Rect -> if (DarkIconDispatcher.isInAreas(areas, viewBounds)) { @@ -125,10 +125,8 @@ constructor( val isolatedIconLocation: Flow<Rect> = headsUpIconInteractor.isolatedIconLocation.filterNotNull().conflate().distinctUntilChanged() - private class IconColorsImpl( - override val tint: Int, - private val areas: Collection<Rect>, - ) : NotificationIconColors { + private class IconColorsImpl(override val tint: Int, private val areas: Collection<Rect>) : + NotificationIconColors { override fun staticDrawableColor(viewBounds: Rect): Int { return if (DarkIconDispatcher.isInAreas(areas, viewBounds)) { tint 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 b166defb96a2..2dcb706234b8 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 @@ -95,7 +95,7 @@ constructor( private val smartReplyStateInflater: SmartReplyStateInflater, private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, private val headsUpStyleProvider: HeadsUpStyleProvider, - private val logger: NotificationRowContentBinderLogger + private val logger: NotificationRowContentBinderLogger, ) : NotificationRowContentBinder { init { @@ -110,7 +110,7 @@ constructor( @InflationFlag contentToBind: Int, bindParams: BindParams, forceInflate: Boolean, - callback: InflationCallback? + callback: InflationCallback?, ) { if (row.isRemoved) { // We don't want to reinflate anything for removed notifications. Otherwise views might @@ -147,7 +147,7 @@ constructor( /* isMediaFlagEnabled = */ smartReplyStateInflater, notifLayoutInflaterFactoryProvider, headsUpStyleProvider, - logger + logger, ) if (inflateSynchronously) { task.onPostExecute(task.doInBackground()) @@ -165,7 +165,7 @@ constructor( @InflationFlag reInflateFlags: Int, builder: Notification.Builder, packageContext: Context, - smartRepliesInflater: SmartReplyStateInflater + smartRepliesInflater: SmartReplyStateInflater, ): InflationProgress { val systemUIContext = row.context val result = @@ -229,7 +229,7 @@ constructor( row, remoteInputManager.remoteViewsOnClickHandler, /* callback= */ null, - logger + logger, ) return result } @@ -246,7 +246,7 @@ constructor( override fun unbindContent( entry: NotificationEntry, row: ExpandableNotificationRow, - @InflationFlag contentToUnbind: Int + @InflationFlag contentToUnbind: Int, ) { logger.logUnbinding(entry, contentToUnbind) var curFlag = 1 @@ -268,7 +268,7 @@ constructor( private fun freeNotificationView( entry: NotificationEntry, row: ExpandableNotificationRow, - @InflationFlag inflateFlag: Int + @InflationFlag inflateFlag: Int, ) { when (inflateFlag) { FLAG_CONTENT_VIEW_CONTRACTED -> @@ -319,7 +319,7 @@ constructor( */ private fun cancelContentViewFrees( row: ExpandableNotificationRow, - @InflationFlag contentViews: Int + @InflationFlag contentViews: Int, ) { if (contentViews and FLAG_CONTENT_VIEW_CONTRACTED != 0) { row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED) @@ -372,7 +372,7 @@ constructor( private val smartRepliesInflater: SmartReplyStateInflater, private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, private val headsUpStyleProvider: HeadsUpStyleProvider, - private val logger: NotificationRowContentBinderLogger + private val logger: NotificationRowContentBinderLogger, ) : AsyncTask<Void, Void, Result<InflationProgress>>(), InflationCallback, InflationTask { private val context: Context get() = row.context @@ -393,7 +393,7 @@ constructor( context.packageManager.getApplicationInfoAsUser( packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, - userId + userId, ) } catch (e: PackageManager.NameNotFoundException) { return @@ -442,11 +442,11 @@ constructor( notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider, headsUpStyleProvider = headsUpStyleProvider, conversationProcessor = conversationProcessor, - logger = logger + logger = logger, ) logger.logAsyncTaskProgress( entry, - "getting existing smart reply state (on wrong thread!)" + "getting existing smart reply state (on wrong thread!)", ) val previousSmartReplyState: InflatedSmartReplyState? = row.existingSmartReplyState logger.logAsyncTaskProgress(entry, "inflating smart reply views") @@ -469,7 +469,7 @@ constructor( reInflateFlags, entry, context, - logger + logger, ) } } @@ -483,7 +483,7 @@ constructor( reInflateFlags, entry, context, - logger + logger, ) } } @@ -513,7 +513,7 @@ constructor( row, remoteViewClickHandler, this /* callback */, - logger + logger, ) } .onFailure { error -> handleError(error as Exception) } @@ -530,7 +530,7 @@ constructor( Log.e(TAG, "couldn't inflate view for notification $ident", e) callback?.handleInflationException( row.entry, - InflationException("Couldn't inflate contentViews$e") + InflationException("Couldn't inflate contentViews$e"), ) // Cancel any image loading tasks, not useful any more @@ -618,7 +618,7 @@ constructor( packageContext: Context, previousSmartReplyState: InflatedSmartReplyState?, inflater: SmartReplyStateInflater, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ) { val inflateContracted = (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0 && @@ -641,7 +641,7 @@ constructor( packageContext, entry, previousSmartReplyState, - result.inflatedSmartReplyState!! + result.inflatedSmartReplyState!!, ) } if (inflateHeadsUp) { @@ -652,7 +652,7 @@ constructor( packageContext, entry, previousSmartReplyState, - result.inflatedSmartReplyState!! + result.inflatedSmartReplyState!!, ) } } @@ -670,7 +670,7 @@ constructor( notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, headsUpStyleProvider: HeadsUpStyleProvider, conversationProcessor: ConversationNotificationProcessor, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ): InflationProgress { // process conversations and extract the messaging style val messagingStyle = @@ -713,7 +713,7 @@ constructor( logger.logAsyncTaskProgress(entry, "inflating public single line view model") SingleLineViewInflater.inflateRedactedSingleLineViewModel( systemUIContext, - entry.ranking.isConversation + entry.ranking.isConversation, ) } else null @@ -746,7 +746,7 @@ constructor( row: ExpandableNotificationRow, notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider, headsUpStyleProvider: HeadsUpStyleProvider, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ): NewRemoteViews { return TraceUtils.trace("NotificationContentInflater.createRemoteViews") { val entryForLogging: NotificationEntry = row.entry @@ -754,7 +754,7 @@ constructor( if (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0) { logger.logAsyncTaskProgress( entryForLogging, - "creating contracted remote view" + "creating contracted remote view", ) createContentView(builder, isMinimized, usesIncreasedHeight) } else null @@ -762,7 +762,7 @@ constructor( if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) { logger.logAsyncTaskProgress( entryForLogging, - "creating expanded remote view" + "creating expanded remote view", ) createExpandedView(builder, isMinimized) } else null @@ -770,7 +770,7 @@ constructor( if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) { logger.logAsyncTaskProgress( entryForLogging, - "creating heads up remote view" + "creating heads up remote view", ) val isHeadsUpCompact = headsUpStyleProvider.shouldApplyCompactStyle() if (isHeadsUpCompact) { @@ -791,7 +791,7 @@ constructor( ) { logger.logAsyncTaskProgress( entryForLogging, - "creating group summary remote view" + "creating group summary remote view", ) builder.makeNotificationGroupHeader() } else null @@ -802,7 +802,7 @@ constructor( ) { logger.logAsyncTaskProgress( entryForLogging, - "creating low-priority group summary remote view" + "creating low-priority group summary remote view", ) builder.makeLowPriorityContentView(true /* useRegularSubtext */) } else null @@ -812,7 +812,7 @@ constructor( expanded = expanded, public = public, normalGroupHeader = normalGroupHeader, - minimizedGroupHeader = minimizedGroupHeader + minimizedGroupHeader = minimizedGroupHeader, ) .withLayoutInflaterFactory(row, notifLayoutInflaterFactoryProvider) } @@ -820,7 +820,7 @@ constructor( private fun NewRemoteViews.withLayoutInflaterFactory( row: ExpandableNotificationRow, - provider: NotifLayoutInflaterFactory.Provider + provider: NotifLayoutInflaterFactory.Provider, ): NewRemoteViews { contracted?.let { it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED) @@ -848,7 +848,7 @@ constructor( row: ExpandableNotificationRow, remoteViewClickHandler: InteractionHandler?, callback: InflationCallback?, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ): CancellationSignal { Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row)) val privateLayout = row.privateLayout @@ -859,7 +859,7 @@ constructor( val isNewView = !canReapplyRemoteView( newView = result.remoteViews.contracted, - oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED) + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -890,7 +890,7 @@ constructor( existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED), runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } flag = FLAG_CONTENT_VIEW_EXPANDED @@ -898,7 +898,7 @@ constructor( val isNewView = !canReapplyRemoteView( newView = result.remoteViews.expanded, - oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED) + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -929,7 +929,7 @@ constructor( existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED), runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } flag = FLAG_CONTENT_VIEW_HEADS_UP @@ -937,7 +937,7 @@ constructor( val isNewView = !canReapplyRemoteView( newView = result.remoteViews.headsUp, - oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP) + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -968,7 +968,7 @@ constructor( existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP), runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } flag = FLAG_CONTENT_VIEW_PUBLIC @@ -976,7 +976,7 @@ constructor( val isNewView = !canReapplyRemoteView( newView = result.remoteViews.public, - oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC) + oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -1007,7 +1007,7 @@ constructor( existingWrapper = publicLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED), runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } if (AsyncGroupHeaderViewInflation.isEnabled) { @@ -1018,7 +1018,7 @@ constructor( !canReapplyRemoteView( newView = result.remoteViews.normalGroupHeader, oldView = - remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER) + remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER), ) val applyCallback: ApplyCallback = object : ApplyCallback() { @@ -1049,7 +1049,7 @@ constructor( existingWrapper = childrenContainer.notificationHeaderWrapper, runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } if (reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) { @@ -1059,15 +1059,15 @@ constructor( oldView = remoteViewCache.getCachedView( entry, - FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER - ) + FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER, + ), ) val applyCallback: ApplyCallback = object : ApplyCallback() { override fun setResultView(v: View) { logger.logAsyncTaskProgress( entry, - "low-priority group header view applied" + "low-priority group header view applied", ) result.inflatedMinimizedGroupHeaderView = v as NotificationHeaderView? @@ -1095,7 +1095,7 @@ constructor( existingWrapper = childrenContainer.minimizedGroupHeaderWrapper, runningInflations = runningInflations, applyCallback = applyCallback, - logger = logger + logger = logger, ) } } @@ -1110,7 +1110,7 @@ constructor( callback, entry, row, - logger + logger, ) val cancellationSignal = CancellationSignal() cancellationSignal.setOnCancelListener { @@ -1142,7 +1142,7 @@ constructor( existingWrapper: NotificationViewWrapper?, runningInflations: HashMap<Int, CancellationSignal>, applyCallback: ApplyCallback, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ) { val newContentView: RemoteViews = applyCallback.remoteView if (inflateSynchronously) { @@ -1152,7 +1152,7 @@ constructor( newContentView.apply( result.packageContext, parentLayout, - remoteViewClickHandler + remoteViewClickHandler, ) validateView(v, entry, row.resources) applyCallback.setResultView(v) @@ -1162,7 +1162,7 @@ constructor( newContentView.reapply( result.packageContext, existingView, - remoteViewClickHandler + remoteViewClickHandler, ) validateView(existingView, entry, row.resources) existingWrapper.onReinflated() @@ -1174,7 +1174,7 @@ constructor( row.entry, callback, logger, - "applying view synchronously" + "applying view synchronously", ) // Add a running inflation to make sure we don't trigger callbacks. // Safe to do because only happens in tests. @@ -1199,7 +1199,7 @@ constructor( row.entry, callback, logger, - "applied invalid view" + "applied invalid view", ) runningInflations.remove(inflationId) return @@ -1219,7 +1219,7 @@ constructor( callback, entry, row, - logger + logger, ) } @@ -1234,20 +1234,20 @@ constructor( newContentView.apply( result.packageContext, parentLayout, - remoteViewClickHandler + remoteViewClickHandler, ) } else { newContentView.reapply( result.packageContext, existingView, - remoteViewClickHandler + remoteViewClickHandler, ) existingView!! } Log.wtf( TAG, "Async Inflation failed but normal inflation finished normally.", - e + e, ) onViewApplied(newView) } catch (anotherException: Exception) { @@ -1258,7 +1258,7 @@ constructor( row.entry, callback, logger, - "applying view" + "applying view", ) } } @@ -1270,7 +1270,7 @@ constructor( parentLayout, inflationExecutor, listener, - remoteViewClickHandler + remoteViewClickHandler, ) } else { newContentView.reapplyAsync( @@ -1278,7 +1278,7 @@ constructor( existingView, inflationExecutor, listener, - remoteViewClickHandler + remoteViewClickHandler, ) } runningInflations[inflationId] = cancellationSignal @@ -1299,7 +1299,7 @@ constructor( private fun satisfiesMinHeightRequirement( view: View, entry: NotificationEntry, - resources: Resources + resources: Resources, ): Boolean { return if (!requiresHeightCheck(entry)) { true @@ -1353,7 +1353,7 @@ constructor( notification: NotificationEntry, callback: InflationCallback?, logger: NotificationRowContentBinderLogger, - logContext: String + logContext: String, ) { Assert.isMainThread() logger.logAsyncTaskException(notification, logContext, e) @@ -1375,7 +1375,7 @@ constructor( endListener: InflationCallback?, entry: NotificationEntry, row: ExpandableNotificationRow, - logger: NotificationRowContentBinderLogger + logger: NotificationRowContentBinderLogger, ): Boolean { Assert.isMainThread() if (runningInflations.isNotEmpty()) { @@ -1439,19 +1439,19 @@ constructor( FLAG_CONTENT_VIEW_CONTRACTED, result.remoteViews.contracted, result.inflatedContentView, - privateLayout::setContractedChild + privateLayout::setContractedChild, ) remoteViewsUpdater.setContentView( FLAG_CONTENT_VIEW_EXPANDED, result.remoteViews.expanded, result.inflatedExpandedView, - privateLayout::setExpandedChild + privateLayout::setExpandedChild, ) remoteViewsUpdater.setSmartReplies( FLAG_CONTENT_VIEW_EXPANDED, result.remoteViews.expanded, result.expandedInflatedSmartReplies, - privateLayout::setExpandedInflatedSmartReplies + privateLayout::setExpandedInflatedSmartReplies, ) if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) { row.setExpandable(result.remoteViews.expanded != null) @@ -1460,19 +1460,19 @@ constructor( FLAG_CONTENT_VIEW_HEADS_UP, result.remoteViews.headsUp, result.inflatedHeadsUpView, - privateLayout::setHeadsUpChild + privateLayout::setHeadsUpChild, ) remoteViewsUpdater.setSmartReplies( FLAG_CONTENT_VIEW_HEADS_UP, result.remoteViews.headsUp, result.headsUpInflatedSmartReplies, - privateLayout::setHeadsUpInflatedSmartReplies + privateLayout::setHeadsUpInflatedSmartReplies, ) remoteViewsUpdater.setContentView( FLAG_CONTENT_VIEW_PUBLIC, result.remoteViews.public, result.inflatedPublicView, - publicLayout::setContractedChild + publicLayout::setContractedChild, ) if (AsyncGroupHeaderViewInflation.isEnabled) { remoteViewsUpdater.setContentView( @@ -1540,7 +1540,7 @@ constructor( private fun createExpandedView( builder: Notification.Builder, - isMinimized: Boolean + isMinimized: Boolean, ): RemoteViews? { @Suppress("DEPRECATION") val bigContentView: RemoteViews? = builder.createBigContentView() @@ -1558,7 +1558,7 @@ constructor( private fun createContentView( builder: Notification.Builder, isMinimized: Boolean, - useLarge: Boolean + useLarge: Boolean, ): RemoteViews { return if (isMinimized) { builder.makeLowPriorityContentView(false /* useRegularSubtext */) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt index 7177a7bd473a..08c1d71b86c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt @@ -73,8 +73,8 @@ constructor(private val userManager: UserManager, dumpManager: DumpManager) : private val cache = NotifCollectionCache<Boolean>() override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean { - val packageContext = notification.getPackageContext(context) return cache.getOrFetch(notification.packageName) { + val packageContext = notification.getPackageContext(context) !belongsToHeadlessSystemApp(packageContext) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt index 3a650aa19eb0..53d0c2e879e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt @@ -22,6 +22,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor import com.android.systemui.util.animation.data.repository.AnimationStatusRepository import com.android.systemui.util.kotlin.WithPrev @@ -46,9 +47,9 @@ class HideNotificationsInteractor @Inject constructor( private val unfoldTransitionInteractor: UnfoldTransitionInteractor, - private val configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, private val animationsStatus: AnimationStatusRepository, - private val powerInteractor: PowerInteractor + private val powerInteractor: PowerInteractor, ) { val shouldHideNotifications: Flow<Boolean> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt index e644815960aa..b6ce70826f9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt @@ -47,7 +47,7 @@ class SharedNotificationContainerInteractor constructor( @ShadeDisplayAware private val context: Context, private val splitShadeStateController: Lazy<SplitShadeStateController>, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, keyguardInteractor: KeyguardInteractor, deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index f75c89aeb44c..bffcae99e7f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -20,6 +20,7 @@ import android.view.LayoutInflater import android.view.View import androidx.lifecycle.lifecycleScope import com.android.app.tracing.TraceUtils.traceAsync +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.logging.MetricsLogger import com.android.internal.logging.nano.MetricsProto import com.android.systemui.common.ui.ConfigurationState @@ -30,6 +31,7 @@ import com.android.systemui.lifecycle.repeatWhenAttachedToWindow 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.ShadeDisplayAware import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController @@ -70,7 +72,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */ class NotificationListViewBinder @@ -78,7 +79,7 @@ class NotificationListViewBinder constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, private val hiderTracker: DisplaySwitchNotificationsHiderTracker, - private val configuration: ConfigurationState, + @ShadeDisplayAware private val configuration: ConfigurationState, private val falsingManager: FalsingManager, private val hunBinder: HeadsUpNotificationViewBinder, private val loggerOptional: Optional<NotificationStatsLogger>, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 4a768714b84f..c5bef99f9307 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -28,6 +28,7 @@ import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel import com.android.systemui.util.kotlin.FlowDumperImpl @@ -48,7 +49,7 @@ constructor( @Main private val mainImmediateDispatcher: CoroutineDispatcher, private val view: NotificationScrollView, private val viewModelFactory: NotificationScrollViewModel.Factory, - private val configuration: ConfigurationState, + @ShadeDisplayAware private val configuration: ConfigurationState, ) : FlowDumperImpl(dumpManager) { private val viewLeftOffset = MutableStateFlow(0).dumpValue("viewLeftOffset") 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 827e2bfeba0f..fb60f266e188 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 @@ -72,6 +72,7 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.LargeScreenHeaderHelper +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode.Dual import com.android.systemui.shade.shared.model.ShadeMode.Single @@ -116,7 +117,7 @@ constructor( dumpManager: DumpManager, @Application applicationScope: CoroutineScope, private val context: Context, - configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val shadeInteractor: ShadeInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index 398c1d43d4fc..90b591f173f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -23,11 +23,15 @@ import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.Rect; import android.util.ArrayMap; +import android.view.Display; import android.widget.ImageView; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; + import kotlinx.coroutines.flow.FlowKt; import kotlinx.coroutines.flow.MutableStateFlow; import kotlinx.coroutines.flow.StateFlow; @@ -36,17 +40,15 @@ import kotlinx.coroutines.flow.StateFlowKt; import java.io.PrintWriter; import java.util.ArrayList; -import javax.inject.Inject; - /** */ -@SysUISingleton public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, LightBarTransitionsController.DarkIntensityApplier { private final LightBarTransitionsController mTransitionsController; private final ArrayList<Rect> mTintAreas = new ArrayList<>(); private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>(); + private final DumpManager mDumpManager; private int mIconTint = DEFAULT_ICON_TINT; private int mContrastTint = DEFAULT_INVERSE_ICON_TINT; @@ -61,14 +63,25 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, private final MutableStateFlow<DarkChange> mDarkChangeFlow = StateFlowKt.MutableStateFlow( DarkChange.EMPTY); + private final String mDumpableName; + + /** */ + @AssistedFactory + @FunctionalInterface + public interface Factory { + /** */ + DarkIconDispatcherImpl create(int displayId, Context context); + } + /** */ - @Inject + @AssistedInject public DarkIconDispatcherImpl( - Context context, + @Assisted int displayId, + @Assisted Context context, LightBarTransitionsController.Factory lightBarTransitionsControllerFactory, DumpManager dumpManager) { - + mDumpManager = dumpManager; if (newStatusBarIcons()) { mDarkModeIconColorSingleTone = Color.BLACK; mLightModeIconColorSingleTone = Color.WHITE; @@ -81,7 +94,19 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, mTransitionsController = lightBarTransitionsControllerFactory.create(this); - dumpManager.registerDumpable(getClass().getSimpleName(), this); + mDumpableName = getDumpableName(displayId); + dumpManager.registerNormalDumpable(mDumpableName, this); + } + + @Override + public void stop() { + mDumpManager.unregisterDumpable(mDumpableName); + } + + private String getDumpableName(int displayId) { + String dumpableNameSuffix = + displayId == Display.DEFAULT_DISPLAY ? "" : String.valueOf(displayId); + return getClass().getSimpleName() + dumpableNameSuffix; } public LightBarTransitionsController getTransitionsController() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index d0f4b6f4a4bb..8de03d83d7af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -26,6 +26,7 @@ import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; +import com.android.systemui.dagger.qualifiers.DisplaySpecific; import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -117,7 +118,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar PhoneStatusBarTransitions phoneStatusBarTransitions, KeyguardBypassController bypassController, NotificationWakeUpCoordinator wakeUpCoordinator, - DarkIconDispatcher darkIconDispatcher, + @DisplaySpecific DarkIconDispatcher darkIconDispatcher, KeyguardStateController keyguardStateController, CommandQueue commandQueue, NotificationStackScrollLayoutController stackScrollerController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java index edc1f88b7579..ccb9a119d92f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java @@ -133,7 +133,7 @@ public class LightBarControllerImpl implements public LightBarControllerImpl( @Assisted int displayId, @Assisted CoroutineScope coroutineScope, - DarkIconDispatcher darkIconDispatcher, + @Assisted DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, @Assisted StatusBarModePerDisplayRepository statusBarModeRepository, @@ -487,6 +487,7 @@ public class LightBarControllerImpl implements LightBarControllerImpl create( int displayId, CoroutineScope coroutineScope, + DarkIconDispatcher darkIconDispatcher, StatusBarModePerDisplayRepository statusBarModePerDisplayRepository); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 424549453af3..16e023ce17fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -27,6 +27,7 @@ import androidx.annotation.VisibleForTesting import com.android.systemui.Flags import com.android.systemui.Gefingerpoken import com.android.systemui.battery.BatteryMeterView +import com.android.systemui.dagger.qualifiers.DisplaySpecific import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS import com.android.systemui.plugins.DarkIconDispatcher @@ -341,7 +342,7 @@ private constructor( private val viewUtil: ViewUtil, private val configurationController: ConfigurationController, private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, - private val darkIconDispatcher: DarkIconDispatcher, + @DisplaySpecific private val darkIconDispatcher: DarkIconDispatcher, private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore, ) { fun create(view: PhoneStatusBarView): PhoneStatusBarViewController { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt index c341bd3466ed..394502b2d31f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt @@ -28,10 +28,13 @@ import androidx.annotation.ColorInt import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R +import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore +import com.android.systemui.statusbar.data.repository.SysuiDarkIconDispatcherStore import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener @@ -40,14 +43,14 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch class StatusOverlayHoverListenerFactory @Inject constructor( @Main private val resources: Resources, private val configurationController: ConfigurationController, - private val darkIconDispatcher: SysuiDarkIconDispatcher, + private val darkIconDispatcherStore: SysuiDarkIconDispatcherStore, + private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore, ) { /** Creates listener always using the same light color for overlay */ @@ -63,7 +66,7 @@ constructor( * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay */ fun createDarkAwareListener(view: View) = - createDarkAwareListener(view, darkIconDispatcher.darkChangeFlow()) + createDarkAwareListener(view, view.darkIconDispatcher.darkChangeFlow()) /** * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay @@ -78,7 +81,7 @@ constructor( ) = createDarkAwareListener( view, - darkIconDispatcher.darkChangeFlow(), + view.darkIconDispatcher.darkChangeFlow(), leftHoverMargin, rightHoverMargin, topHoverMargin, @@ -92,8 +95,8 @@ constructor( fun createDarkAwareListener(view: View, darkFlow: StateFlow<DarkChange>) = StatusOverlayHoverListener( view, - configurationController, - resources, + view.statusBarConfigurationController, + view.resources, darkFlow.map { toHoverTheme(view, it) }, ) @@ -107,8 +110,8 @@ constructor( ) = StatusOverlayHoverListener( view, - configurationController, - resources, + view.statusBarConfigurationController, + view.resources, darkFlow.map { toHoverTheme(view, it) }, leftHoverMargin, rightHoverMargin, @@ -116,6 +119,12 @@ constructor( bottomHoverMargin, ) + private val View.statusBarConfigurationController + get() = statusBarConfigurationControllerStore.forDisplay(context.displayId) + + private val View.darkIconDispatcher + get() = darkIconDispatcherStore.forDisplay(context.displayId) + private fun toHoverTheme(view: View, darkChange: DarkChange): HoverTheme { val calculatedTint = DarkIconDispatcher.getTint(darkChange.areas, view, darkChange.tint) // currently calculated tint is either white or some shade of black. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt index ba377497bce4..49356eba2842 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.phone.data.repository import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher +import com.android.systemui.statusbar.data.repository.SysuiDarkIconDispatcherStore import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange import dagger.Binds import dagger.Module @@ -25,16 +25,16 @@ import kotlinx.coroutines.flow.StateFlow /** Dark-mode state for tinting icons. */ interface DarkIconRepository { - val darkState: StateFlow<DarkChange> + fun darkState(displayId: Int): StateFlow<DarkChange> } @SysUISingleton class DarkIconRepositoryImpl @Inject -constructor( - darkIconDispatcher: SysuiDarkIconDispatcher, -) : DarkIconRepository { - override val darkState: StateFlow<DarkChange> = darkIconDispatcher.darkChangeFlow() +constructor(private val darkIconDispatcherStore: SysuiDarkIconDispatcherStore) : + DarkIconRepository { + override fun darkState(displayId: Int): StateFlow<DarkChange> = + darkIconDispatcherStore.forDisplay(displayId).darkChangeFlow() } @Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt index 72f45406b35e..095f0cba8a46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt @@ -22,7 +22,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map /** States pertaining to calculating colors for icons in dark mode. */ -class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) { +class DarkIconInteractor @Inject constructor(private val repository: DarkIconRepository) { /** Dark-mode state for tinting icons. */ - val darkState: Flow<DarkState> = repository.darkState.map { DarkState(it.areas, it.tint) } + fun darkState(displayId: Int): Flow<DarkState> = + repository.darkState(displayId).map { DarkState(it.areas, it.tint) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 013141b5e142..5cc44762dde8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -351,8 +351,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBar.restoreHierarchyState( savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE)); } - mDarkIconManager = mDarkIconManagerFactory.create( - view.findViewById(R.id.statusIcons), StatusBarLocation.HOME); + mDarkIconManager = + mDarkIconManagerFactory.create( + view.findViewById(R.id.statusIcons), + StatusBarLocation.HOME, + mHomeStatusBarComponent.getDarkIconDispatcher()); mDarkIconManager.setShouldLog(true); updateBlockedIcons(); mStatusBarIconController.addIconGroup(mDarkIconManager); @@ -496,11 +499,12 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue NotificationIconContainer notificationIcons = notificationIconArea.requireViewById(R.id.notificationIcons); mNotificationIconAreaInner = notificationIcons; - if (getContext().getDisplayId() == Display.DEFAULT_DISPLAY) { + int displayId = mHomeStatusBarComponent.getDisplayId(); + if (displayId == Display.DEFAULT_DISPLAY) { //TODO(b/369337701): implement notification icons for all displays. // Currently if we try to bind for all displays, there is a crash, because the same // notification icon view can't have multiple parents. - mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons); + mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons, displayId); } if (!StatusBarSimpleFragment.isEnabled()) { @@ -939,7 +943,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue if (mCarrierConfigTracker.getShowOperatorNameInStatusBarConfig(subId)) { View view = mStatusBar.findViewById(R.id.operator_name); mOperatorNameViewController = - mOperatorNameViewControllerFactory.create((OperatorNameView) view); + mOperatorNameViewControllerFactory.create( + (OperatorNameView) view, + mHomeStatusBarComponent.getDarkIconDispatcher()); mOperatorNameViewController.init(); // This view should not be visible on lock-screen if (mKeyguardStateController.isShowing()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java index d4cb625d3722..f8ad0f2324bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.phone.fragment.dagger; import com.android.systemui.battery.BatteryMeterViewController; +import com.android.systemui.dagger.qualifiers.DisplaySpecific; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.LegacyLightsOutNotifController; @@ -121,4 +123,12 @@ public interface HomeStatusBarComponent { /** */ StatusBarBoundsProvider getBoundsProvider(); + + /** */ + @DisplaySpecific + DarkIconDispatcher getDarkIconDispatcher(); + + /** */ + @DisplaySpecific + int getDisplayId(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java index 45c53b05d478..182f8d7e2fd6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java @@ -22,8 +22,10 @@ import android.view.ViewStub; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.DisplaySpecific; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.res.R; import com.android.systemui.statusbar.HeadsUpStatusBarView; +import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore; import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController; import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; @@ -164,4 +166,12 @@ public interface HomeStatusBarModule { return store.forDisplay(displayId); } + /** */ + @Provides + @HomeStatusBarScope + @DisplaySpecific + static DarkIconDispatcher darkIconDispatcher( + @DisplaySpecific int displayId, DarkIconDispatcherStore store) { + return store.forDisplay(displayId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java index 6c303303c8f8..8d314eeb8493 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.phone.ui; import android.widget.LinearLayout; import com.android.internal.statusbar.StatusBarIcon; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider; @@ -29,35 +28,34 @@ import com.android.systemui.statusbar.phone.StatusBarLocation; import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter; import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter; -import javax.inject.Inject; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; -/** - * Version of {@link IconManager} that observes state from the DarkIconDispatcher. - */ +/** Version of {@link IconManager} that observes state from the {@link DarkIconDispatcher}. */ public class DarkIconManager extends IconManager { private final DarkIconDispatcher mDarkIconDispatcher; private final int mIconHorizontalMargin; + @AssistedInject public DarkIconManager( - LinearLayout linearLayout, - StatusBarLocation location, + @Assisted LinearLayout linearLayout, + @Assisted StatusBarLocation location, WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider, - DarkIconDispatcher darkIconDispatcher) { - super(linearLayout, - location, - wifiUiAdapter, - mobileUiAdapter, - mobileContextProvider); - mIconHorizontalMargin = mContext.getResources().getDimensionPixelSize( - com.android.systemui.res.R.dimen.status_bar_icon_horizontal_margin); + @Assisted DarkIconDispatcher darkIconDispatcher) { + super(linearLayout, location, wifiUiAdapter, mobileUiAdapter, mobileContextProvider); + mIconHorizontalMargin = + mContext.getResources() + .getDimensionPixelSize( + com.android.systemui.res.R.dimen.status_bar_icon_horizontal_margin); mDarkIconDispatcher = darkIconDispatcher; } @Override - protected void onIconAdded(int index, String slot, boolean blocked, - StatusBarIconHolder holder) { + protected void onIconAdded( + int index, String slot, boolean blocked, StatusBarIconHolder holder) { StatusIconDisplayable view = addHolder(index, slot, blocked, holder); mDarkIconDispatcher.addDarkReceiver(view); } @@ -106,34 +104,14 @@ public class DarkIconManager extends IconManager { super.exitDemoMode(); } - @SysUISingleton - public static class Factory { - private final WifiUiAdapter mWifiUiAdapter; - private final MobileContextProvider mMobileContextProvider; - private final MobileUiAdapter mMobileUiAdapter; - private final DarkIconDispatcher mDarkIconDispatcher; - - @Inject - public Factory( - WifiUiAdapter wifiUiAdapter, - MobileContextProvider mobileContextProvider, - MobileUiAdapter mobileUiAdapter, - DarkIconDispatcher darkIconDispatcher) { - mWifiUiAdapter = wifiUiAdapter; - mMobileContextProvider = mobileContextProvider; - mMobileUiAdapter = mobileUiAdapter; - mDarkIconDispatcher = darkIconDispatcher; - } + /** */ + @AssistedFactory + public interface Factory { - /** Creates a new {@link DarkIconManager} for the given view group and location. */ - public DarkIconManager create(LinearLayout group, StatusBarLocation location) { - return new DarkIconManager( - group, - location, - mWifiUiAdapter, - mMobileUiAdapter, - mMobileContextProvider, - mDarkIconDispatcher); - } + /** Creates a new {@link DarkIconManager}. */ + DarkIconManager create( + LinearLayout group, + StatusBarLocation location, + DarkIconDispatcher darkIconDispatcher); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt index a472318a1e86..247abc3da175 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt @@ -31,7 +31,10 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R +import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.phone.PhoneStatusBarView @@ -44,7 +47,6 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarVie import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel import javax.inject.Inject -import com.android.app.tracing.coroutines.launchTraced as launch /** Factory to simplify the dependency management for [StatusBarRoot] */ class StatusBarRootFactory @@ -56,6 +58,7 @@ constructor( private val darkIconManagerFactory: DarkIconManager.Factory, private val iconController: StatusBarIconController, private val ongoingCallController: OngoingCallController, + private val darkIconDispatcherStore: DarkIconDispatcherStore, ) { fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView { val composeView = ComposeView(root.context) @@ -69,6 +72,7 @@ constructor( darkIconManagerFactory = darkIconManagerFactory, iconController = iconController, ongoingCallController = ongoingCallController, + darkIconDispatcher = darkIconDispatcherStore.forDisplay(root.context.displayId), onViewCreated = andThen, ) } @@ -97,6 +101,7 @@ fun StatusBarRoot( darkIconManagerFactory: DarkIconManager.Factory, iconController: StatusBarIconController, ongoingCallController: OngoingCallController, + darkIconDispatcher: DarkIconDispatcher, onViewCreated: (ViewGroup) -> Unit, ) { // None of these methods are used when [StatusBarSimpleFragment] is on. @@ -135,7 +140,11 @@ fun StatusBarRoot( phoneStatusBarView.requireViewById<StatusIconContainer>(R.id.statusIcons) // TODO(b/364360986): turn this into a repo/intr/viewmodel val darkIconManager = - darkIconManagerFactory.create(statusIconContainer, StatusBarLocation.HOME) + darkIconManagerFactory.create( + statusIconContainer, + StatusBarLocation.HOME, + darkIconDispatcher, + ) iconController.addIconGroup(darkIconManager) // TODO(b/372657935): This won't be needed once OngoingCallController is @@ -157,9 +166,13 @@ fun StatusBarRoot( // TODO(b/369337701): implement notification icons for all displays. // Currently if we try to bind for all displays, there is a crash, because the // same notification icon view can't have multiple parents. - if (context.displayId == Display.DEFAULT_DISPLAY) { + val displayId = context.displayId + if (displayId == Display.DEFAULT_DISPLAY) { scope.launch { - notificationIconsBinder.bindWhileAttached(notificationIconContainer) + notificationIconsBinder.bindWhileAttached( + notificationIconContainer, + displayId, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt index 03499cbdccb2..885a2b0d7305 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt @@ -19,6 +19,7 @@ import android.view.View import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress @@ -41,7 +42,7 @@ class UnfoldTransitionInteractor @Inject constructor( private val repository: UnfoldTransitionRepository, - private val configurationInteractor: ConfigurationInteractor, + @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, ) { /** Returns availability of fold/unfold transitions on the device */ val isAvailable: Boolean diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt index f7ad3205f3dd..9440a9364b62 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog.dagger import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent import dagger.BindsInstance import dagger.Subcomponent import kotlinx.coroutines.CoroutineScope @@ -40,6 +41,8 @@ interface VolumeDialogComponent { @VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog + fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory + @Subcomponent.Factory interface Factory { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt new file mode 100644 index 000000000000..538ee47915a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt @@ -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.systemui.volume.dialog.sliders.dagger + +import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType +import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder +import dagger.BindsInstance +import dagger.Subcomponent + +/** + * This component hosts all the stuff, that Volume Dialog sliders need. It's recreated alongside + * each slider view. + */ +@VolumeDialogSliderScope +@Subcomponent +interface VolumeDialogSliderComponent { + + fun sliderViewBinder(): VolumeDialogSliderViewBinder + + @Subcomponent.Factory + interface Factory { + + fun create(@BindsInstance sliderType: VolumeDialogSliderType): VolumeDialogSliderComponent + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt new file mode 100644 index 000000000000..9f5e0f6ba1aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.dagger + +import javax.inject.Scope + +/** + * Volume Panel Slider dependency injection scope. This scope is created for each of the volume + * sliders in the dialog. + */ +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +@Scope +annotation class VolumeDialogSliderScope diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt index 876bf2c4a154..2967fe8ca906 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt @@ -17,22 +17,21 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor import com.android.systemui.plugins.VolumeDialogController -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.mapNotNull /** Operates a state of particular slider of the Volume Dialog. */ +@VolumeDialogSliderScope class VolumeDialogSliderInteractor -@AssistedInject +@Inject constructor( - @Assisted private val sliderType: VolumeDialogSliderType, + private val sliderType: VolumeDialogSliderType, volumeDialogStateInteractor: VolumeDialogStateInteractor, private val volumeDialogController: VolumeDialogController, ) { @@ -56,11 +55,4 @@ constructor( setActiveStream(sliderType.audioStream) } } - - @VolumeDialogScope - @AssistedFactory - interface Factory { - - fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderInteractor - } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index 3bf8c54cb9d8..1c231b521bae 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -24,16 +24,14 @@ import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.viewModel import com.android.systemui.res.R -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory import com.android.systemui.volume.dialog.ui.utils.awaitAnimation import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import javax.inject.Inject import kotlin.math.roundToInt import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.launchIn @@ -41,10 +39,11 @@ import kotlinx.coroutines.flow.onEach private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L +@VolumeDialogSliderScope class VolumeDialogSliderViewBinder -@AssistedInject +@Inject constructor( - @Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel, + private val viewModelFactory: VolumeDialogSliderViewModel.Factory, private val jankListenerFactory: JankListenerFactory, ) { @@ -58,7 +57,7 @@ constructor( viewModel( traceName = "VolumeDialogSliderViewBinder", minWindowLifecycleState = WindowLifecycleState.ATTACHED, - factory = { viewModelProvider() }, + factory = { viewModelFactory.create() }, ) { viewModel -> sliderView.addOnChangeListener { _, value, fromUser -> viewModel.setStreamVolume(value.roundToInt(), fromUser) @@ -85,15 +84,6 @@ constructor( ) } } - - @AssistedFactory - @VolumeDialogScope - interface Factory { - - fun create( - viewModelProvider: () -> VolumeDialogSliderViewModel - ): VolumeDialogSliderViewBinder - } } private suspend fun Slider.setValueAnimated( diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt index 0a4e3f481e88..a17c1e541b5e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt @@ -28,7 +28,6 @@ import com.android.systemui.res.R import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel import javax.inject.Inject -import kotlin.math.abs import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -50,15 +49,17 @@ constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory) ) { viewModel -> viewModel.sliders .onEach { uiModel -> - uiModel.sliderViewBinder.bind(volumeDialog) + uiModel.sliderComponent.sliderViewBinder().bind(volumeDialog) - val floatingSliderViewBinders = uiModel.floatingSliderViewBinders + val floatingSliderViewBinders = uiModel.floatingSliderComponent floatingSlidersContainer.ensureChildCount( viewLayoutId = R.layout.volume_dialog_slider_floating, count = floatingSliderViewBinders.size, ) - floatingSliderViewBinders.fastForEachIndexed { index, viewBinder -> - viewBinder.bind(floatingSlidersContainer.getChildAt(index)) + floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent -> + sliderComponent + .sliderViewBinder() + .bind(floatingSlidersContainer.getChildAt(index)) } } .launchIn(this) @@ -76,7 +77,7 @@ private fun ViewGroup.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) } childCountDelta < 0 -> { val inflater = LayoutInflater.from(context) - repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) } + repeat(-childCountDelta) { inflater.inflate(viewLayoutId, this, true) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index ea0b49d5294e..cf04d45d54ff 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -22,7 +22,6 @@ import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor -import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope @@ -53,7 +52,7 @@ private const val VOLUME_UPDATE_GRACE_PERIOD = 1000 class VolumeDialogSliderViewModel @AssistedInject constructor( - @Assisted private val interactor: VolumeDialogSliderInteractor, + private val interactor: VolumeDialogSliderInteractor, private val visibilityInteractor: VolumeDialogVisibilityInteractor, @VolumeDialog private val coroutineScope: CoroutineScope, private val systemClock: SystemClock, @@ -90,11 +89,11 @@ constructor( private fun getTimestampMillis(): Long = systemClock.uptimeMillis() + private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long) + @AssistedFactory interface Factory { - fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel + fun create(): VolumeDialogSliderViewModel } - - private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt index b5b292fa4a66..d1972231d373 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt @@ -17,16 +17,13 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog -import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor +import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor -import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType -import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -36,29 +33,21 @@ class VolumeDialogSlidersViewModel constructor( @VolumeDialog coroutineScope: CoroutineScope, private val slidersInteractor: VolumeDialogSlidersInteractor, - private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory, - private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory, - private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory, + private val sliderComponentFactory: VolumeDialogSliderComponent.Factory, ) { val sliders: Flow<VolumeDialogSliderUiModel> = slidersInteractor.sliders - .distinctUntilChanged() .map { slidersModel -> VolumeDialogSliderUiModel( - sliderViewBinder = createSliderViewBinder(slidersModel.slider), - floatingSliderViewBinders = - slidersModel.floatingSliders.map(::createSliderViewBinder), + sliderComponent = sliderComponentFactory.create(slidersModel.slider), + floatingSliderComponent = + slidersModel.floatingSliders.map(sliderComponentFactory::create), ) } .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() - private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder = - sliderViewBinderFactory.create { - sliderViewModelFactory.create(sliderInteractorFactory.create(type)) - } - @AssistedFactory interface Factory { @@ -68,6 +57,6 @@ constructor( /** Models slider ui */ data class VolumeDialogSliderUiModel( - val sliderViewBinder: VolumeDialogSliderViewBinder, - val floatingSliderViewBinders: List<VolumeDialogSliderViewBinder>, + val sliderComponent: VolumeDialogSliderComponent, + val floatingSliderComponent: List<VolumeDialogSliderComponent>, ) diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt index 9be669f3df0c..869a6a2e87d5 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt @@ -18,16 +18,19 @@ package com.android.systemui.volume.dialog.ui.viewmodel import android.content.Context import com.android.systemui.res.R +import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor +import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel import com.android.systemui.volume.dialog.shared.model.streamLabel -import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor +import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map /** Provides a state for the Volume Dialog. */ @@ -38,18 +41,21 @@ constructor( private val context: Context, dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor, - private val volumeDialogSliderInteractorFactory: VolumeDialogSliderInteractor.Factory, + volumeDialogStateInteractor: VolumeDialogStateInteractor, ) { val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> = dialogVisibilityInteractor.dialogVisibility val dialogTitle: Flow<String> = - volumeDialogSlidersInteractor.sliders.flatMapLatest { slidersModel -> - val interactor = volumeDialogSliderInteractorFactory.create(slidersModel.slider) - interactor.slider.map { sliderModel -> - context.getString(R.string.volume_dialog_title, sliderModel.streamLabel(context)) + combine( + volumeDialogStateInteractor.volumeDialogState, + volumeDialogSlidersInteractor.sliders.map { it.slider }, + ) { state: VolumeDialogStateModel, sliderType: VolumeDialogSliderType -> + state.streamModels[sliderType.audioStream]?.let { model -> + context.getString(R.string.volume_dialog_title, model.streamLabel(context)) + } } - } + .filterNotNull() @AssistedFactory interface Factory { diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json index aa8044515ea2..aa8044515ea2 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json index a840d3cb1225..7abff2c74531 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenLaunching_withSpring.json", + "goldenIdentifier": "backgroundAnimation_whenLaunching_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimation_whenLaunching[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenLaunching_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json index aa8044515ea2..aa8044515ea2 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json index a840d3cb1225..561961145ca1 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenReturning_withSpring.json", + "goldenIdentifier": "backgroundAnimation_whenReturning_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimation_whenReturning[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenReturning_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json index 7f623575fef4..7f623575fef4 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json index 18eedd450751..825190ba7a32 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenLaunching_withSpring.json", + "goldenIdentifier": "backgroundAnimationWithoutFade_whenLaunching_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimationWithoutFade_whenLaunching[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenLaunching_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json index 98005c53f6e0..98005c53f6e0 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json index 18eedd450751..63c263175122 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenReturning_withSpring.json", + "goldenIdentifier": "backgroundAnimationWithoutFade_whenReturning_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimationWithoutFade_whenReturning[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenReturning_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index c6e4e0db8945..fa88f6207c49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -46,7 +46,6 @@ import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.View; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.animation.AccelerateInterpolator; import android.window.InputTransferToken; @@ -1030,10 +1029,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { callback, sysUiState, secureSettings, - scvhSupplier, - sfVsyncFrameProvider, - WindowManagerGlobal::getWindowSession, - viewCaptureAwareWindowManager); + scvhSupplier); mSpyController = Mockito.mock(WindowMagnificationController.class); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java deleted file mode 100644 index 9b09ec2ec55f..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java +++ /dev/null @@ -1,1594 +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.accessibility; - -import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static android.view.MotionEvent.ACTION_DOWN; -import static android.view.MotionEvent.ACTION_UP; -import static android.view.WindowInsets.Type.systemGestures; -import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; - -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.AdditionalAnswers.returnsSecondArg; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import static java.util.Arrays.asList; - -import android.animation.ValueAnimator; -import android.annotation.IdRes; -import android.annotation.Nullable; -import android.app.Instrumentation; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Insets; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.Handler; -import android.os.RemoteException; -import android.os.SystemClock; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; -import android.provider.Settings; -import android.testing.TestableLooper; -import android.testing.TestableResources; -import android.util.Size; -import android.view.AttachedSurfaceControl; -import android.view.Display; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewRootImpl; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.widget.FrameLayout; -import android.window.InputTransferToken; - -import androidx.test.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.LargeTest; - -import com.android.app.viewcapture.ViewCaptureAwareWindowManager; -import com.android.systemui.Flags; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.AnimatorTestRule; -import com.android.systemui.kosmos.KosmosJavaAdapter; -import com.android.systemui.model.SysUiState; -import com.android.systemui.res.R; -import com.android.systemui.settings.FakeDisplayTracker; -import com.android.systemui.util.FakeSharedPreferences; -import com.android.systemui.util.leak.ReferenceTestUtils; -import com.android.systemui.util.settings.SecureSettings; -import com.android.systemui.utils.os.FakeHandler; - -import com.google.common.util.concurrent.AtomicDouble; - -import org.junit.After; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; - -@LargeTest -@TestableLooper.RunWithLooper -@RunWith(AndroidJUnit4.class) -@EnableFlags(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER) -public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase { - - @Rule - // NOTE: pass 'null' to allow this test advances time on the main thread. - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null); - - private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; - @Mock - private MirrorWindowControl mMirrorWindowControl; - @Mock - private WindowMagnifierCallback mWindowMagnifierCallback; - @Mock - IRemoteMagnificationAnimationCallback mAnimationCallback; - @Mock - IRemoteMagnificationAnimationCallback mAnimationCallback2; - - private SurfaceControl.Transaction mTransaction; - @Mock - private SecureSettings mSecureSettings; - @Mock - private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; - - private long mWaitAnimationDuration; - private long mWaitBounceEffectDuration; - - private Handler mHandler; - private TestableWindowManager mWindowManager; - private SysUiState mSysUiState; - private Resources mResources; - private WindowMagnificationAnimationController mWindowMagnificationAnimationController; - private WindowMagnificationController mWindowMagnificationController; - private Instrumentation mInstrumentation; - private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0); - private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); - - private View mSpyView; - private View.OnTouchListener mTouchListener; - - private MotionEventHelper mMotionEventHelper = new MotionEventHelper(); - - // This list contains all SurfaceControlViewHosts created during a given test. If the - // magnification window is recreated during a test, the list will contain more than a single - // element. - private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>(); - // The most recently created SurfaceControlViewHost. - private SurfaceControlViewHost mSurfaceControlViewHost; - private KosmosJavaAdapter mKosmos; - private FakeSharedPreferences mSharedPreferences; - - /** - * return whether window magnification is supported for current test context. - */ - private boolean isWindowModeSupported() { - return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION); - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mContext = spy(mContext); - mKosmos = new KosmosJavaAdapter(this); - mContext = Mockito.spy(getContext()); - mHandler = new FakeHandler(TestableLooper.get(this).getLooper()); - mInstrumentation = InstrumentationRegistry.getInstrumentation(); - final WindowManager wm = mContext.getSystemService(WindowManager.class); - mWindowManager = spy(new TestableWindowManager(wm)); - - mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); - mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin()); - mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class)); - when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then( - returnsSecondArg()); - when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then( - returnsSecondArg()); - - mResources = getContext().getOrCreateTestableResources().getResources(); - // prevent the config orientation from undefined, which may cause config.diff method - // neglecting the orientation update. - if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) { - mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT; - } - - // Using the animation duration in WindowMagnificationAnimationController for testing. - mWaitAnimationDuration = mResources.getInteger( - com.android.internal.R.integer.config_longAnimTime); - // Using the bounce effect duration in WindowMagnificationController for testing. - mWaitBounceEffectDuration = mResources.getInteger( - com.android.internal.R.integer.config_shortAnimTime); - - mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( - mContext, mValueAnimator); - Supplier<SurfaceControlViewHost> scvhSupplier = () -> { - mSurfaceControlViewHost = spy(new SurfaceControlViewHost( - mContext, mContext.getDisplay(), new InputTransferToken(), - "WindowMagnification")); - ViewRootImpl viewRoot = mock(ViewRootImpl.class); - when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot); - mSurfaceControlViewHosts.add(mSurfaceControlViewHost); - return mSurfaceControlViewHost; - }; - mTransaction = spy(new SurfaceControl.Transaction()); - mSharedPreferences = new FakeSharedPreferences(); - when(mContext.getSharedPreferences( - eq("window_magnification_preferences"), anyInt())) - .thenReturn(mSharedPreferences); - mWindowMagnificationController = - new WindowMagnificationController( - mContext, - mHandler, - mWindowMagnificationAnimationController, - mMirrorWindowControl, - mTransaction, - mWindowMagnifierCallback, - mSysUiState, - mSecureSettings, - scvhSupplier, - /* sfVsyncFrameProvider= */ null, - /* globalWindowSessionSupplier= */ null, - mViewCaptureAwareWindowManager); - - verify(mMirrorWindowControl).setWindowDelegate( - any(MirrorWindowControl.MirrorWindowDelegate.class)); - mSpyView = Mockito.spy(new View(mContext)); - doAnswer((invocation) -> { - mTouchListener = invocation.getArgument(0); - return null; - }).when(mSpyView).setOnTouchListener( - any(View.OnTouchListener.class)); - - // skip test if window magnification is not supported to prevent fail results. (b/279820875) - Assume.assumeTrue(isWindowModeSupported()); - } - - @After - public void tearDown() { - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.deleteWindowMagnification(); - }); - mValueAnimator.cancel(); - } - - @Test - public void initWindowMagnificationController_checkAllowDiagonalScrollingWithSecureSettings() { - verify(mSecureSettings).getIntForUser( - eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING), - /* def */ eq(1), /* userHandle= */ anyInt()); - assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue(); - } - - @Test - public void enableWindowMagnification_showControlAndNotifyBoundsChanged() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - verify(mMirrorWindowControl).showControl(); - verify(mWindowMagnifierCallback, - timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeastOnce()).onWindowMagnifierBoundsChanged( - eq(mContext.getDisplayId()), any(Rect.class)); - } - - @Test - public void enableWindowMagnification_notifySourceBoundsChanged() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, - Float.NaN, /* magnificationFrameOffsetRatioX= */ 0, - /* magnificationFrameOffsetRatioY= */ 0, null)); - - // Waits for the surface created - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged( - (eq(mContext.getDisplayId())), any()); - } - - @Test - public void enableWindowMagnification_disabled_notifySourceBoundsChanged() { - enableWindowMagnification_notifySourceBoundsChanged(); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.deleteWindowMagnification(null)); - Mockito.reset(mWindowMagnifierCallback); - - enableWindowMagnification_notifySourceBoundsChanged(); - } - - @Test - public void enableWindowMagnification_withAnimation_schedulesFrame() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(2.0f, 10, - 10, /* magnificationFrameOffsetRatioX= */ 0, - /* magnificationFrameOffsetRatioY= */ 0, - Mockito.mock(IRemoteMagnificationAnimationCallback.class)); - }); - advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS); - - verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(), - eq(Surface.ROTATION_0)); - } - - @Test - public void moveWindowMagnifier_enabled_notifySourceBoundsChanged() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, - Float.NaN, 0, 0, null); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.moveWindowMagnifier(10, 10); - }); - - final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); - verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged( - (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertThat(mWindowMagnificationController.getCenterX()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); - assertThat(mWindowMagnificationController.getCenterY()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); - } - - @Test - public void enableWindowMagnification_systemGestureExclusionRectsIsSet() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - // Wait for Rects updated. - waitForIdleSync(); - - List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects(); - assertThat(rects).isNotEmpty(); - } - - @Ignore("The default window size should be constrained after fixing b/288056772") - @Test - public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() { - final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10; - mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize)); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - final int halfScreenSize = screenSize / 2; - ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - // The frame size should be the half of smaller value of window height/width unless it - //exceed the max frame size. - assertThat(params.width).isLessThan(halfScreenSize); - assertThat(params.height).isLessThan(halfScreenSize); - } - - @Test - public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, - Float.NaN)); - - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.deleteWindowMagnification()); - - verify(mMirrorWindowControl).destroyControl(); - verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController); - } - - @Test - public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() { - final WindowManager wm = mContext.getSystemService(WindowManager.class); - final Rect bounds = wm.getCurrentWindowMetrics().getBounds(); - setSystemGestureInsets(); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - bounds.bottom); - }); - ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.deleteWindowMagnification(); - }); - - verify(mMirrorWindowControl).destroyControl(); - assertThat(hasMagnificationOverlapFlag()).isFalse(); - } - - @Test - public void deleteWindowMagnification_notifySourceBoundsChanged() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, - Float.NaN)); - - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.deleteWindowMagnification()); - - // The first time is for notifying magnification enabled and the second time is for - // notifying magnification disabled. - verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged( - (eq(mContext.getDisplayId())), any()); - } - - @Test - public void moveMagnifier_schedulesFrame() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - waitForIdleSync(); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f)); - - verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(), - eq(Surface.ROTATION_0)); - } - - @Test - public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback() - throws RemoteException { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, - Float.NaN, 0, 0, null); - }); - - final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) - .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10; - final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10; - - reset(mWindowMagnifierCallback); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.moveWindowMagnifierToPosition( - targetCenterX, targetCenterY, mAnimationCallback); - }); - advanceTimeBy(mWaitAnimationDuration); - - verify(mAnimationCallback, times(1)).onResult(eq(true)); - verify(mAnimationCallback, never()).onResult(eq(false)); - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) - .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertThat(mWindowMagnificationController.getCenterX()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); - assertThat(mWindowMagnificationController.getCenterY()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); - assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX); - assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY); - } - - @Test - public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback() - throws RemoteException { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, - Float.NaN, 0, 0, null); - }); - - final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) - .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - final float centerX = sourceBoundsCaptor.getValue().exactCenterX(); - final float centerY = sourceBoundsCaptor.getValue().exactCenterY(); - - reset(mWindowMagnifierCallback); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 10, centerY + 10, mAnimationCallback); - mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 20, centerY + 20, mAnimationCallback); - mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 30, centerY + 30, mAnimationCallback); - mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 40, centerY + 40, mAnimationCallback2); - }); - advanceTimeBy(mWaitAnimationDuration); - - // only the last one callback will return true - verify(mAnimationCallback2).onResult(eq(true)); - // the others will return false - verify(mAnimationCallback, times(3)).onResult(eq(false)); - verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) - .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); - assertThat(mWindowMagnificationController.getCenterX()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX()); - assertThat(mWindowMagnificationController.getCenterY()) - .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY()); - assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40); - assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40); - } - - @Test - public void setScale_enabled_expectedValueAndUpdateStateDescription() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f, - Float.NaN, Float.NaN)); - - mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f)); - - assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f); - final View mirrorView = mSurfaceControlViewHost.getView(); - assertThat(mirrorView).isNotNull(); - assertThat(mirrorView.getStateDescription().toString()).contains("300"); - } - - @Test - public void onConfigurationChanged_disabled_withoutException() { - Display display = Mockito.spy(mContext.getDisplay()); - when(display.getRotation()).thenReturn(Surface.ROTATION_90); - when(mContext.getDisplay()).thenReturn(display); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION); - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE); - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - } - - @Test - public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() { - final int newRotation = simulateRotateTheDevice(); - final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); - final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY()); - final float displayWidth = windowBounds.width(); - final PointF magnifiedCenter = new PointF(center, center + 5f); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - magnifiedCenter.x, magnifiedCenter.y); - // Get the center again in case the center we set is out of screen. - magnifiedCenter.set(mWindowMagnificationController.getCenterX(), - mWindowMagnificationController.getCenterY()); - }); - // Rotate the window clockwise 90 degree. - windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom, - windowBounds.right); - mWindowManager.setWindowBounds(windowBounds); - - mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged( - ActivityInfo.CONFIG_ORIENTATION)); - - assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); - final PointF expectedCenter = new PointF(magnifiedCenter.y, - displayWidth - magnifiedCenter.x); - final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(), - mWindowMagnificationController.getCenterY()); - assertThat(actualCenter).isEqualTo(expectedCenter); - } - - @Test - public void onOrientationChanged_disabled_updateDisplayRotation() { - final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); - // Rotate the window clockwise 90 degree. - windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom, - windowBounds.right); - mWindowManager.setWindowBounds(windowBounds); - final int newRotation = simulateRotateTheDevice(); - - mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged( - ActivityInfo.CONFIG_ORIENTATION)); - - assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); - } - - @Test - public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() { - // The default position is at the center of the screen. - final float expectedRatio = 0.5f; - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - // Screen size and density change - mContext.getResources().getConfiguration().smallestScreenWidthDp = - mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; - final Rect testWindowBounds = new Rect( - mWindowManager.getCurrentWindowMetrics().getBounds()); - testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, - testWindowBounds.right + 100, testWindowBounds.bottom + 100); - mWindowManager.setWindowBounds(testWindowBounds); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - - // The ratio of center to window size should be the same. - assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width()) - .isEqualTo(expectedRatio); - assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height()) - .isEqualTo(expectedRatio); - } - - @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) - @Test - public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() { - int newSmallestScreenWidthDp = - mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; - int windowFrameSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize); - mSharedPreferences - .edit() - .putString(String.valueOf(newSmallestScreenWidthDp), - preferredWindowSize.toString()) - .commit(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - // Screen density and size change - mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp; - final Rect testWindowBounds = new Rect( - mWindowManager.getCurrentWindowMetrics().getBounds()); - testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, - testWindowBounds.right + 100, testWindowBounds.bottom + 100); - mWindowManager.setWindowBounds(testWindowBounds); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - - // wait for rect update - waitForIdleSync(); - ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // The width and height of the view include the magnification frame and the margins. - assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); - assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); - } - - @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) - @Test - public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() { - int newSmallestScreenWidthDp = - mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; - int windowFrameSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize); - mSharedPreferences - .edit() - .putString(String.valueOf(newSmallestScreenWidthDp), - WindowMagnificationFrameSpec.serialize( - WindowMagnificationSettings.MagnificationSize.CUSTOM, - preferredWindowSize)) - .commit(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - // Screen density and size change - mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp; - final Rect testWindowBounds = new Rect( - mWindowManager.getCurrentWindowMetrics().getBounds()); - testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, - testWindowBounds.right + 100, testWindowBounds.bottom + 100); - mWindowManager.setWindowBounds(testWindowBounds); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - - // wait for rect update - waitForIdleSync(); - verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored( - eq(mContext.getDisplayId()), - eq(WindowMagnificationSettings.MagnificationSize.CUSTOM)); - ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // The width and height of the view include the magnification frame and the margins. - assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); - assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin); - } - - @Test - public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10; - // Screen size and density change - mContext.getResources().getConfiguration().smallestScreenWidthDp = - mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; - mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize)); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); - }); - - final int defaultWindowSize = - mWindowMagnificationController.getMagnificationWindowSizeFromIndex( - WindowMagnificationSettings.MagnificationSize.MEDIUM); - ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); - - assertThat(params.width).isEqualTo(defaultWindowSize); - assertThat(params.height).isEqualTo(defaultWindowSize); - } - - @Test - public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - Mockito.reset(mWindowManager); - Mockito.reset(mMirrorWindowControl); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); - }); - - verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt()); - verify(mSurfaceControlViewHosts.get(0)).release(); - verify(mMirrorWindowControl).destroyControl(); - verify(mSurfaceControlViewHosts.get(1)).setView(any(), any()); - verify(mMirrorWindowControl).showControl(); - } - - @Test - public void onDensityChanged_disabled_updateDimensions() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); - }); - - verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt()); - } - - @Test - public void initializeA11yNode_enabled_expectedValues() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN, - Float.NaN); - }); - final View mirrorView = mSurfaceControlViewHost.getView(); - assertThat(mirrorView).isNotNull(); - final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); - - mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo); - - assertThat(nodeInfo.getContentDescription()).isNotNull(); - assertThat(nodeInfo.getStateDescription().toString()).contains("250"); - assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList( - new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), - mContext.getResources().getString( - R.string.magnification_open_settings_click_label)), - new AccessibilityAction(R.id.accessibility_action_zoom_in, - mContext.getString(R.string.accessibility_control_zoom_in)), - new AccessibilityAction(R.id.accessibility_action_zoom_out, - mContext.getString(R.string.accessibility_control_zoom_out)), - new AccessibilityAction(R.id.accessibility_action_move_right, - mContext.getString(R.string.accessibility_control_move_right)), - new AccessibilityAction(R.id.accessibility_action_move_left, - mContext.getString(R.string.accessibility_control_move_left)), - new AccessibilityAction(R.id.accessibility_action_move_down, - mContext.getString(R.string.accessibility_control_move_down)), - new AccessibilityAction(R.id.accessibility_action_move_up, - mContext.getString(R.string.accessibility_control_move_up)))); - } - - @Test - public void performA11yActions_visible_expectedResults() { - final int displayId = mContext.getDisplayId(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN, - Float.NaN); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null)) - .isTrue(); - // Minimum scale is 1.0. - verify(mWindowMagnifierCallback).onPerformScaleAction( - eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true)); - - assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null)) - .isTrue(); - verify(mWindowMagnifierCallback).onPerformScaleAction( - eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true)); - - // TODO: Verify the final state when the mirror surface is visible. - assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null)) - .isTrue(); - assertThat( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null)) - .isTrue(); - assertThat( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null)) - .isTrue(); - assertThat( - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null)) - .isTrue(); - verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId)); - - assertThat(mirrorView.performAccessibilityAction( - AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue(); - verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId)); - } - - @Test - public void performA11yActions_visible_notifyAccessibilityActionPerformed() { - final int displayId = mContext.getDisplayId(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN, - Float.NaN); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null); - - verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId)); - } - - @Test - public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - View closeButton = getInternalView(R.id.close_button); - View bottomRightCorner = getInternalView(R.id.bottom_right_corner); - View bottomLeftCorner = getInternalView(R.id.bottom_left_corner); - View topRightCorner = getInternalView(R.id.top_right_corner); - View topLeftCorner = getInternalView(R.id.top_left_corner); - - assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE); - - final View mirrorView = mSurfaceControlViewHost.getView(); - mInstrumentation.runOnMainSync(() -> - mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), - null)); - - assertThat(closeButton.getVisibility()).isEqualTo(View.GONE); - assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE); - assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE); - assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE); - assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE); - } - - @Test - - public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() { - final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - final int startingWidth = (int) (windowBounds.width() * 0.8); - final int startingHeight = (int) (windowBounds.height() * 0.8); - final float changeWindowSizeAmount = mContext.getResources().getFraction( - R.fraction.magnification_resize_window_size_amount, - /* base= */ 1, - /* pbase= */ 1); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingWidth, startingHeight); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mirrorView.performAccessibilityAction( - R.id.accessibility_action_increase_window_width, null); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // Window width includes the magnifier frame and the margin. Increasing the window size - // will be increasing the amount of the frame size only. - int newWindowWidth = - (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount)) - + 2 * mirrorSurfaceMargin; - assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth); - assertThat(actualWindowHeight.get()).isEqualTo(startingHeight); - } - - @Test - public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() { - final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - final int startingWidth = (int) (windowBounds.width() * 0.8); - final int startingHeight = (int) (windowBounds.height() * 0.8); - final float changeWindowSizeAmount = mContext.getResources().getFraction( - R.fraction.magnification_resize_window_size_amount, - /* base= */ 1, - /* pbase= */ 1); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingWidth, startingHeight); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mirrorView.performAccessibilityAction( - R.id.accessibility_action_increase_window_height, null); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // Window height includes the magnifier frame and the margin. Increasing the window size - // will be increasing the amount of the frame size only. - int newWindowHeight = - (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount)) - + 2 * mirrorSurfaceMargin; - assertThat(actualWindowWidth.get()).isEqualTo(startingWidth); - assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight); - } - - @Test - public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() { - final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - final int startingWidth = windowBounds.width(); - final int startingHeight = windowBounds.height(); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingWidth, startingHeight); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AccessibilityNodeInfo accessibilityNodeInfo = - mirrorView.createAccessibilityNodeInfo(); - assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( - new AccessibilityAction( - R.id.accessibility_action_increase_window_width, - mContext.getString( - R.string.accessibility_control_increase_window_width))); - } - - @Test - public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() { - final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - final int startingWidth = windowBounds.width(); - final int startingHeight = windowBounds.height(); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingWidth, startingHeight); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AccessibilityNodeInfo accessibilityNodeInfo = - mirrorView.createAccessibilityNodeInfo(); - assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( - new AccessibilityAction( - R.id.accessibility_action_increase_window_height, null)); - } - - @Test - public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() { - int mMinWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int startingSize = (int) (mMinWindowSize * 1.1); - final float changeWindowSizeAmount = mContext.getResources().getFraction( - R.fraction.magnification_resize_window_size_amount, - /* base= */ 1, - /* pbase= */ 1); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mirrorView.performAccessibilityAction( - R.id.accessibility_action_decrease_window_width, null); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // Window width includes the magnifier frame and the margin. Decreasing the window size - // will be decreasing the amount of the frame size only. - int newWindowWidth = - (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount)) - + 2 * mirrorSurfaceMargin; - assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth); - assertThat(actualWindowHeight.get()).isEqualTo(startingSize); - } - - @Test - public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() { - int mMinWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int startingSize = (int) (mMinWindowSize * 1.1); - final float changeWindowSizeAmount = mContext.getResources().getFraction( - R.fraction.magnification_resize_window_size_amount, - /* base= */ 1, - /* pbase= */ 1); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mirrorView.performAccessibilityAction( - R.id.accessibility_action_decrease_window_height, null); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - // Window height includes the magnifier frame and the margin. Decreasing the window size - // will be decreasing the amount of the frame size only. - int newWindowHeight = - (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount)) - + 2 * mirrorSurfaceMargin; - assertThat(actualWindowWidth.get()).isEqualTo(startingSize); - assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight); - } - - @Test - public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() { - int mMinWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int startingSize = mMinWindowSize; - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AccessibilityNodeInfo accessibilityNodeInfo = - mirrorView.createAccessibilityNodeInfo(); - assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( - new AccessibilityAction( - R.id.accessibility_action_decrease_window_width, null)); - } - - @Test - public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() { - int mMinWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int startingSize = mMinWindowSize; - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - final AccessibilityNodeInfo accessibilityNodeInfo = - mirrorView.createAccessibilityNodeInfo(); - assertThat(accessibilityNodeInfo.getActionList()).doesNotContain( - new AccessibilityAction( - R.id.accessibility_action_decrease_window_height, null)); - } - - @Test - public void enableWindowMagnification_hasA11yWindowTitle() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString( - com.android.internal.R.string.android_system_label)); - } - - @Test - public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN, - Float.NaN); - }); - - assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN); - } - - @Test - public void enableWindowMagnification_rotationIsChanged_updateRotationValue() { - // the config orientation should not be undefined, since it would cause config.diff - // returning 0 and thus the orientation changed would not be detected - assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED); - - final Configuration config = mResources.getConfiguration(); - config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT - : ORIENTATION_LANDSCAPE; - final int newRotation = simulateRotateTheDevice(); - - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation); - } - - @Test - public void enableWindowMagnification_registerComponentCallback() { - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, - Float.NaN)); - - verify(mContext).registerComponentCallbacks(mWindowMagnificationController); - } - - @Test - public void onLocaleChanged_enabled_updateA11yWindowTitle() { - final String newA11yWindowTitle = "new a11y window title"; - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - final TestableResources testableResources = getContext().getOrCreateTestableResources(); - testableResources.addOverride(com.android.internal.R.string.android_system_label, - newA11yWindowTitle); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE); - }); - - assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle); - } - - @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925") - @Test - public void onSingleTap_enabled_scaleAnimates() { - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.onSingleTap(mSpyView); - }); - - final View mirrorView = mSurfaceControlViewHost.getView(); - - final AtomicDouble maxScaleX = new AtomicDouble(); - advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> { - // For some reason the fancy way doesn't compile... - // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max); - final double oldMax = maxScaleX.get(); - final double newMax = Math.max(mirrorView.getScaleX(), oldMax); - assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue(); - }); - - assertThat(maxScaleX.get()).isGreaterThan(1.0); - } - - @Test - public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - setSystemGestureInsets(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, - Float.NaN); - }); - - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.moveWindowMagnifier(0, bounds.height()); - }); - - ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag()); - } - - @Test - public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion() - throws RemoteException { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - setSystemGestureInsets(); - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN); - }); - // Wait for Region updated. - waitForIdleSync(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0); - }); - // Wait for Region updated. - waitForIdleSync(); - - AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl(); - // Verifying two times in: (1) enable window magnification (2) reposition drag handle - verify(viewRoot, times(2)).setTouchableRegion(any()); - - View dragButton = getInternalView(R.id.drag_handle); - FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams(); - assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT); - } - - @Test - public void moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion() - throws RemoteException { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - setSystemGestureInsets(); - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN); - }); - // Wait for Region updated. - waitForIdleSync(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0); - }); - // Wait for Region updated. - waitForIdleSync(); - - AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl(); - // Verifying one times in: (1) enable window magnification - verify(viewRoot).setTouchableRegion(any()); - - View dragButton = getInternalView(R.id.drag_handle); - FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams(); - assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT); - } - - @Test - public void setMinimumWindowSize_enabled_expectedWindowSize() { - final int minimumWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int expectedWindowHeight = minimumWindowSize; - final int expectedWindowWidth = minimumWindowSize; - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight); - actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); - - }); - - assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); - assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); - } - - @Test - public void setMinimumWindowSizeThenEnable_expectedWindowSize() { - final int minimumWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final int expectedWindowHeight = minimumWindowSize; - final int expectedWindowWidth = minimumWindowSize; - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight); - mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN); - actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); - assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); - } - - @Test - public void setWindowSizeLessThanMin_enabled_minimumWindowSize() { - final int minimumWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSize(minimumWindowSize - 10, - minimumWindowSize - 10); - actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize); - assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize); - } - - @Test - public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10); - actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(bounds.height()); - assertThat(actualWindowWidth.get()).isEqualTo(bounds.width()); - } - - @Test - public void changeMagnificationSize_expectedWindowSize() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - - final float magnificationScaleLarge = 2.5f; - final int initSize = Math.min(bounds.width(), bounds.height()) / 3; - final int magnificationSize = (int) (initSize * magnificationScaleLarge) - - (int) (initSize * magnificationScaleLarge) % 2; - - final int expectedWindowHeight = magnificationSize; - final int expectedWindowWidth = magnificationSize; - - mInstrumentation.runOnMainSync( - () -> - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.changeMagnificationSize( - WindowMagnificationSettings.MagnificationSize.LARGE); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight); - assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth); - } - - @Test - public void editModeOnDragCorner_resizesWindow() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - - final int startingSize = (int) (bounds.width() / 2); - - mInstrumentation.runOnMainSync( - () -> - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - }); - - waitForIdleSync(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController - .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - - assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1); - assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2); - } - - @Test - public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - - final int startingSize = (int) (bounds.width() / 2f); - - mInstrumentation.runOnMainSync( - () -> - mWindowMagnificationController.updateWindowMagnificationInternal( - Float.NaN, Float.NaN, Float.NaN)); - - final AtomicInteger actualWindowHeight = new AtomicInteger(); - final AtomicInteger actualWindowWidth = new AtomicInteger(); - - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.setWindowSize(startingSize, startingSize); - mWindowMagnificationController.setEditMagnifierSizeMode(true); - mWindowMagnificationController - .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f); - actualWindowHeight.set( - mSurfaceControlViewHost.getView().getLayoutParams().height); - actualWindowWidth.set( - mSurfaceControlViewHost.getView().getLayoutParams().width); - }); - assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1); - assertThat(actualWindowWidth.get()).isEqualTo(startingSize); - } - - @Test - public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() { - - final int minimumWindowSize = mResources.getDimensionPixelSize( - com.android.internal.R.dimen.accessibility_window_magnifier_min_size); - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, - Float.NaN, Float.NaN)); - - final AtomicInteger magnificationCenterX = new AtomicInteger(); - final AtomicInteger magnificationCenterY = new AtomicInteger(); - mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize, - minimumWindowSize, bounds.right, bounds.bottom); - magnificationCenterX.set((int) mWindowMagnificationController.getCenterX()); - magnificationCenterY.set((int) mWindowMagnificationController.getCenterY()); - }); - - assertThat(magnificationCenterX.get()).isLessThan(bounds.right); - assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom); - } - - @Test - public void performSingleTap_DragHandle() { - final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); - mInstrumentation.runOnMainSync( - () -> { - mWindowMagnificationController.updateWindowMagnificationInternal( - 1.5f, bounds.centerX(), bounds.centerY()); - }); - View dragButton = getInternalView(R.id.drag_handle); - - // Perform a single-tap - final long downTime = SystemClock.uptimeMillis(); - dragButton.dispatchTouchEvent( - obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100)); - dragButton.dispatchTouchEvent( - obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100)); - - verify(mSurfaceControlViewHost).setView(any(View.class), any()); - } - - private <T extends View> T getInternalView(@IdRes int idRes) { - View mirrorView = mSurfaceControlViewHost.getView(); - T view = mirrorView.findViewById(idRes); - assertThat(view).isNotNull(); - return view; - } - - private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x, - float y) { - return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y); - } - - private String getAccessibilityWindowTitle() { - final View mirrorView = mSurfaceControlViewHost.getView(); - if (mirrorView == null) { - return null; - } - WindowManager.LayoutParams layoutParams = - (WindowManager.LayoutParams) mirrorView.getLayoutParams(); - return layoutParams.accessibilityTitle.toString(); - } - - private boolean hasMagnificationOverlapFlag() { - return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0; - } - - private void setSystemGestureInsets() { - final WindowInsets testInsets = new WindowInsets.Builder() - .setInsets(systemGestures(), Insets.of(0, 0, 0, 10)) - .build(); - mWindowManager.setWindowInsets(testInsets); - } - - private int updateMirrorSurfaceMarginDimension() { - return mContext.getResources().getDimensionPixelSize( - R.dimen.magnification_mirror_surface_margin); - } - - @Surface.Rotation - private int simulateRotateTheDevice() { - final Display display = Mockito.spy(mContext.getDisplay()); - final int currentRotation = display.getRotation(); - final int newRotation = (currentRotation + 1) % 4; - when(display.getRotation()).thenReturn(newRotation); - when(mContext.getDisplay()).thenReturn(display); - return newRotation; - } - - // advance time based on the device frame refresh rate - private void advanceTimeBy(long timeDelta) { - advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null); - } - - // advance time based on the device frame refresh rate, and trigger runnable on each refresh - private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) { - final float frameRate = mContext.getDisplay().getRefreshRate(); - final int timeSlot = (int) (1000 / frameRate); - int round = (int) Math.ceil((double) timeDelta / timeSlot); - for (; round >= 0; round--) { - mInstrumentation.runOnMainSync(() -> { - mAnimatorTestRule.advanceTimeBy(timeSlot); - if (runnableOnEachRefresh != null) { - runnableOnEachRefresh.run(); - } - }); - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt index 288ed4dc9d4f..a1f59c2cc2b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt @@ -20,18 +20,20 @@ import android.animation.AnimatorRuleRecordingSpec import android.animation.AnimatorTestRuleToolkit import android.animation.MotionControl import android.animation.recordMotion +import android.graphics.Color +import android.graphics.PointF import android.graphics.drawable.GradientDrawable import android.platform.test.annotations.MotionTest import android.view.ViewGroup import android.widget.FrameLayout import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.systemui.SysuiTestCase import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.runOnMainThreadAndWaitForIdleSync import kotlin.test.assertTrue import org.junit.Rule import org.junit.Test @@ -47,13 +49,25 @@ import platform.test.screenshot.PathConfig @SmallTest @MotionTest @RunWith(ParameterizedAndroidJunit4::class) -class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { +class TransitionAnimatorTest( + private val fadeWindowBackgroundLayer: Boolean, + private val isLaunching: Boolean, + private val useSpring: Boolean, +) : SysuiTestCase() { companion object { private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens" - @get:Parameters(name = "{0}") + @get:Parameters(name = "fadeBackground={0}, isLaunching={1}, useSpring={2}") @JvmStatic - val useSpringValues = booleanArrayOf(false, true).toList() + val parameterValues = buildList { + booleanArrayOf(true, false).forEach { fadeBackground -> + booleanArrayOf(true, false).forEach { isLaunching -> + booleanArrayOf(true, false).forEach { useSpring -> + add(arrayOf(fadeBackground, isLaunching, useSpring)) + } + } + } + } } private val kosmos = Kosmos() @@ -66,11 +80,23 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { ActivityTransitionAnimator.SPRING_TIMINGS, ActivityTransitionAnimator.SPRING_INTERPOLATORS, ) - private val withSpring = + private val fade = + if (fadeWindowBackgroundLayer) { + "withFade" + } else { + "withoutFade" + } + private val direction = + if (isLaunching) { + "whenLaunching" + } else { + "whenReturning" + } + private val mode = if (useSpring) { - "_withSpring" + "withSpring" } else { - "" + "withAnimator" } @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java) @@ -83,113 +109,75 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { ) @Test - fun backgroundAnimation_whenLaunching() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = true).apply { - getInstrumentation().runOnMainSync { start() } - } + fun backgroundAnimationTimeSeries() { + val transitionContainer = createScene() + val backgroundLayer = createBackgroundLayer() + val animation = createAnimation(transitionContainer, backgroundLayer) - val recordedMotion = recordMotion(backgroundLayer, animator) + val recordedMotion = record(backgroundLayer, animation) motionRule .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimation_whenLaunching$withSpring") + .timeSeriesMatchesGolden("backgroundAnimationTimeSeries_${fade}_${direction}_$mode") } - @Test - fun backgroundAnimation_whenReturning() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = false).apply { - getInstrumentation().runOnMainSync { start() } - } - - val recordedMotion = recordMotion(backgroundLayer, animator) - - motionRule - .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimation_whenReturning$withSpring") - } - - @Test - fun backgroundAnimationWithoutFade_whenLaunching() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = true, fadeWindowBackgroundLayer = false) - .apply { getInstrumentation().runOnMainSync { start() } } - - val recordedMotion = recordMotion(backgroundLayer, animator) - - motionRule - .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenLaunching$withSpring") - } - - @Test - fun backgroundAnimationWithoutFade_whenReturning() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = false, fadeWindowBackgroundLayer = false) - .apply { getInstrumentation().runOnMainSync { start() } } - - val recordedMotion = recordMotion(backgroundLayer, animator) - - motionRule - .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenReturning$withSpring") - } - - private fun setUpTest( - backgroundLayer: GradientDrawable, - isLaunching: Boolean, - fadeWindowBackgroundLayer: Boolean = true, - ): TransitionAnimator.Animation { + private fun createScene(): ViewGroup { lateinit var transitionContainer: ViewGroup activityRule.scenario.onActivity { activity -> - transitionContainer = FrameLayout(activity).apply { setBackgroundColor(0x00FF00) } + transitionContainer = FrameLayout(activity) activity.setContentView(transitionContainer) } waitForIdleSync() + return transitionContainer + } + private fun createBackgroundLayer() = + GradientDrawable().apply { + setColor(Color.BLACK) + alpha = 0 + } + + private fun createAnimation( + transitionContainer: ViewGroup, + backgroundLayer: GradientDrawable, + ): TransitionAnimator.Animation { val controller = TestController(transitionContainer, isLaunching) - return transitionAnimator.createAnimation( - controller, - controller.createAnimatorState(), - createEndState(transitionContainer), - backgroundLayer, - fadeWindowBackgroundLayer, - useSpring = useSpring, - ) - } - private fun createEndState(container: ViewGroup): TransitionAnimator.State { val containerLocation = IntArray(2) - container.getLocationOnScreen(containerLocation) - return TransitionAnimator.State( - left = containerLocation[0], - top = containerLocation[1], - right = containerLocation[0] + 320, - bottom = containerLocation[1] + 690, - topCornerRadius = 0f, - bottomCornerRadius = 0f, - ) + transitionContainer.getLocationOnScreen(containerLocation) + val endState = + TransitionAnimator.State( + left = containerLocation[0], + top = containerLocation[1], + right = containerLocation[0] + 320, + bottom = containerLocation[1] + 690, + topCornerRadius = 0f, + bottomCornerRadius = 0f, + ) + + val startVelocity = + if (useSpring) { + PointF(2500f, 30000f) + } else { + null + } + + return transitionAnimator + .createAnimation( + controller, + controller.createAnimatorState(), + endState, + backgroundLayer, + fadeWindowBackgroundLayer, + startVelocity = startVelocity, + ) + .apply { runOnMainThreadAndWaitForIdleSync { start() } } } - private fun recordMotion( + private fun record( backgroundLayer: GradientDrawable, animation: TransitionAnimator.Animation, ): RecordedMotion { - fun record(motionControl: MotionControl, sampleIntervalMs: Long): RecordedMotion { - return motionRule.recordMotion( - AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) { - feature(DrawableFeatureCaptures.bounds, "bounds") - feature(DrawableFeatureCaptures.cornerRadii, "corner_radii") - feature(DrawableFeatureCaptures.alpha, "alpha") - } - ) - } - val motionControl: MotionControl val sampleIntervalMs: Long if (useSpring) { @@ -204,9 +192,13 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { sampleIntervalMs = 20L } - var recording: RecordedMotion? = null - getInstrumentation().runOnMainSync { recording = record(motionControl, sampleIntervalMs) } - return recording!! + return motionRule.recordMotion( + AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) { + feature(DrawableFeatureCaptures.bounds, "bounds") + feature(DrawableFeatureCaptures.cornerRadii, "corner_radii") + feature(DrawableFeatureCaptures.alpha, "alpha") + } + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index 6061063db903..562481567536 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -20,6 +20,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS; import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE; +import static com.android.systemui.Flags.FLAG_SHOW_CLIPBOARD_INDICATION; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED; @@ -121,6 +122,24 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + private class FakeClipboardIndicationProvider implements ClipboardIndicationProvider { + private ClipboardIndicationCallback mIndicationCallback; + + public void notifyIndicationTextChanged(CharSequence indicationText) { + if (mIndicationCallback != null) { + mIndicationCallback.onIndicationTextChanged(indicationText); + } + } + + @Override + public void getIndicationText(ClipboardIndicationCallback callback) { + mIndicationCallback = callback; + } + } + + private FakeClipboardIndicationProvider mClipboardIndicationProvider = + new FakeClipboardIndicationProvider(); + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -156,6 +175,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mExecutor, mClipboardImageLoader, mClipboardTransitionExecutor, + mClipboardIndicationProvider, mUiEventLogger); verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); mCallbacks = mOverlayCallbacksCaptor.getValue(); @@ -305,6 +325,17 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_SHOW_CLIPBOARD_INDICATION) + public void test_onIndicationTextChanged_setIndicationTextCorrectly() { + initController(); + mOverlayController.setClipData(mSampleClipData, ""); + + mClipboardIndicationProvider.notifyIndicationTextChanged("copied"); + + verify(mClipboardOverlayView).setIndicationText("copied"); + } + + @Test @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS) public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() { initController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt index b3bd7d15cbf3..c7beb158c2de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt @@ -23,7 +23,6 @@ import android.view.ViewGroup import android.view.accessibility.AccessibilityManager import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.systemui.dump.DumpManager -import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryViewUiEventLogger @@ -43,7 +42,6 @@ class FakeMediaTttChipControllerReceiver( dumpManager: DumpManager, powerManager: PowerManager, mainHandler: Handler, - mediaTttFlags: MediaTttFlags, uiEventLogger: MediaTttReceiverUiEventLogger, viewUtil: ViewUtil, wakeLockBuilder: WakeLock.Builder, @@ -62,7 +60,6 @@ class FakeMediaTttChipControllerReceiver( dumpManager, powerManager, mainHandler, - mediaTttFlags, uiEventLogger, viewUtil, wakeLockBuilder, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 9afa5ad1dd43..378dd452d030 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -37,7 +37,6 @@ import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager -import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.res.R import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController @@ -55,7 +54,6 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.never -import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -66,32 +64,18 @@ import org.mockito.MockitoAnnotations class MediaTttChipControllerReceiverTest : SysuiTestCase() { private lateinit var controllerReceiver: MediaTttChipControllerReceiver - @Mock - private lateinit var packageManager: PackageManager - @Mock - private lateinit var applicationInfo: ApplicationInfo - @Mock - private lateinit var logger: MediaTttReceiverLogger - @Mock - private lateinit var accessibilityManager: AccessibilityManager - @Mock - private lateinit var configurationController: ConfigurationController - @Mock - private lateinit var dumpManager: DumpManager - @Mock - private lateinit var mediaTttFlags: MediaTttFlags - @Mock - private lateinit var powerManager: PowerManager - @Mock - private lateinit var viewUtil: ViewUtil - @Mock - private lateinit var windowManager: WindowManager - @Mock - private lateinit var commandQueue: CommandQueue - @Mock - private lateinit var rippleController: MediaTttReceiverRippleController - @Mock - private lateinit var lazyViewCapture: Lazy<ViewCapture> + @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var applicationInfo: ApplicationInfo + @Mock private lateinit var logger: MediaTttReceiverLogger + @Mock private lateinit var accessibilityManager: AccessibilityManager + @Mock private lateinit var configurationController: ConfigurationController + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var powerManager: PowerManager + @Mock private lateinit var viewUtil: ViewUtil + @Mock private lateinit var windowManager: WindowManager + @Mock private lateinit var commandQueue: CommandQueue + @Mock private lateinit var rippleController: MediaTttReceiverRippleController + @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture> private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager private lateinit var commandQueueCallback: CommandQueue.Callbacks private lateinit var fakeAppIconDrawable: Drawable @@ -106,14 +90,17 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true) fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable) whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME) - whenever(packageManager.getApplicationInfo( - eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>() - )).thenReturn(applicationInfo) + whenever( + packageManager.getApplicationInfo( + eq(PACKAGE_NAME), + any<PackageManager.ApplicationInfoFlags>(), + ) + ) + .thenReturn(applicationInfo) context.setMockPackageManager(packageManager) fakeClock = FakeSystemClock() @@ -127,27 +114,31 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { fakeWakeLockBuilder = WakeLockFake.Builder(context) fakeWakeLockBuilder.setWakeLock(fakeWakeLock) - viewCaptureAwareWindowManager = ViewCaptureAwareWindowManager(windowManager, - lazyViewCapture, isViewCaptureEnabled = false) - controllerReceiver = FakeMediaTttChipControllerReceiver( - commandQueue, - context, - logger, - viewCaptureAwareWindowManager, - fakeExecutor, - accessibilityManager, - configurationController, - dumpManager, - powerManager, - Handler.getMain(), - mediaTttFlags, - receiverUiEventLogger, - viewUtil, - fakeWakeLockBuilder, - fakeClock, - rippleController, - temporaryViewUiEventLogger, - ) + viewCaptureAwareWindowManager = + ViewCaptureAwareWindowManager( + windowManager, + lazyViewCapture, + isViewCaptureEnabled = false, + ) + controllerReceiver = + FakeMediaTttChipControllerReceiver( + commandQueue, + context, + logger, + viewCaptureAwareWindowManager, + fakeExecutor, + accessibilityManager, + configurationController, + dumpManager, + powerManager, + Handler.getMain(), + receiverUiEventLogger, + viewUtil, + fakeWakeLockBuilder, + fakeClock, + rippleController, + temporaryViewUiEventLogger, + ) controllerReceiver.start() val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) @@ -156,48 +147,18 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { } @Test - fun commandQueueCallback_flagOff_noCallbackAdded() { - reset(commandQueue) - whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false) - - controllerReceiver = MediaTttChipControllerReceiver( - commandQueue, - context, - logger, - viewCaptureAwareWindowManager, - FakeExecutor(FakeSystemClock()), - accessibilityManager, - configurationController, - dumpManager, - powerManager, - Handler.getMain(), - mediaTttFlags, - receiverUiEventLogger, - viewUtil, - fakeWakeLockBuilder, - fakeClock, - rippleController, - temporaryViewUiEventLogger, - ) - controllerReceiver.start() - - verify(commandQueue, never()).addCallback(any()) - } - - @Test fun commandQueueCallback_closeToSender_triggersChip() { val appName = "FakeAppName" commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, routeInfo, /* appIcon= */ null, - appName + appName, ) assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_CLOSE_TO_SENDER.id - ) + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_CLOSE_TO_SENDER.id) assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() } @@ -207,45 +168,44 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, routeInfo, null, - null + null, ) verify(windowManager, never()).addView(any(), any()) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_FAR_FROM_SENDER.id - ) + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_FAR_FROM_SENDER.id) assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() } @Test fun commandQueueCallback_transferToReceiverSucceeded_noChipShown() { commandQueueCallback.updateMediaTapToTransferReceiverDisplay( - StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, - routeInfo, - null, - null + StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null, + null, ) verify(windowManager, never()).addView(any(), any()) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo( MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED.id - ) + ) assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() } @Test fun commandQueueCallback_transferToReceiverFailed_noChipShown() { commandQueueCallback.updateMediaTapToTransferReceiverDisplay( - StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED, - routeInfo, - null, - null + StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED, + routeInfo, + null, + null, ) verify(windowManager, never()).addView(any(), any()) - assertThat(uiEventLoggerFake.eventId(0)).isEqualTo( - MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED.id - ) + assertThat(uiEventLoggerFake.eventId(0)) + .isEqualTo(MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED.id) assertThat(uiEventLoggerFake.logs[0].instanceId).isNotNull() } @@ -255,14 +215,14 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, routeInfo, null, - null + null, ) commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, routeInfo, null, - null + null, ) val viewCaptor = ArgumentCaptor.forClass(View::class.java) @@ -276,14 +236,14 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, routeInfo, null, - null + null, ) commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, null, - null + null, ) val viewCaptor = ArgumentCaptor.forClass(View::class.java) @@ -297,14 +257,14 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, routeInfo, null, - null + null, ) commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, null, - null + null, ) assertThat(uiEventLoggerFake[0].instanceId).isEqualTo(uiEventLoggerFake[1].instanceId) @@ -316,14 +276,14 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, routeInfo, null, - null + null, ) commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED, routeInfo, null, - null + null, ) val viewCaptor = ArgumentCaptor.forClass(View::class.java) @@ -334,19 +294,19 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Test fun commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleased() { commandQueueCallback.updateMediaTapToTransferReceiverDisplay( - StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, - routeInfo, - null, - null + StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, + routeInfo, + null, + null, ) assertThat(fakeWakeLock.isHeld).isTrue() commandQueueCallback.updateMediaTapToTransferReceiverDisplay( - StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, - routeInfo, - null, - null + StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, + routeInfo, + null, + null, ) assertThat(fakeWakeLock.isHeld).isFalse() @@ -355,10 +315,10 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Test fun commandQueueCallback_closeThenFar_wakeLockNeverAcquired() { commandQueueCallback.updateMediaTapToTransferReceiverDisplay( - StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, - routeInfo, - null, - null + StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, + routeInfo, + null, + null, ) assertThat(fakeWakeLock.isHeld).isFalse() @@ -370,7 +330,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, routeInfo, null, - null + null, ) verify(logger).logStateChange(any(), any(), any()) @@ -391,10 +351,12 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { val view = getChipView() assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) assertThat(view.getAppIconView().contentDescription) - .isEqualTo(context.getString( - R.string.media_transfer_receiver_content_description_with_app_name, - APP_NAME, - )) + .isEqualTo( + context.getString( + R.string.media_transfer_receiver_content_description_with_app_name, + APP_NAME, + ) + ) } @Test @@ -463,7 +425,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, null, - APP_NAME + APP_NAME, ) verify(windowManager, never()).addView(any(), any()) @@ -476,10 +438,11 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { } private fun getChipReceiverInfo(packageName: String?): ChipReceiverInfo { - val routeInfo = MediaRoute2Info.Builder("id", "Test route name") - .addFeature("feature") - .setClientPackageName(packageName) - .build() + val routeInfo = + MediaRoute2Info.Builder("id", "Test route name") + .addFeature("feature") + .setClientPackageName(packageName) + .build() return ChipReceiverInfo( routeInfo, null, @@ -495,7 +458,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { private const val APP_NAME = "Fake app name" private const val PACKAGE_NAME = "com.android.systemui" -private val routeInfo = MediaRoute2Info.Builder("id", "Test route name") - .addFeature("feature") - .setClientPackageName(PACKAGE_NAME) - .build() +private val routeInfo = + MediaRoute2Info.Builder("id", "Test route name") + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index b4cad6bb057b..c90ac5993c31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -41,7 +41,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.dump.DumpManager -import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.CommandQueue @@ -95,7 +94,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var chipbarLogger: ChipbarLogger @Mock private lateinit var logger: MediaTttSenderLogger - @Mock private lateinit var mediaTttFlags: MediaTttFlags @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var viewUtil: ViewUtil @@ -118,7 +116,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true) whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT) fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! @@ -127,7 +124,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { whenever( packageManager.getApplicationInfo( eq(PACKAGE_NAME), - any<PackageManager.ApplicationInfoFlags>() + any<PackageManager.ApplicationInfoFlags>(), ) ) .thenReturn(applicationInfo) @@ -148,8 +145,11 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { ChipbarCoordinator( context, chipbarLogger, - ViewCaptureAwareWindowManager(windowManager, lazyViewCapture, - isViewCaptureEnabled = false), + ViewCaptureAwareWindowManager( + windowManager, + lazyViewCapture, + isViewCaptureEnabled = false, + ), fakeExecutor, accessibilityManager, configurationController, @@ -174,7 +174,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { context, dumpManager, logger, - mediaTttFlags, uiEventLogger, ) underTest.start() @@ -183,30 +182,11 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { } @Test - fun commandQueueCallback_flagOff_noCallbackAdded() { - reset(commandQueue) - whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false) - underTest = - MediaTttSenderCoordinator( - chipbarCoordinator, - commandQueue, - context, - dumpManager, - logger, - mediaTttFlags, - uiEventLogger, - ) - underTest.start() - - verify(commandQueue, never()).addCallback(any()) - } - - @Test fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, routeInfo, - null + null, ) val chipbarView = getChipbarView() @@ -220,13 +200,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id) verify(vibratorHelper) - .vibrate( - any(), - any(), - any<VibrationEffect>(), - any(), - any<VibrationAttributes>(), - ) + .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>()) } @Test @@ -249,7 +223,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, routeInfo, - null + null, ) val chipbarView = getChipbarView() @@ -263,13 +237,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id) verify(vibratorHelper) - .vibrate( - any(), - any(), - any<VibrationEffect>(), - any(), - any<VibrationAttributes>(), - ) + .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>()) } @Test @@ -277,7 +245,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, - null + null, ) val chipbarView = getChipbarView() @@ -291,13 +259,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id) verify(vibratorHelper) - .vibrate( - any(), - any(), - any<VibrationEffect>(), - any(), - any<VibrationAttributes>(), - ) + .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>()) } @Test @@ -320,7 +282,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, - null + null, ) val chipbarView = getChipbarView() @@ -334,13 +296,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id) verify(vibratorHelper) - .vibrate( - any(), - any(), - any<VibrationEffect>(), - any(), - any<VibrationAttributes>(), - ) + .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>()) } @Test @@ -350,7 +306,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, - null + null, ) val chipbarView = getChipbarView() @@ -364,13 +320,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(uiEventLoggerFake.eventId(2)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id) verify(vibratorHelper, never()) - .vibrate( - any(), - any(), - any<VibrationEffect>(), - any(), - any<VibrationAttributes>(), - ) + .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>()) } @Test @@ -380,7 +330,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, - null + null, ) // Event index 2 since initially displaying the triggered chip would also log two events. @@ -397,7 +347,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, - /* undoCallback= */ null + /* undoCallback= */ null, ) val chipbarView = getChipbarView() @@ -452,7 +402,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, - null + null, ) val chipbarView = getChipbarView() @@ -466,13 +416,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(uiEventLoggerFake.eventId(2)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id) verify(vibratorHelper, never()) - .vibrate( - any(), - any(), - any<VibrationEffect>(), - any(), - any<VibrationAttributes>(), - ) + .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>()) } @Test @@ -481,7 +425,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, - /* undoCallback= */ null + /* undoCallback= */ null, ) val chipbarView = getChipbarView() @@ -538,7 +482,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, routeInfo, - null + null, ) val chipbarView = getChipbarView() @@ -553,13 +497,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(uiEventLoggerFake.eventId(2)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id) verify(vibratorHelper) - .vibrate( - any(), - any(), - any<VibrationEffect>(), - any(), - any<VibrationAttributes>(), - ) + .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>()) } @Test @@ -567,13 +505,13 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, - null + null, ) reset(vibratorHelper) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, routeInfo, - null + null, ) val chipbarView = getChipbarView() @@ -588,13 +526,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(uiEventLoggerFake.eventId(2)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id) verify(vibratorHelper) - .vibrate( - any(), - any(), - any<VibrationEffect>(), - any(), - any<VibrationAttributes>(), - ) + .vibrate(any(), any(), any<VibrationEffect>(), any(), any<VibrationAttributes>()) } @Test @@ -602,7 +534,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) verify(windowManager, never()).addView(any(), any()) @@ -615,13 +547,13 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, routeInfo, - null + null, ) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) val viewCaptor = ArgumentCaptor.forClass(View::class.java) @@ -635,7 +567,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, routeInfo, - null + null, ) assertThat(fakeWakeLock.isHeld).isTrue() @@ -643,7 +575,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) assertThat(fakeWakeLock.isHeld).isFalse() @@ -654,7 +586,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) assertThat(fakeWakeLock.isHeld).isFalse() @@ -672,7 +604,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, - null + null, ) verify(windowManager).addView(any(), any()) reset(windowManager) @@ -680,7 +612,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, routeInfo, - null + null, ) verify(logger).logInvalidStateTransitionError(any(), any()) @@ -692,7 +624,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, - null + null, ) verify(windowManager).addView(any(), any()) reset(windowManager) @@ -700,7 +632,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, routeInfo, - null + null, ) verify(logger).logInvalidStateTransitionError(any(), any()) @@ -713,14 +645,14 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, - null + null, ) reset(windowManager) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, - null + null, ) verify(logger).logInvalidStateTransitionError(any(), any()) @@ -733,14 +665,14 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, - null + null, ) reset(windowManager) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, - null + null, ) verify(logger).logInvalidStateTransitionError(any(), any()) @@ -752,7 +684,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, routeInfo, - null + null, ) verify(windowManager).addView(any(), any()) reset(windowManager) @@ -760,7 +692,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, - null + null, ) verify(logger).logInvalidStateTransitionError(any(), any()) @@ -772,7 +704,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, routeInfo, - null + null, ) verify(windowManager).addView(any(), any()) reset(windowManager) @@ -780,7 +712,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, - null + null, ) verify(logger).logInvalidStateTransitionError(any(), any()) @@ -792,7 +724,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, routeInfo, - null + null, ) verify(windowManager).addView(any(), any()) reset(windowManager) @@ -800,7 +732,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, routeInfo, - null + null, ) verify(logger).logInvalidStateTransitionError(any(), any()) @@ -812,7 +744,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, routeInfo, - null + null, ) verify(windowManager).addView(any(), any()) reset(windowManager) @@ -820,7 +752,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, routeInfo, - null + null, ) verify(logger).logInvalidStateTransitionError(any(), any()) @@ -925,7 +857,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, routeInfo, - null + null, ) verify(logger).logStateChange(any(), any(), any()) @@ -936,13 +868,13 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, - null + null, ) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) fakeExecutor.runAllReady() @@ -959,13 +891,13 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, - null + null, ) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) fakeExecutor.runAllReady() @@ -983,13 +915,13 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, - null + null, ) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) fakeExecutor.runAllReady() @@ -1007,13 +939,13 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, - null + null, ) commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) fakeExecutor.runAllReady() @@ -1051,7 +983,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) fakeExecutor.runAllReady() @@ -1091,7 +1023,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) fakeExecutor.runAllReady() @@ -1116,7 +1048,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { context, dumpManager, logger, - mediaTttFlags, uiEventLogger, ) underTest.start() @@ -1144,7 +1075,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { context, dumpManager, logger, - mediaTttFlags, uiEventLogger, ) underTest.start() @@ -1178,7 +1108,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { context, dumpManager, logger, - mediaTttFlags, uiEventLogger, ) underTest.start() @@ -1211,7 +1140,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { context, dumpManager, logger, - mediaTttFlags, uiEventLogger, ) underTest.start() @@ -1230,7 +1158,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, routeInfo, - null + null, ) // THEN the media coordinator unregisters the listener @@ -1248,7 +1176,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { context, dumpManager, logger, - mediaTttFlags, uiEventLogger, ) underTest.start() @@ -1549,7 +1476,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button) private fun ChipStateSender.getExpectedStateText( - otherDeviceName: String = OTHER_DEVICE_NAME, + otherDeviceName: String = OTHER_DEVICE_NAME ): String? { return this.getChipTextString(context, otherDeviceName).loadText(context) } @@ -1560,7 +1487,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, - null + null, ) } @@ -1570,7 +1497,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, - null + null, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt index 963973588236..991f78a3147c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.SessionCreationSource import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver @@ -56,7 +55,6 @@ import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.spy -import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -150,8 +148,6 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { @Test fun screenCaptureDisabledDialog_isShown_whenFunctionalityIsDisabled() { - whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) - .thenReturn(true) whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>())) .thenReturn(true) @@ -170,48 +166,6 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { } @Test - fun screenCapturePermissionDialog_isShown_correctly() { - whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) - .thenReturn(false) - whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>())) - .thenReturn(false) - whenever(state.hasUserApprovedScreenRecording).thenReturn(false) - - val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch) - screenRecordSwitch.isChecked = true - - bgExecutor.runAllReady() - mainExecutor.runAllReady() - - verify(mediaProjectionMetricsLogger) - .notifyProjectionInitiated( - anyInt(), - eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER), - ) - verify(factory, times(2)).create(any(SystemUIDialog.Delegate::class.java)) - } - - @Test - fun noDialogsAreShown_forScreenRecord_whenApprovalIsAlreadyGiven() { - whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) - .thenReturn(false) - whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>())) - .thenReturn(false) - - val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch) - screenRecordSwitch.isChecked = true - - bgExecutor.runAllReady() - - verify(mediaProjectionMetricsLogger) - .notifyProjectionInitiated( - anyInt(), - eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER), - ) - verify(factory, never()).create() - } - - @Test fun startButton_isDisabled_beforeIssueTypeIsSelected() { assertThat(dialog.getButton(Dialog.BUTTON_POSITIVE).isEnabled).isFalse() } 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 6b16e78436d4..afff4858499a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -42,15 +42,11 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.concurrency.FakeExecutor; @@ -79,10 +75,6 @@ public class RecordingControllerTest extends SysuiTestCase { @Mock private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver; @Mock - private DialogTransitionAnimator mDialogTransitionAnimator; - @Mock - private ActivityStarter mActivityStarter; - @Mock private UserTracker mUserTracker; @Mock private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; @@ -92,10 +84,6 @@ public class RecordingControllerTest extends SysuiTestCase { @Mock private SystemUIDialog mScreenCaptureDisabledDialog; @Mock - private ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory; - @Mock - private ScreenRecordDialogDelegate mScreenRecordDialogDelegate; - @Mock private ScreenRecordPermissionDialogDelegate.Factory mScreenRecordPermissionDialogDelegateFactory; @Mock @@ -103,7 +91,6 @@ public class RecordingControllerTest extends SysuiTestCase { @Mock private SystemUIDialog mScreenRecordSystemUIDialog; - private FakeFeatureFlags mFeatureFlags; private RecordingController mController; private static final int USER_ID = 10; @@ -114,12 +101,8 @@ public class RecordingControllerTest extends SysuiTestCase { Context spiedContext = spy(mContext); when(spiedContext.getUserId()).thenReturn(TEST_USER_ID); - mFeatureFlags = new FakeFeatureFlags(); when(mScreenCaptureDisabledDialogDelegate.createSysUIDialog()) .thenReturn(mScreenCaptureDisabledDialog); - when(mScreenRecordDialogFactory.create(any(), any())) - .thenReturn(mScreenRecordDialogDelegate); - when(mScreenRecordDialogDelegate.createDialog()).thenReturn(mScreenRecordSystemUIDialog); when(mScreenRecordPermissionDialogDelegateFactory.create(any(), any(), anyInt(), any())) .thenReturn(mScreenRecordPermissionDialogDelegate); when(mScreenRecordPermissionDialogDelegate.createDialog()) @@ -127,13 +110,11 @@ public class RecordingControllerTest extends SysuiTestCase { mController = new RecordingController( mMainExecutor, mBroadcastDispatcher, - mFeatureFlags, () -> mDevicePolicyResolver, mUserTracker, new RecordingControllerLogger(logcatLogBuffer("RecordingControllerTest")), mMediaProjectionMetricsLogger, mScreenCaptureDisabledDialogDelegate, - mScreenRecordDialogFactory, mScreenRecordPermissionDialogDelegateFactory ); mController.addCallback(mCallback); @@ -236,46 +217,19 @@ public class RecordingControllerTest extends SysuiTestCase { } @Test - public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() { - mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false); - when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true); - - Dialog dialog = - mController.createScreenRecordDialog( - mContext, - mFeatureFlags, - mDialogTransitionAnimator, - mActivityStarter, - /* onStartRecordingClicked= */ null); - - assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog); - assertThat(mScreenRecordPermissionDialogDelegate) - .isInstanceOf(ScreenRecordPermissionDialogDelegate.class); - } - - @Test - public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() { - mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); + public void testScreenCapturingNotAllowed_returnsDevicePolicyDialog() { when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true); - Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags, - mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + Dialog dialog = mController.createScreenRecordDialog(/* onStartRecordingClicked= */ null); assertThat(dialog).isEqualTo(mScreenCaptureDisabledDialog); } @Test - public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() { - mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); + public void testScreenCapturingAllowed_returnsNullDevicePolicyDialog() { when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); - Dialog dialog = - mController.createScreenRecordDialog( - mContext, - mFeatureFlags, - mDialogTransitionAnimator, - mActivityStarter, - /* onStartRecordingClicked= */ null); + Dialog dialog = mController.createScreenRecordDialog(/* onStartRecordingClicked= */ null); assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog); assertThat(mScreenRecordPermissionDialogDelegate) @@ -283,12 +237,10 @@ public class RecordingControllerTest extends SysuiTestCase { } @Test - public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() { - mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); + public void testScreenCapturingAllowed_logsProjectionInitiated() { when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); - mController.createScreenRecordDialog(mContext, mFeatureFlags, - mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + mController.createScreenRecordDialog(/* onStartRecordingClicked= */ null); verify(mMediaProjectionMetricsLogger) .notifyProjectionInitiated( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java deleted file mode 100644 index 0427011c06f6..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ /dev/null @@ -1,574 +0,0 @@ -/* - * 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.notification.row; - -import static android.app.AppOpsManager.OP_CAMERA; -import static android.app.AppOpsManager.OP_RECORD_AUDIO; -import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; -import static android.app.NotificationManager.IMPORTANCE_DEFAULT; -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; - -import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; - -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -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; -import static org.mockito.Mockito.when; - -import android.app.INotificationManager; -import android.app.Notification; -import android.app.NotificationChannel; -import android.content.Intent; -import android.content.pm.LauncherApps; -import android.content.pm.PackageManager; -import android.content.pm.ShortcutManager; -import android.graphics.Color; -import android.os.Binder; -import android.os.Handler; -import android.os.UserManager; -import android.provider.Settings; -import android.service.notification.StatusBarNotification; -import android.testing.TestableLooper; -import android.util.ArraySet; -import android.view.View; -import android.view.accessibility.AccessibilityManager; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.testing.UiEventLoggerFake; -import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.kosmos.KosmosJavaAdapter; -import com.android.systemui.people.widget.PeopleSpaceWidgetManager; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.power.domain.interactor.PowerInteractorFactory; -import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository; -import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; -import com.android.systemui.settings.UserContextProvider; -import com.android.systemui.shade.ShadeController; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationPresenter; -import com.android.systemui.statusbar.notification.AssistantFeedbackController; -import com.android.systemui.statusbar.notification.NotificationActivityStarter; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; -import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.kotlin.JavaAdapter; -import com.android.systemui.wmshell.BubblesManager; - -import kotlinx.coroutines.test.TestScope; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.Optional; - -/** - * Tests for {@link NotificationGutsManager}. - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -@TestableLooper.RunWithLooper -public class NotificationGutsManagerTest extends SysuiTestCase { - private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; - - private NotificationChannel mTestNotificationChannel = new NotificationChannel( - TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); - - private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); - private final TestScope mTestScope = mKosmos.getTestScope(); - private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); - private final FakeExecutor mExecutor = mKosmos.getFakeExecutor(); - private final Handler mHandler = mKosmos.getFakeExecutorHandler(); - private NotificationTestHelper mHelper; - private NotificationGutsManager mGutsManager; - - @Rule public MockitoRule mockito = MockitoJUnit.rule(); - @Mock private MetricsLogger mMetricsLogger; - @Mock private OnUserInteractionCallback mOnUserInteractionCallback; - @Mock private NotificationPresenter mPresenter; - @Mock private NotificationActivityStarter mNotificationActivityStarter; - @Mock private NotificationListContainer mNotificationListContainer; - @Mock private OnSettingsClickListener mOnSettingsClickListener; - @Mock private DeviceProvisionedController mDeviceProvisionedController; - @Mock private AccessibilityManager mAccessibilityManager; - @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private INotificationManager mINotificationManager; - @Mock private IStatusBarService mBarService; - @Mock private LauncherApps mLauncherApps; - @Mock private ShortcutManager mShortcutManager; - @Mock private ChannelEditorDialogController mChannelEditorDialogController; - @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier; - @Mock private UserContextProvider mContextTracker; - @Mock private BubblesManager mBubblesManager; - @Mock private ShadeController mShadeController; - @Mock private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; - @Mock private AssistantFeedbackController mAssistantFeedbackController; - @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager; - @Mock private StatusBarStateController mStatusBarStateController; - @Mock private HeadsUpManager mHeadsUpManager; - @Mock private ActivityStarter mActivityStarter; - - @Mock private UserManager mUserManager; - - private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; - - @Before - public void setUp() { - allowTestableLooperAsMainThread(); - mHelper = new NotificationTestHelper(mContext, mDependency); - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - - mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor( - mTestScope.getBackgroundScope(), - new WindowRootViewVisibilityRepository(mBarService, mExecutor), - new FakeKeyguardRepository(), - mHeadsUpManager, - PowerInteractorFactory.create().getPowerInteractor(), - mKosmos.getActiveNotificationsInteractor(), - () -> mKosmos.getSceneInteractor() - ); - - mGutsManager = new NotificationGutsManager( - mContext, - mHandler, - mHandler, - mJavaAdapter, - mAccessibilityManager, - mHighPriorityProvider, - mINotificationManager, - mUserManager, - mPeopleSpaceWidgetManager, - mLauncherApps, - mShortcutManager, - mChannelEditorDialogController, - mContextTracker, - mAssistantFeedbackController, - Optional.of(mBubblesManager), - new UiEventLoggerFake(), - mOnUserInteractionCallback, - mShadeController, - mWindowRootViewVisibilityInteractor, - mNotificationLockscreenUserManager, - mStatusBarStateController, - mBarService, - mDeviceProvisionedController, - mMetricsLogger, - mHeadsUpManager, - mActivityStarter); - mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer, - mOnSettingsClickListener); - mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); - mGutsManager.start(); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Test methods: - - @Test - public void testOpenAndCloseGuts() { - NotificationGuts guts = spy(new NotificationGuts(mContext)); - when(guts.post(any())).thenAnswer(invocation -> { - mHandler.post(((Runnable) invocation.getArguments()[0])); - return null; - }); - - // Test doesn't support animation since the guts view is not attached. - doNothing().when(guts).openControls( - anyInt(), - anyInt(), - anyBoolean(), - any(Runnable.class)); - - ExpandableNotificationRow realRow = createTestNotificationRow(); - NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow); - - ExpandableNotificationRow row = spy(realRow); - when(row.getWindowToken()).thenReturn(new Binder()); - when(row.getGuts()).thenReturn(guts); - - assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem)); - assertEquals(View.INVISIBLE, guts.getVisibility()); - mExecutor.runAllReady(); - verify(guts).openControls( - anyInt(), - anyInt(), - anyBoolean(), - any(Runnable.class)); - verify(mHeadsUpManager).setGutsShown(realRow.getEntry(), true); - - assertEquals(View.VISIBLE, guts.getVisibility()); - mGutsManager.closeAndSaveGuts(false, false, true, 0, 0, false); - - verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()); - verify(row, times(1)).setGutsView(any()); - mExecutor.runAllReady(); - verify(mHeadsUpManager).setGutsShown(realRow.getEntry(), false); - } - - @Test - public void testLockscreenShadeVisible_visible_gutsNotClosed() { - // First, start out lockscreen or shade as not visible - mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); - mTestScope.getTestScheduler().runCurrent(); - - NotificationGuts guts = mock(NotificationGuts.class); - mGutsManager.setExposedGuts(guts); - - // WHEN the lockscreen or shade becomes visible - mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); - mTestScope.getTestScheduler().runCurrent(); - - // THEN the guts are not closed - verify(guts, never()).removeCallbacks(any()); - verify(guts, never()).closeControls( - anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()); - } - - @Test - public void testLockscreenShadeVisible_notVisible_gutsClosed() { - // First, start out lockscreen or shade as visible - mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); - mTestScope.getTestScheduler().runCurrent(); - - NotificationGuts guts = mock(NotificationGuts.class); - mGutsManager.setExposedGuts(guts); - - // WHEN the lockscreen or shade is no longer visible - mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); - mTestScope.getTestScheduler().runCurrent(); - - // THEN the guts are closed - verify(guts).removeCallbacks(any()); - verify(guts).closeControls( - /* leavebehinds= */ eq(true), - /* controls= */ eq(true), - /* x= */ anyInt(), - /* y= */ anyInt(), - /* force= */ eq(true)); - } - - @Test - public void testLockscreenShadeVisible_notVisible_listContainerReset() { - // First, start out lockscreen or shade as visible - mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); - mTestScope.getTestScheduler().runCurrent(); - - // WHEN the lockscreen or shade is no longer visible - mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false); - mTestScope.getTestScheduler().runCurrent(); - - // THEN the list container is reset - verify(mNotificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean()); - } - - @Test - public void testChangeDensityOrFontScale() { - NotificationGuts guts = spy(new NotificationGuts(mContext)); - when(guts.post(any())).thenAnswer(invocation -> { - mHandler.post(((Runnable) invocation.getArguments()[0])); - return null; - }); - - // Test doesn't support animation since the guts view is not attached. - doNothing().when(guts).openControls( - anyInt(), - anyInt(), - anyBoolean(), - any(Runnable.class)); - - ExpandableNotificationRow realRow = createTestNotificationRow(); - NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow); - - ExpandableNotificationRow row = spy(realRow); - - when(row.getWindowToken()).thenReturn(new Binder()); - when(row.getGuts()).thenReturn(guts); - doNothing().when(row).ensureGutsInflated(); - - NotificationEntry realEntry = realRow.getEntry(); - NotificationEntry entry = spy(realEntry); - - when(entry.getRow()).thenReturn(row); - when(entry.getGuts()).thenReturn(guts); - - assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem)); - mExecutor.runAllReady(); - verify(guts).openControls( - anyInt(), - anyInt(), - anyBoolean(), - any(Runnable.class)); - - // called once by mGutsManager.bindGuts() in mGutsManager.openGuts() - verify(row).setGutsView(any()); - - row.onDensityOrFontScaleChanged(); - mGutsManager.onDensityOrFontScaleChanged(entry); - - mExecutor.runAllReady(); - - mGutsManager.closeAndSaveGuts(false, false, false, 0, 0, false); - - verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()); - - // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged() - verify(row, times(2)).setGutsView(any()); - } - - @Test - public void testAppOpsSettingsIntent_camera() { - ArraySet<Integer> ops = new ArraySet<>(); - ops.add(OP_CAMERA); - mGutsManager.startAppOpsSettingsActivity("", 0, ops, null); - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mNotificationActivityStarter, times(1)) - .startNotificationGutsIntent(captor.capture(), anyInt(), any()); - assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.getValue().getAction()); - } - - @Test - public void testAppOpsSettingsIntent_mic() { - ArraySet<Integer> ops = new ArraySet<>(); - ops.add(OP_RECORD_AUDIO); - mGutsManager.startAppOpsSettingsActivity("", 0, ops, null); - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mNotificationActivityStarter, times(1)) - .startNotificationGutsIntent(captor.capture(), anyInt(), any()); - assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.getValue().getAction()); - } - - @Test - public void testAppOpsSettingsIntent_camera_mic() { - ArraySet<Integer> ops = new ArraySet<>(); - ops.add(OP_CAMERA); - ops.add(OP_RECORD_AUDIO); - mGutsManager.startAppOpsSettingsActivity("", 0, ops, null); - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mNotificationActivityStarter, times(1)) - .startNotificationGutsIntent(captor.capture(), anyInt(), any()); - assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.getValue().getAction()); - } - - @Test - public void testAppOpsSettingsIntent_overlay() { - ArraySet<Integer> ops = new ArraySet<>(); - ops.add(OP_SYSTEM_ALERT_WINDOW); - mGutsManager.startAppOpsSettingsActivity("", 0, ops, null); - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mNotificationActivityStarter, times(1)) - .startNotificationGutsIntent(captor.capture(), anyInt(), any()); - assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.getValue().getAction()); - } - - @Test - public void testAppOpsSettingsIntent_camera_mic_overlay() { - ArraySet<Integer> ops = new ArraySet<>(); - ops.add(OP_CAMERA); - ops.add(OP_RECORD_AUDIO); - ops.add(OP_SYSTEM_ALERT_WINDOW); - mGutsManager.startAppOpsSettingsActivity("", 0, ops, null); - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mNotificationActivityStarter, times(1)) - .startNotificationGutsIntent(captor.capture(), anyInt(), any()); - assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction()); - } - - @Test - public void testAppOpsSettingsIntent_camera_overlay() { - ArraySet<Integer> ops = new ArraySet<>(); - ops.add(OP_CAMERA); - ops.add(OP_SYSTEM_ALERT_WINDOW); - mGutsManager.startAppOpsSettingsActivity("", 0, ops, null); - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mNotificationActivityStarter, times(1)) - .startNotificationGutsIntent(captor.capture(), anyInt(), any()); - assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction()); - } - - @Test - public void testAppOpsSettingsIntent_mic_overlay() { - ArraySet<Integer> ops = new ArraySet<>(); - ops.add(OP_RECORD_AUDIO); - ops.add(OP_SYSTEM_ALERT_WINDOW); - mGutsManager.startAppOpsSettingsActivity("", 0, ops, null); - ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); - verify(mNotificationActivityStarter, times(1)) - .startNotificationGutsIntent(captor.capture(), anyInt(), any()); - assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction()); - } - - @Test - public void testInitializeNotificationInfoView_highPriority() throws Exception { - NotificationInfo notificationInfoView = mock(NotificationInfo.class); - ExpandableNotificationRow row = spy(mHelper.createRow()); - final NotificationEntry entry = row.getEntry(); - modifyRanking(entry) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .setImportance(IMPORTANCE_HIGH) - .build(); - - when(row.getIsNonblockable()).thenReturn(false); - when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true); - StatusBarNotification statusBarNotification = entry.getSbn(); - mGutsManager.initializeNotificationInfo(row, notificationInfoView); - - verify(notificationInfoView).bindNotification( - any(PackageManager.class), - any(INotificationManager.class), - eq(mOnUserInteractionCallback), - eq(mChannelEditorDialogController), - eq(statusBarNotification.getPackageName()), - any(NotificationChannel.class), - eq(entry), - any(NotificationInfo.OnSettingsClickListener.class), - any(NotificationInfo.OnAppSettingsClickListener.class), - any(UiEventLogger.class), - eq(false), - eq(false), - eq(true), /* wasShownHighPriority */ - eq(mAssistantFeedbackController), - any(MetricsLogger.class)); - } - - @Test - public void testInitializeNotificationInfoView_PassesAlongProvisionedState() throws Exception { - NotificationInfo notificationInfoView = mock(NotificationInfo.class); - ExpandableNotificationRow row = spy(mHelper.createRow()); - modifyRanking(row.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - when(row.getIsNonblockable()).thenReturn(false); - StatusBarNotification statusBarNotification = row.getEntry().getSbn(); - NotificationEntry entry = row.getEntry(); - - when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); - - mGutsManager.initializeNotificationInfo(row, notificationInfoView); - - verify(notificationInfoView).bindNotification( - any(PackageManager.class), - any(INotificationManager.class), - eq(mOnUserInteractionCallback), - eq(mChannelEditorDialogController), - eq(statusBarNotification.getPackageName()), - any(NotificationChannel.class), - eq(entry), - any(NotificationInfo.OnSettingsClickListener.class), - any(NotificationInfo.OnAppSettingsClickListener.class), - any(UiEventLogger.class), - eq(true), - eq(false), - eq(false), /* wasShownHighPriority */ - eq(mAssistantFeedbackController), - any(MetricsLogger.class)); - } - - @Test - public void testInitializeNotificationInfoView_withInitialAction() throws Exception { - NotificationInfo notificationInfoView = mock(NotificationInfo.class); - ExpandableNotificationRow row = spy(mHelper.createRow()); - modifyRanking(row.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - when(row.getIsNonblockable()).thenReturn(false); - StatusBarNotification statusBarNotification = row.getEntry().getSbn(); - NotificationEntry entry = row.getEntry(); - - mGutsManager.initializeNotificationInfo(row, notificationInfoView); - - verify(notificationInfoView).bindNotification( - any(PackageManager.class), - any(INotificationManager.class), - eq(mOnUserInteractionCallback), - eq(mChannelEditorDialogController), - eq(statusBarNotification.getPackageName()), - any(NotificationChannel.class), - eq(entry), - any(NotificationInfo.OnSettingsClickListener.class), - any(NotificationInfo.OnAppSettingsClickListener.class), - any(UiEventLogger.class), - eq(false), - eq(false), - eq(false), /* wasShownHighPriority */ - eq(mAssistantFeedbackController), - any(MetricsLogger.class)); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Utility methods: - - private ExpandableNotificationRow createTestNotificationRow() { - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setContentTitle("foo") - .setColorized(true).setColor(Color.RED) - .setFlag(Notification.FLAG_CAN_COLORIZE, true) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - try { - ExpandableNotificationRow row = mHelper.createRow(nb.build()); - modifyRanking(row.getEntry()) - .setChannel(mTestNotificationChannel) - .build(); - return row; - } catch (Exception e) { - fail(); - return null; - } - } - - private NotificationMenuRowPlugin.MenuItem createTestMenuItem(ExpandableNotificationRow row) { - NotificationMenuRowPlugin menuRow = - new NotificationMenuRow(mContext, mPeopleNotificationIdentifier); - menuRow.createMenu(row, row.getEntry().getSbn()); - - NotificationMenuRowPlugin.MenuItem menuItem = menuRow.getLongpressMenuItem(mContext); - assertNotNull(menuItem); - return menuItem; - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 7d019bf1be92..b142fc2deea9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -510,7 +510,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } mShadeController.setNotificationPresenter(mNotificationPresenter); - when(mOperatorNameViewControllerFactory.create(any())) + when(mOperatorNameViewControllerFactory.create(any(), any())) .thenReturn(mOperatorNameViewController); when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser()); when(mUserTracker.getUserHandle()).thenReturn( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index d01c1ca36c4e..4b648a3e76e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -1184,9 +1184,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mKeyguardStateController = mock(KeyguardStateController.class); mOperatorNameViewController = mock(OperatorNameViewController.class); mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class); - when(mOperatorNameViewControllerFactory.create(any())) + when(mOperatorNameViewControllerFactory.create(any(), any())) .thenReturn(mOperatorNameViewController); - when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager); + when(mIconManagerFactory.create(any(), any(), any())).thenReturn(mIconManager); mSecureSettings = mock(SecureSettings.class); mShadeExpansionStateManager = new ShadeExpansionStateManager(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt index 7e7eea216584..f49e3771763a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt @@ -16,7 +16,48 @@ package com.android.systemui.media.controls.domain.pipeline +import android.content.applicationContext +import android.os.Bundle +import android.os.Handler +import android.os.looper +import androidx.media3.session.CommandButton +import androidx.media3.session.MediaController +import androidx.media3.session.SessionToken +import com.android.systemui.Flags +import com.android.systemui.graphics.imageLoader import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.shared.mediaLogger +import com.android.systemui.media.controls.util.fakeMediaControllerFactory +import com.android.systemui.media.controls.util.fakeSessionTokenFactory +import com.google.common.collect.ImmutableList import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever -var Kosmos.media3ActionFactory: Media3ActionFactory by Kosmos.Fixture { mock {} } +/** + * Set up fake [Media3ActionFactory]. Note that tests using this fake will need to be + * annotated @RunWithLooper + */ +var Kosmos.media3ActionFactory: Media3ActionFactory by + Kosmos.Fixture { + if (Flags.mediaControlsButtonMedia3()) { + val customLayout = ImmutableList.of<CommandButton>() + val media3Controller = + mock<MediaController>().also { + whenever(it.customLayout).thenReturn(customLayout) + whenever(it.sessionExtras).thenReturn(Bundle()) + } + fakeMediaControllerFactory.setMedia3Controller(media3Controller) + fakeSessionTokenFactory.setMedia3SessionToken(mock<SessionToken>()) + } + Media3ActionFactory( + context = applicationContext, + imageLoader = imageLoader, + controllerFactory = fakeMediaControllerFactory, + tokenFactory = fakeSessionTokenFactory, + logger = mediaLogger, + looper = looper, + handler = Handler(looper), + bgScope = testScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt index bda3192085ed..4ed491233f3c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt @@ -29,6 +29,7 @@ import com.android.systemui.qs.footerActionsController import com.android.systemui.qs.footerActionsViewModelFactory import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor import com.android.systemui.qs.panels.ui.viewmodel.inFirstPageViewModel +import com.android.systemui.qs.panels.ui.viewmodel.mediaInRowInLandscapeViewModelFactory import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.shade.transition.largeScreenShadeInterpolator @@ -57,6 +58,7 @@ val Kosmos.qsFragmentComposeViewModelFactory by largeScreenHeaderHelper, tileSquishinessInteractor, inFirstPageViewModel, + mediaInRowInLandscapeViewModelFactory, qqsMediaHost, qsMediaHost, usingMediaInComposeFragment, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt index 7613ea31c622..57aa20ae5f02 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModelKosmos.kt @@ -25,7 +25,7 @@ val Kosmos.infiniteGridViewModelFactory by override fun create(): InfiniteGridViewModel { return InfiniteGridViewModel( dynamicIconTilesViewModelFactory, - qsColumnsViewModel, + qsColumnsViewModelFactory, tileSquishinessViewModel, qsResetDialogDelegateKosmos, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt new file mode 100644 index 000000000000..d1b613fe7f6e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.Configuration +import android.content.res.mainResources +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager +import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.domain.interactor.shadeModeInteractor + +val Kosmos.mediaInRowInLandscapeViewModelFactory by + Kosmos.Fixture { + object : MediaInRowInLandscapeViewModel.Factory { + override fun create(inLocation: Int): MediaInRowInLandscapeViewModel { + return MediaInRowInLandscapeViewModel( + mainResources, + configurationInteractor, + shadeModeInteractor, + mediaHostStatesManager, + usingMediaInComposeFragment, + inLocation, + ) + } + } + } + +fun Kosmos.setConfigurationForMediaInRow(mediaInRow: Boolean) { + shadeRepository.setShadeLayoutWide(!mediaInRow) // media in row only in non wide + val config = + Configuration(mainResources.configuration).apply { + orientation = + if (mediaInRow) { + Configuration.ORIENTATION_LANDSCAPE + } else { + Configuration.ORIENTATION_PORTRAIT + } + screenLayout = Configuration.SCREENLAYOUT_LONG_YES + } + mainResources.configuration.updateFrom(config) + fakeConfigurationRepository.onConfigurationChange(config) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt index 5c8ca83ff2ae..0e5edb75846d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt @@ -23,7 +23,7 @@ val Kosmos.paginatedGridViewModel by Kosmos.Fixture { PaginatedGridViewModel( iconTilesViewModel, - qsColumnsViewModel, + qsColumnsViewModelFactory, paginatedGridInteractor, inFirstPageViewModel, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt index 16b2f5438797..d63b1b0b4224 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt @@ -19,4 +19,15 @@ package com.android.systemui.qs.panels.ui.viewmodel import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.domain.interactor.qsColumnsInteractor -val Kosmos.qsColumnsViewModel by Kosmos.Fixture { QSColumnsSizeViewModelImpl(qsColumnsInteractor) } +val Kosmos.qsColumnsViewModelFactory by + Kosmos.Fixture { + object : QSColumnsViewModel.Factory { + override fun create(mediaLocation: Int?): QSColumnsViewModel { + return QSColumnsViewModel( + qsColumnsInteractor, + mediaInRowInLandscapeViewModelFactory, + mediaLocation, + ) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt index 20be5c675851..81beb20706db 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt @@ -27,8 +27,9 @@ val Kosmos.quickQuickSettingsViewModelFactory by override fun create(): QuickQuickSettingsViewModel { return QuickQuickSettingsViewModel( currentTilesInteractor, - qsColumnsViewModel, + qsColumnsViewModelFactory, quickQuickSettingsRowInteractor, + mediaInRowInLandscapeViewModelFactory, tileSquishinessViewModel, iconTilesViewModel, tileHapticsViewModelFactoryProvider, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/notes/NotesTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/notes/NotesTileKosmos.kt new file mode 100644 index 000000000000..75c98cb07338 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/notes/NotesTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.notes + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.notetask.NoteTaskModule +import com.android.systemui.qs.qsEventLogger + +val Kosmos.qsNotesTileConfig by + Kosmos.Fixture { NoteTaskModule.provideNotesTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index 4228c3c0b110..7e6a7271c561 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -32,7 +32,6 @@ import com.android.systemui.haptics.vibratorHelper import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope @@ -78,7 +77,6 @@ val Kosmos.sceneContainerStartable by Fixture { uiEventLogger = uiEventLogger, sceneBackInteractor = sceneBackInteractor, shadeSessionStorage = shadeSessionStorage, - windowMgrLockscreenVisInteractor = windowManagerLockscreenVisibilityInteractor, keyguardEnabledInteractor = keyguardEnabledInteractor, dismissCallbackRegistry = dismissCallbackRegistry, statusBarStateController = sysuiStatusBarStateController, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt index 718347fc3490..20e4523fda0f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory val Kosmos.notificationsShadeOverlayContentViewModel: @@ -30,5 +31,6 @@ val Kosmos.notificationsShadeOverlayContentViewModel: notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory, sceneInteractor = sceneInteractor, shadeInteractor = shadeInteractor, + activeNotificationsInteractor = activeNotificationsInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt index ad2654a6b471..45aab860cde7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt @@ -29,6 +29,7 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.shade.mockNotificationShadeWindowViewController import com.android.systemui.shade.mockShadeSurface import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository +import com.android.systemui.statusbar.data.repository.lightBarControllerStore import com.android.systemui.statusbar.data.repository.privacyDotWindowControllerStore import com.android.systemui.statusbar.data.repository.statusBarModeRepository import com.android.systemui.statusbar.mockNotificationRemoteInputManager @@ -79,5 +80,6 @@ val Kosmos.multiDisplayStatusBarStarter by statusBarWindowControllerStore, statusBarInitializerStore, privacyDotWindowControllerStore, + lightBarControllerStore, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStoreKosmos.kt new file mode 100644 index 000000000000..34a828176a08 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStoreKosmos.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.display.data.repository.displayWindowPropertiesRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import org.mockito.kotlin.mock + +val Kosmos.multiDisplayDarkIconDispatcherStore by + Kosmos.Fixture { + MultiDisplayDarkIconDispatcherStore( + backgroundApplicationScope = applicationCoroutineScope, + displayRepository = displayRepository, + factory = { _, _ -> mock() }, + displayWindowPropertiesRepository = displayWindowPropertiesRepository, + ) + } + +val Kosmos.fakeDarkIconDispatcherStore by Kosmos.Fixture { FakeDarkIconDispatcherStore() } + +var Kosmos.darkIconDispatcherStore by Kosmos.Fixture { fakeDarkIconDispatcherStore } + +val Kosmos.fakeSysUiDarkIconDispatcherStore by Kosmos.Fixture { FakeSysUiDarkIconDispatcherStore() } + +var Kosmos.sysUiDarkIconDispatcherStore by Kosmos.Fixture { fakeSysUiDarkIconDispatcherStore } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeDarkIconDispatcherStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeDarkIconDispatcherStore.kt new file mode 100644 index 000000000000..871b277d7c82 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeDarkIconDispatcherStore.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +import android.view.Display +import com.android.systemui.plugins.DarkIconDispatcher +import org.mockito.kotlin.mock + +class FakeDarkIconDispatcherStore : DarkIconDispatcherStore { + + private val perDisplayMocks = mutableMapOf<Int, DarkIconDispatcher>() + + override val defaultDisplay: DarkIconDispatcher + get() = forDisplay(Display.DEFAULT_DISPLAY) + + override fun forDisplay(displayId: Int): DarkIconDispatcher { + return perDisplayMocks.computeIfAbsent(displayId) { mock() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeLightBarControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeLightBarControllerStore.kt new file mode 100644 index 000000000000..28232465e633 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeLightBarControllerStore.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +import android.view.Display +import com.android.systemui.statusbar.phone.LightBarController +import org.mockito.kotlin.mock + +class FakeLightBarControllerStore : LightBarControllerStore { + + val perDisplayMocks = mutableMapOf<Int, LightBarController>() + + override val defaultDisplay: LightBarController + get() = forDisplay(Display.DEFAULT_DISPLAY) + + override fun forDisplay(displayId: Int): LightBarController { + return perDisplayMocks.computeIfAbsent(displayId) { mock() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSysUiDarkIconDispatcherStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSysUiDarkIconDispatcherStore.kt new file mode 100644 index 000000000000..4ee323ac40b4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSysUiDarkIconDispatcherStore.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.repository + +import android.view.Display +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher +import org.mockito.kotlin.mock + +class FakeSysUiDarkIconDispatcherStore : SysuiDarkIconDispatcherStore { + + private val perDisplayMocks = mutableMapOf<Int, SysuiDarkIconDispatcher>() + + override val defaultDisplay: SysuiDarkIconDispatcher + get() = forDisplay(Display.DEFAULT_DISPLAY) + + override fun forDisplay(displayId: Int): SysuiDarkIconDispatcher { + return perDisplayMocks.computeIfAbsent(displayId) { mock() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt index 5f337326b546..13fa3feaa453 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt @@ -27,8 +27,13 @@ val Kosmos.lightBarControllerStoreImpl by LightBarControllerStoreImpl( backgroundApplicationScope = applicationCoroutineScope, displayRepository = displayRepository, - factory = { _, _, _ -> mock() }, + factory = { _, _, _, _ -> mock() }, displayScopeRepository = displayScopeRepository, statusBarModeRepositoryStore = statusBarModeRepository, + darkIconDispatcherStore = darkIconDispatcherStore, ) } + +val Kosmos.fakeLightBarControllerStore by Kosmos.Fixture { FakeLightBarControllerStore() } + +var Kosmos.lightBarControllerStore by Kosmos.Fixture { fakeLightBarControllerStore } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt index 282e2e859afe..cb092ced9c72 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt @@ -24,7 +24,10 @@ import kotlinx.coroutines.flow.MutableStateFlow @SysUISingleton class FakeDarkIconRepository @Inject constructor() : DarkIconRepository { - override val darkState = MutableStateFlow(DarkChange.EMPTY) + private val perDisplayStates = mutableMapOf<Int, MutableStateFlow<DarkChange>>() + + override fun darkState(displayId: Int) = + perDisplayStates.computeIfAbsent(displayId) { MutableStateFlow(DarkChange.EMPTY) } } @Module diff --git a/packages/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp new file mode 100644 index 000000000000..be64bb1ae404 --- /dev/null +++ b/packages/Vcn/framework-b/Android.bp @@ -0,0 +1,44 @@ +// +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_team: "trendy_team_enigma", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_defaults { + name: "framework-connectivity-b-defaults", + sdk_version: "module_current", + min_sdk_version: "35", // TODO: Make it Android 25Q2 when this is included in mainline + defaults: ["framework-module-defaults"], // This is a boot jar + + srcs: [ + "src/**/*.java", + ], +} + +java_sdk_library { + name: "framework-connectivity-b", + defaults: [ + "framework-connectivity-b-defaults", + ], + + permitted_packages: [ + "android.net.vcn", + ], + + // TODO: b/375213246 Expose this library to Tethering module +} diff --git a/packages/Vcn/framework-b/api/current.txt b/packages/Vcn/framework-b/api/current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Vcn/framework-b/api/current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Vcn/framework-b/api/module-lib-current.txt b/packages/Vcn/framework-b/api/module-lib-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Vcn/framework-b/api/module-lib-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Vcn/framework-b/api/module-lib-removed.txt b/packages/Vcn/framework-b/api/module-lib-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Vcn/framework-b/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Vcn/framework-b/api/removed.txt b/packages/Vcn/framework-b/api/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Vcn/framework-b/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Vcn/framework-b/api/system-current.txt b/packages/Vcn/framework-b/api/system-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Vcn/framework-b/api/system-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Vcn/framework-b/api/system-removed.txt b/packages/Vcn/framework-b/api/system-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Vcn/framework-b/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Vcn/framework-b/src/android/net/vcn/Placeholder.java b/packages/Vcn/framework-b/src/android/net/vcn/Placeholder.java new file mode 100644 index 000000000000..fb5e15386cc7 --- /dev/null +++ b/packages/Vcn/framework-b/src/android/net/vcn/Placeholder.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.vcn; + +/** + * Placeholder class so new framework-vcn isn't empty + * + * @hide + */ +// This class will be removed once source code is migrated +public class Placeholder {} diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp new file mode 100644 index 000000000000..a462297c07af --- /dev/null +++ b/packages/Vcn/service-b/Android.bp @@ -0,0 +1,36 @@ +// +// 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 { + default_team: "trendy_team_enigma", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "service-connectivity-b-pre-jarjar", + sdk_version: "system_server_current", + min_sdk_version: "35", // TODO: Make it Android 25Q2 when this is included in mainline + defaults: ["framework-system-server-module-defaults"], // This is a system server jar + + srcs: [ + "src/**/*.java", + ], + + // TODO: b/375213246 Expose this library to Tethering module + visibility: [ + "//frameworks/base/services", + ], +} diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/Placeholder.java b/packages/Vcn/service-b/src/com/android/server/vcn/Placeholder.java new file mode 100644 index 000000000000..e79914531c38 --- /dev/null +++ b/packages/Vcn/service-b/src/com/android/server/vcn/Placeholder.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +/** + * Placeholder class so new service-vcn isn't empty + * + * @hide + */ +// This class will be removed once source code is migrated +public class Placeholder {} diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp index 5075f6322d1b..44abb9ffb34f 100644 --- a/packages/overlays/Android.bp +++ b/packages/overlays/Android.bp @@ -21,6 +21,7 @@ package { phony { name: "frameworks-base-overlays", + product_specific: true, required: [ "DisplayCutoutEmulationCornerOverlay", "DisplayCutoutEmulationDoubleOverlay", diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index b3f78ab30021..4731cfbfc935 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -279,6 +279,15 @@ cc_defaults { shared_libs: [ "liblog", ], + visibility: ["//visibility:private"], +} + +cc_library_host_shared { + name: "libravenwood_initializer", + defaults: ["ravenwood_jni_defaults"], + srcs: [ + "runtime-jni/ravenwood_initializer.cpp", + ], } // We need this as a separate library because we need to overload the @@ -301,7 +310,6 @@ cc_library_host_shared { "libutils", "libcutils", ], - visibility: ["//frameworks/base"], } // For collecting the *stats.csv files in a known directory under out/host/linux-x86/testcases/. @@ -659,6 +667,7 @@ android_ravenwood_libgroup { ], jni_libs: [ // Libraries has to be loaded in the following order + "libravenwood_initializer", "libravenwood_sysprop", "libravenwood_runtime", "libandroid_runtime", diff --git a/ravenwood/Framework.bp b/ravenwood/Framework.bp index d20773844df3..99fc31b258e9 100644 --- a/ravenwood/Framework.bp +++ b/ravenwood/Framework.bp @@ -214,7 +214,8 @@ java_genrule { java_genrule { name: "services.core.ravenwood", - defaults: ["ravenwood-internal-only-visibility-genrule"], + // This is used by unit tests too (so tests will be able to access HSG-processed implementation) + // so it's visible to all. cmd: "cp $(in) $(out)", srcs: [ ":services.core.ravenwood-base{ravenwood.jar}", diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING index a1243e37003e..607592b4cbbe 100644 --- a/ravenwood/TEST_MAPPING +++ b/ravenwood/TEST_MAPPING @@ -117,7 +117,11 @@ "host": true }, { - "name": "FrameworksServicesTestsRavenwood", + "name": "FrameworksServicesTestsRavenwood_Compat", + "host": true + }, + { + "name": "FrameworksServicesTestsRavenwood_Uri", "host": true }, { diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java index d29b93c0c171..a208d6dce2ce 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java @@ -40,7 +40,7 @@ public final class RavenwoodNativeLoader { * See frameworks/base/core/jni/platform/host/HostRuntime.cpp */ private static final Class<?>[] sLibandroidClasses = { - android.util.Log.class, +// android.util.Log.class, // Not using native log: b/377377826 android.os.Parcel.class, android.os.Binder.class, android.os.SystemProperties.class, diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index 28c262d53ff1..e61a054c4c39 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -31,9 +31,12 @@ import static org.mockito.Mockito.mock; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.AppCompatCallbacks; import android.app.Instrumentation; import android.app.ResourcesManager; import android.app.UiAutomation; +import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.os.Binder; import android.os.Build; @@ -42,6 +45,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Process_ravenwood; import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; import android.os.SystemProperties; import android.provider.DeviceConfig_host; import android.system.ErrnoException; @@ -51,6 +55,7 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; import com.android.hoststubgen.hosthelper.HostTestUtils; +import com.android.internal.annotations.GuardedBy; import com.android.internal.os.RuntimeInit; import com.android.ravenwood.RavenwoodRuntimeNative; import com.android.ravenwood.RavenwoodRuntimeState; @@ -58,6 +63,7 @@ import com.android.ravenwood.common.RavenwoodCommonUtils; import com.android.ravenwood.common.RavenwoodRuntimeException; import com.android.ravenwood.common.SneakyThrow; import com.android.server.LocalServices; +import com.android.server.compat.PlatformCompat; import org.junit.runner.Description; @@ -86,6 +92,7 @@ public class RavenwoodRuntimeEnvironmentController { } private static final String MAIN_THREAD_NAME = "RavenwoodMain"; + private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer"; private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop"; private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime"; private static final String RAVENWOOD_BUILD_PROP = @@ -139,23 +146,61 @@ public class RavenwoodRuntimeEnvironmentController { return res; } + private static final Object sInitializationLock = new Object(); + + @GuardedBy("sInitializationLock") + private static boolean sInitialized = false; + + @GuardedBy("sInitializationLock") + private static Throwable sExceptionFromGlobalInit; + private static RavenwoodAwareTestRunner sRunner; private static RavenwoodSystemProperties sProps; - private static boolean sInitialized = false; /** * Initialize the global environment. */ public static void globalInitOnce() { - if (sInitialized) { - return; + synchronized (sInitializationLock) { + if (!sInitialized) { + // globalInitOnce() is called from class initializer, which cause + // this method to be called recursively, + sInitialized = true; + + // This is the first call. + try { + globalInitInner(); + } catch (Throwable th) { + Log.e(TAG, "globalInit() failed", th); + + sExceptionFromGlobalInit = th; + throw th; + } + } else { + // Subsequent calls. If the first call threw, just throw the same error, to prevent + // the test from running. + if (sExceptionFromGlobalInit != null) { + Log.e(TAG, "globalInit() failed re-throwing the same exception", + sExceptionFromGlobalInit); + + SneakyThrow.sneakyThrow(sExceptionFromGlobalInit); + } + } + } + } + + private static void globalInitInner() { + if (RAVENWOOD_VERBOSE_LOGGING) { + Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH")); } - sInitialized = true; + + // Some process-wide initialization. (maybe redirect stdout/stderr) + RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME); // We haven't initialized liblog yet, so directly write to System.out here. - RavenwoodCommonUtils.log(TAG, "globalInit()"); + RavenwoodCommonUtils.log(TAG, "globalInitInner()"); - // Load libravenwood_sysprop first + // Load libravenwood_sysprop before other libraries that may use SystemProperties. var libProp = RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_SYSPROP_NAME); System.load(libProp); RavenwoodRuntimeNative.reloadNativeLibrary(libProp); @@ -294,6 +339,8 @@ public class RavenwoodRuntimeEnvironmentController { RavenwoodSystemServer.init(config); + initializeCompatIds(config); + if (ENABLE_TIMEOUT_STACKS) { sPendingTimeout = sTimeoutExecutor.schedule( RavenwoodRuntimeEnvironmentController::dumpStacks, @@ -309,6 +356,31 @@ public class RavenwoodRuntimeEnvironmentController { Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid)); } + private static void initializeCompatIds(RavenwoodConfig config) { + // Set up compat-IDs for the app side. + // TODO: Inside the system server, all the compat-IDs should be enabled, + // Due to the `AppCompatCallbacks.install(new long[0], new long[0])` call in + // SystemServer. + + // Compat framework only uses the package name and the target SDK level. + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.packageName = config.mTargetPackageName; + appInfo.targetSdkVersion = config.mTargetSdkLevel; + + PlatformCompat platformCompat = null; + try { + platformCompat = (PlatformCompat) ServiceManager.getServiceOrThrow( + Context.PLATFORM_COMPAT_SERVICE); + } catch (ServiceNotFoundException e) { + throw new RuntimeException(e); + } + + var disabledChanges = platformCompat.getDisabledChanges(appInfo); + var loggableChanges = platformCompat.getLoggableChanges(appInfo); + + AppCompatCallbacks.install(disabledChanges, loggableChanges); + } + /** * De-initialize. */ diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java index f198a08a50e3..438a2bfa7a14 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java @@ -17,7 +17,9 @@ package android.platform.test.ravenwood; import android.content.ClipboardManager; +import android.content.Context; import android.hardware.SerialManager; +import android.os.ServiceManager; import android.os.SystemClock; import android.ravenwood.example.BlueManager; import android.ravenwood.example.RedManager; @@ -27,6 +29,8 @@ import android.util.ArraySet; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; +import com.android.server.compat.PlatformCompat; +import com.android.server.compat.PlatformCompatNative; import com.android.server.utils.TimingsTraceAndSlog; import java.util.List; @@ -65,6 +69,14 @@ public class RavenwoodSystemServer { private static SystemServiceManager sServiceManager; public static void init(RavenwoodConfig config) { + // Always start PlatformCompat, regardless of the requested services. + // PlatformCompat is not really a SystemService, so it won't receive boot phases / etc. + // This initialization code is copied from SystemServer.java. + PlatformCompat platformCompat = new PlatformCompat(config.mState.mSystemServerContext); + ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat); + ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE, + new PlatformCompatNative(platformCompat)); + // Avoid overhead if no services required if (config.mServicesRequired.isEmpty()) return; diff --git a/ravenwood/runtime-jni/ravenwood_initializer.cpp b/ravenwood/runtime-jni/ravenwood_initializer.cpp new file mode 100644 index 000000000000..89fb7c3c3510 --- /dev/null +++ b/ravenwood/runtime-jni/ravenwood_initializer.cpp @@ -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. + */ + + /* + * This file is compiled into a single SO file, which we load at the very first. + * We can do process-wide initialization here. + */ + +#include <fcntl.h> +#include <unistd.h> + +#include "jni_helper.h" + +static void maybeRedirectLog() { + auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT"); + if (ravenwoodLogOut == NULL) { + return; + } + ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut); + + // Redirect stdin / stdout to /dev/tty. + int ttyFd = open(ravenwoodLogOut, O_WRONLY | O_APPEND); + if (ttyFd == -1) { + ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut, + strerror(errno)); + return; + } + dup2(ttyFd, 1); + dup2(ttyFd, 2); +} + +extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { + ALOGI("%s: JNI_OnLoad", __FILE__); + + maybeRedirectLog(); + return JNI_VERSION_1_4; +} diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp index 5b75e9854758..c1993f691686 100644 --- a/ravenwood/runtime-jni/ravenwood_runtime.cpp +++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp @@ -180,24 +180,6 @@ static jint Linux_gettid(JNIEnv* env, jobject) { return syscall(__NR_gettid); } -static void maybeRedirectLog() { - auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT"); - if (ravenwoodLogOut == NULL) { - return; - } - ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut); - - // Redirect stdin / stdout to /dev/tty. - int ttyFd = open(ravenwoodLogOut, O_WRONLY); - if (ttyFd == -1) { - ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut, - strerror(errno)); - return; - } - dup2(ttyFd, 1); - dup2(ttyFd, 2); -} - // ---- Registration ---- extern void register_android_system_OsConstants(JNIEnv* env); @@ -218,8 +200,6 @@ static const JNINativeMethod sMethods[] = }; extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { - maybeRedirectLog(); - ALOGI("%s: JNI_OnLoad", __FILE__); JNIEnv* env = GetJNIEnvOrDie(vm); diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh index 672c685aa6d7..1910100a7f5d 100755 --- a/ravenwood/scripts/run-ravenwood-tests.sh +++ b/ravenwood/scripts/run-ravenwood-tests.sh @@ -26,7 +26,7 @@ # Regex to identify slow tests, in PCRE -SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$' +SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood|CarSystemUIRavenTests)$' smoke=0 include_re="" @@ -67,7 +67,7 @@ filter() { if [[ "$re" == "" ]] ; then cat # No filtering else - grep $grep_arg -P "$re" + grep $grep_arg -iP "$re" fi } diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp index 4895a1a6d1a2..40e6672a3c63 100644 --- a/ravenwood/tests/bivalenttest/Android.bp +++ b/ravenwood/tests/bivalenttest/Android.bp @@ -58,6 +58,9 @@ java_defaults { java_defaults { name: "ravenwood-bivalent-device-defaults", defaults: ["ravenwood-bivalent-defaults"], + + target_sdk_version: "34", // For compat-framework tests + // TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture exclude_srcs: [ "test/**/ravenizer/*.java", diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt new file mode 100644 index 000000000000..a95760db1a61 --- /dev/null +++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.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.ravenwoodtest.bivalenttest.compat + +import android.app.compat.CompatChanges +import android.os.Build +import android.platform.test.ravenwood.RavenwoodConfig +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.internal.ravenwood.RavenwoodEnvironment.CompatIdsForTest +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RavenwoodCompatFrameworkTest { + companion object { + @JvmField // Expose as a raw field, not as a property. + @RavenwoodConfig.Config + val config = RavenwoodConfig.Builder() + .setTargetSdkLevel(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + .build() + } + + @Test + fun testEnabled() { + Assert.assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_1)) + } + + @Test + fun testDisabled() { + Assert.assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_2)) + } + + @Test + fun testEnabledAfterSForUApps() { + Assert.assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_3)) + } + + @Test + fun testEnabledAfterUForUApps() { + Assert.assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_4)) + } +}
\ No newline at end of file diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index 70c1d781587f..82be2c0db24e 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -365,3 +365,9 @@ com.android.server.utils.TimingsTraceAndSlog android.os.IpcDataCache android.app.PropertyInvalidatedCache + +android.app.compat.* +com.android.server.compat.* +com.android.internal.compat.* +android.app.AppCompatCallbacks + diff --git a/ravenwood/texts/ravenwood-services-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt index cc2fa602b3c3..530e5c8f5986 100644 --- a/ravenwood/texts/ravenwood-services-policies.txt +++ b/ravenwood/texts/ravenwood-services-policies.txt @@ -1 +1,12 @@ # Ravenwood "policy" file for services.core. + +# Auto-generated from XSD +class com.android.server.compat.config.Change keepclass +class com.android.server.compat.config.Config keepclass +class com.android.server.compat.config.XmlParser keepclass +class com.android.server.compat.overrides.ChangeOverrides keepclass +class com.android.server.compat.overrides.OverrideValue keepclass +class com.android.server.compat.overrides.Overrides keepclass +class com.android.server.compat.overrides.RawOverrideValue keepclass +class com.android.server.compat.overrides.XmlParser keepclass +class com.android.server.compat.overrides.XmlWriter keepclass
\ No newline at end of file diff --git a/services/Android.bp b/services/Android.bp index 899e224c6fd7..fc0bb33e6e4e 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -282,6 +282,7 @@ system_java_library { "services.wifi", "service-blobstore", "service-jobscheduler", + "service-connectivity-b-pre-jarjar", // Move it to mainline module "android.hidl.base-V1.0-java", ], diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 51034d24df14..7cba9e0ccca8 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -31,16 +31,13 @@ import static android.os.UserHandle.getCallingUserId; import static com.android.internal.util.CollectionUtils.any; import static com.android.internal.util.Preconditions.checkState; -import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; -import static com.android.server.companion.utils.PackageUtils.getPackageInfo; import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr; import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MINUTES; import android.annotation.EnforcePermission; import android.annotation.NonNull; @@ -69,31 +66,22 @@ import android.companion.datatransfer.PermissionSyncRequest; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.net.MacAddress; -import android.net.NetworkPolicyManager; import android.os.Binder; -import android.os.Environment; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.PowerExemptionManager; import android.os.PowerManagerInternal; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.permission.flags.Flags; -import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.Slog; -import com.android.internal.app.IAppOpsService; import com.android.internal.content.PackageMonitor; import com.android.internal.notification.NotificationAccessConfirmationActivityContract; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; @@ -114,35 +102,25 @@ import com.android.server.companion.devicepresence.DevicePresenceProcessor; import com.android.server.companion.devicepresence.ObservableUuid; import com.android.server.companion.devicepresence.ObservableUuidStore; import com.android.server.companion.transport.CompanionTransportManager; -import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; -import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collection; import java.util.List; -import java.util.Set; @SuppressLint("LongLogTag") public class CompanionDeviceManagerService extends SystemService { private static final String TAG = "CDM_CompanionDeviceManagerService"; private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min - - private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; - private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; private static final int MAX_CN_LENGTH = 500; - private final ActivityTaskManagerInternal mAtmInternal; - private final ActivityManagerInternal mAmInternal; - private final IAppOpsService mAppOpsManager; - private final PowerExemptionManager mPowerExemptionManager; - private final PackageManagerInternal mPackageManagerInternal; - private final AssociationStore mAssociationStore; private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; private final ObservableUuidStore mObservableUuidStore; + + private final CompanionExemptionProcessor mCompanionExemptionProcessor; private final AssociationRequestsProcessor mAssociationRequestsProcessor; private final SystemDataTransferProcessor mSystemDataTransferProcessor; private final BackupRestoreProcessor mBackupRestoreProcessor; @@ -156,12 +134,15 @@ public class CompanionDeviceManagerService extends SystemService { super(context); final ActivityManager activityManager = context.getSystemService(ActivityManager.class); - mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class); - mAppOpsManager = IAppOpsService.Stub.asInterface( - ServiceManager.getService(Context.APP_OPS_SERVICE)); - mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); - mAmInternal = LocalServices.getService(ActivityManagerInternal.class); - mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + final PowerExemptionManager powerExemptionManager = context.getSystemService( + PowerExemptionManager.class); + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + final ActivityTaskManagerInternal atmInternal = LocalServices.getService( + ActivityTaskManagerInternal.class); + final ActivityManagerInternal amInternal = LocalServices.getService( + ActivityManagerInternal.class); + final PackageManagerInternal packageManagerInternal = LocalServices.getService( + PackageManagerInternal.class); final UserManager userManager = context.getSystemService(UserManager.class); final PowerManagerInternal powerManagerInternal = LocalServices.getService( PowerManagerInternal.class); @@ -173,25 +154,29 @@ public class CompanionDeviceManagerService extends SystemService { // Init processors mAssociationRequestsProcessor = new AssociationRequestsProcessor(context, - mPackageManagerInternal, mAssociationStore); - mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal, + packageManagerInternal, mAssociationStore); + mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal, mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore, mAssociationRequestsProcessor); mCompanionAppBinder = new CompanionAppBinder(context); + mCompanionExemptionProcessor = new CompanionExemptionProcessor(context, + powerExemptionManager, appOpsManager, packageManagerInternal, atmInternal, + amInternal, mAssociationStore); + mDevicePresenceProcessor = new DevicePresenceProcessor(context, mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore, - powerManagerInternal); + powerManagerInternal, mCompanionExemptionProcessor); mTransportManager = new CompanionTransportManager(context, mAssociationStore); mDisassociationProcessor = new DisassociationProcessor(context, activityManager, - mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor, + mAssociationStore, packageManagerInternal, mDevicePresenceProcessor, mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager); mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, - mPackageManagerInternal, mAssociationStore, + packageManagerInternal, mAssociationStore, mSystemDataTransferRequestStore, mTransportManager); // TODO(b/279663946): move context sync to a dedicated system service @@ -202,7 +187,6 @@ public class CompanionDeviceManagerService extends SystemService { public void onStart() { // Init association stores mAssociationStore.refreshCache(); - mAssociationStore.registerLocalListener(mAssociationStoreChangeListener); // Init UUID store mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId()); @@ -240,11 +224,8 @@ public class CompanionDeviceManagerService extends SystemService { if (associations.isEmpty()) return; - updateAtm(userId, associations); - - BackgroundThread.getHandler().sendMessageDelayed( - obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this), - MINUTES.toMillis(10)); + mCompanionExemptionProcessor.updateAtm(userId, associations); + mCompanionExemptionProcessor.updateAutoRevokeExemptions(); } @Override @@ -262,9 +243,12 @@ public class CompanionDeviceManagerService extends SystemService { if (!associationsForPackage.isEmpty()) { Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=[" + packageName + "]. Cleaning up CDM data..."); - } - for (AssociationInfo association : associationsForPackage) { - mDisassociationProcessor.disassociate(association.getId()); + + for (AssociationInfo association : associationsForPackage) { + mDisassociationProcessor.disassociate(association.getId()); + } + + mCompanionAppBinder.onPackageChanged(userId); } // Clear observable UUIDs for the package. @@ -273,19 +257,16 @@ public class CompanionDeviceManagerService extends SystemService { for (ObservableUuid uuid : uuidsTobeObserved) { mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName); } - - mCompanionAppBinder.onPackagesChanged(userId); } private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) { - final List<AssociationInfo> associationsForPackage = + final List<AssociationInfo> associations = mAssociationStore.getAssociationsByPackage(userId, packageName); - for (AssociationInfo association : associationsForPackage) { - updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(), - association.getPackageName()); - } + if (!associations.isEmpty()) { + mCompanionExemptionProcessor.exemptPackage(userId, packageName, false); - mCompanionAppBinder.onPackagesChanged(userId); + mCompanionAppBinder.onPackageChanged(userId); + } } private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) { @@ -765,130 +746,6 @@ public class CompanionDeviceManagerService extends SystemService { } } - /** - * Update special access for the association's package - */ - public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) { - final PackageInfo packageInfo = - getPackageInfo(getContext(), userId, packageName); - - Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo)); - } - - private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) { - if (packageInfo == null) { - return; - } - - if (containsEither(packageInfo.requestedPermissions, - android.Manifest.permission.RUN_IN_BACKGROUND, - android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) { - mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName); - } else { - try { - mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName); - } catch (UnsupportedOperationException e) { - Slog.w(TAG, packageInfo.packageName + " can't be removed from power save" - + " whitelist. It might due to the package is whitelisted by the system."); - } - } - - NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext()); - try { - if (containsEither(packageInfo.requestedPermissions, - android.Manifest.permission.USE_DATA_IN_BACKGROUND, - android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) { - networkPolicyManager.addUidPolicy( - packageInfo.applicationInfo.uid, - NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); - } else { - networkPolicyManager.removeUidPolicy( - packageInfo.applicationInfo.uid, - NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); - } - } catch (IllegalArgumentException e) { - Slog.e(TAG, e.getMessage()); - } - - exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); - } - - private void exemptFromAutoRevoke(String packageName, int uid) { - try { - mAppOpsManager.setMode( - AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, - uid, - packageName, - AppOpsManager.MODE_IGNORED); - } catch (RemoteException e) { - Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e); - } - } - - private void updateAtm(int userId, List<AssociationInfo> associations) { - final Set<Integer> companionAppUids = new ArraySet<>(); - for (AssociationInfo association : associations) { - final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(), - 0, userId); - if (uid >= 0) { - companionAppUids.add(uid); - } - } - if (mAtmInternal != null) { - mAtmInternal.setCompanionAppUids(userId, companionAppUids); - } - if (mAmInternal != null) { - // Make a copy of the set and send it to ActivityManager. - mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids)); - } - } - - private void maybeGrantAutoRevokeExemptions() { - Slog.d(TAG, "maybeGrantAutoRevokeExemptions()"); - - PackageManager pm = getContext().getPackageManager(); - for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { - SharedPreferences pref = getContext().getSharedPreferences( - new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), - Context.MODE_PRIVATE); - if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { - continue; - } - - try { - final List<AssociationInfo> associations = - mAssociationStore.getActiveAssociationsByUser(userId); - for (AssociationInfo a : associations) { - try { - int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); - exemptFromAutoRevoke(a.getPackageName(), uid); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e); - } - } - } finally { - pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); - } - } - } - - private final AssociationStore.OnChangeListener mAssociationStoreChangeListener = - new AssociationStore.OnChangeListener() { - @Override - public void onAssociationChanged(int changeType, AssociationInfo association) { - Slog.d(TAG, "onAssociationChanged changeType=[" + changeType - + "], association=[" + association); - - final int userId = association.getUserId(); - final List<AssociationInfo> updatedAssociations = - mAssociationStore.getActiveAssociationsByUser(userId); - - updateAtm(userId, updatedAssociations); - updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(), - association.getPackageName()); - } - }; - private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override public void onPackageRemoved(String packageName, int uid) { @@ -911,10 +768,6 @@ public class CompanionDeviceManagerService extends SystemService { } }; - private static <T> boolean containsEither(T[] array, T a, T b) { - return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); - } - private class LocalService implements CompanionDeviceManagerServiceInternal { @Override diff --git a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java new file mode 100644 index 000000000000..ea2bc17dafcd --- /dev/null +++ b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java @@ -0,0 +1,225 @@ +/* + * 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.companion; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; + +import static com.android.server.companion.utils.PackageUtils.getPackageInfo; + +import android.annotation.SuppressLint; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.companion.AssociationInfo; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.net.NetworkPolicyManager; +import android.os.Binder; +import android.os.Environment; +import android.os.PowerExemptionManager; +import android.util.ArraySet; +import android.util.Pair; +import android.util.Slog; + +import com.android.internal.util.ArrayUtils; +import com.android.server.LocalServices; +import com.android.server.companion.association.AssociationStore; +import com.android.server.pm.UserManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; + +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@SuppressLint("LongLogTag") +public class CompanionExemptionProcessor { + + private static final String TAG = "CDM_CompanionExemptionProcessor"; + + private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; + private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; + + private final Context mContext; + private final PowerExemptionManager mPowerExemptionManager; + private final AppOpsManager mAppOpsManager; + private final PackageManagerInternal mPackageManager; + private final ActivityTaskManagerInternal mAtmInternal; + private final ActivityManagerInternal mAmInternal; + private final AssociationStore mAssociationStore; + + public CompanionExemptionProcessor(Context context, PowerExemptionManager powerExemptionManager, + AppOpsManager appOpsManager, PackageManagerInternal packageManager, + ActivityTaskManagerInternal atmInternal, ActivityManagerInternal amInternal, + AssociationStore associationStore) { + mContext = context; + mPowerExemptionManager = powerExemptionManager; + mAppOpsManager = appOpsManager; + mPackageManager = packageManager; + mAtmInternal = atmInternal; + mAmInternal = amInternal; + mAssociationStore = associationStore; + + mAssociationStore.registerLocalListener(new AssociationStore.OnChangeListener() { + @Override + public void onAssociationChanged(int changeType, AssociationInfo association) { + final int userId = association.getUserId(); + final List<AssociationInfo> updatedAssociations = + mAssociationStore.getActiveAssociationsByUser(userId); + + updateAtm(userId, updatedAssociations); + } + }); + } + + /** + * Update ActivityManager and ActivityTaskManager exemptions + */ + public void updateAtm(int userId, List<AssociationInfo> associations) { + final Set<Integer> companionAppUids = new ArraySet<>(); + for (AssociationInfo association : associations) { + int uid = mPackageManager.getPackageUid(association.getPackageName(), 0, userId); + if (uid >= 0) { + companionAppUids.add(uid); + } + } + if (mAtmInternal != null) { + mAtmInternal.setCompanionAppUids(userId, companionAppUids); + } + if (mAmInternal != null) { + // Make a copy of the set and send it to ActivityManager. + mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids)); + } + } + + /** + * Update special access for the association's package + */ + public void exemptPackage(int userId, String packageName, boolean hasPresentDevices) { + Slog.i(TAG, "Exempting package [" + packageName + "]..."); + + final PackageInfo packageInfo = getPackageInfo(mContext, userId, packageName); + + Binder.withCleanCallingIdentity( + () -> exemptPackageAsSystem(userId, packageInfo, hasPresentDevices)); + } + + @SuppressLint("MissingPermission") + private void exemptPackageAsSystem(int userId, PackageInfo packageInfo, + boolean hasPresentDevices) { + if (packageInfo == null) { + return; + } + + // If the app has run-in-bg permission and present devices, add it to power saver allowlist. + if (containsEither(packageInfo.requestedPermissions, + android.Manifest.permission.RUN_IN_BACKGROUND, + android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND) + && hasPresentDevices) { + mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName); + } else { + try { + mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName); + } catch (UnsupportedOperationException e) { + Slog.w(TAG, packageInfo.packageName + " can't be removed from power save" + + " allowlist. It might be due to the package being allowlisted by the" + + " system."); + } + } + + // If the app has run-in-bg permission and present device, allow metered network use. + NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(mContext); + try { + if (containsEither(packageInfo.requestedPermissions, + android.Manifest.permission.USE_DATA_IN_BACKGROUND, + android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND) + && hasPresentDevices) { + networkPolicyManager.addUidPolicy( + packageInfo.applicationInfo.uid, + NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); + } else { + networkPolicyManager.removeUidPolicy( + packageInfo.applicationInfo.uid, + NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); + } + } catch (IllegalArgumentException e) { + Slog.e(TAG, e.getMessage()); + } + + updateAutoRevokeExemption(packageInfo.packageName, packageInfo.applicationInfo.uid, + !mAssociationStore.getActiveAssociationsByPackage(userId, + packageInfo.packageName).isEmpty()); + } + + /** + * Update auto revoke exemptions. + * If the app has any association, exempt it from permission auto revoke. + */ + public void updateAutoRevokeExemptions() { + Slog.d(TAG, "maybeGrantAutoRevokeExemptions()"); + + PackageManager pm = mContext.getPackageManager(); + for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { + SharedPreferences pref = mContext.getSharedPreferences( + new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), + Context.MODE_PRIVATE); + if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { + continue; + } + + try { + final List<AssociationInfo> associations = + mAssociationStore.getActiveAssociationsByUser(userId); + Set<Pair<String, Integer>> exemptedPackages = new HashSet<>(); + for (AssociationInfo a : associations) { + try { + int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); + exemptedPackages.add(new Pair<>(a.getPackageName(), uid)); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e); + } + } + for (Pair<String, Integer> exemptedPackage : exemptedPackages) { + updateAutoRevokeExemption(exemptedPackage.first, exemptedPackage.second, true); + } + } finally { + pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); + } + } + } + + @SuppressLint("MissingPermission") + private void updateAutoRevokeExemption(String packageName, int uid, boolean hasAssociations) { + try { + mAppOpsManager.setMode( + AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, + uid, + packageName, + hasAssociations ? MODE_IGNORED : MODE_ALLOWED); + } catch (Exception e) { + Slog.e(TAG, "Error while granting auto revoke exemption for " + packageName, e); + } + } + + private <T> boolean containsEither(T[] array, T a, T b) { + return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); + } + +} diff --git a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java index 60f46887fa5c..8307da5f7218 100644 --- a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java +++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java @@ -95,7 +95,9 @@ public class CompanionAppBinder { /** * On package changed. */ - public void onPackagesChanged(@UserIdInt int userId) { + public void onPackageChanged(@UserIdInt int userId) { + // Note: To invalidate the user space for simplicity. We could alternatively manage each + // package, but that would easily cause errors if one case is mis-handled. mCompanionServicesRegister.invalidate(userId); } @@ -299,12 +301,14 @@ public class CompanionAppBinder { private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { @Override - public synchronized @NonNull Map<String, List<ComponentName>> forUser( + @NonNull + public synchronized Map<String, List<ComponentName>> forUser( @UserIdInt int userId) { return super.forUser(userId); } - synchronized @NonNull List<ComponentName> forPackage( + @NonNull + synchronized List<ComponentName> forPackage( @UserIdInt int userId, @NonNull String packageName) { return forUser(userId).getOrDefault(packageName, Collections.emptyList()); } @@ -314,7 +318,8 @@ public class CompanionAppBinder { } @Override - protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) { + @NonNull + protected final Map<String, List<ComponentName>> create(@UserIdInt int userId) { return PackageUtils.getCompanionServicesForUser(mContext, userId); } } diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java index a374d279af0b..7b4dd7df8be3 100644 --- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java +++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java @@ -57,6 +57,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.CollectionUtils; +import com.android.server.companion.CompanionExemptionProcessor; import com.android.server.companion.association.AssociationStore; import java.io.PrintWriter; @@ -101,6 +102,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene private final PowerManagerInternal mPowerManagerInternal; @NonNull private final UserManager mUserManager; + @NonNull + private final CompanionExemptionProcessor mCompanionExemptionProcessor; // NOTE: Same association may appear in more than one of the following sets at the same time. // (E.g. self-managed devices that have MAC addresses, could be reported as present by their @@ -111,7 +114,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene @NonNull private final Set<Integer> mNearbyBleDevices = new HashSet<>(); @NonNull - private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>(); + private final Set<Integer> mConnectedSelfManagedDevices = new HashSet<>(); @NonNull private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>(); @NonNull @@ -146,7 +149,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene @NonNull UserManager userManager, @NonNull AssociationStore associationStore, @NonNull ObservableUuidStore observableUuidStore, - @NonNull PowerManagerInternal powerManagerInternal) { + @NonNull PowerManagerInternal powerManagerInternal, + @NonNull CompanionExemptionProcessor companionExemptionProcessor) { mContext = context; mCompanionAppBinder = companionAppBinder; mAssociationStore = associationStore; @@ -156,6 +160,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene mObservableUuidStore, this); mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this); mPowerManagerInternal = powerManagerInternal; + mCompanionExemptionProcessor = companionExemptionProcessor; } /** Initialize {@link DevicePresenceProcessor} */ @@ -404,7 +409,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * nearby (for "self-managed" associations). */ public boolean isDevicePresent(int associationId) { - return mReportedSelfManagedDevices.contains(associationId) + return mConnectedSelfManagedDevices.contains(associationId) || mConnectedBtDevices.contains(associationId) || mNearbyBleDevices.contains(associationId) || mSimulated.contains(associationId); @@ -451,7 +456,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * notifyDeviceAppeared()} */ public void onSelfManagedDeviceConnected(int associationId) { - onDevicePresenceEvent(mReportedSelfManagedDevices, + onDevicePresenceEvent(mConnectedSelfManagedDevices, associationId, EVENT_SELF_MANAGED_APPEARED); } @@ -467,7 +472,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * notifyDeviceDisappeared()} */ public void onSelfManagedDeviceDisconnected(int associationId) { - onDevicePresenceEvent(mReportedSelfManagedDevices, + onDevicePresenceEvent(mConnectedSelfManagedDevices, associationId, EVENT_SELF_MANAGED_DISAPPEARED); } @@ -475,7 +480,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * Marks a "self-managed" device as disconnected when binderDied. */ public void onSelfManagedDeviceReporterBinderDied(int associationId) { - onDevicePresenceEvent(mReportedSelfManagedDevices, + onDevicePresenceEvent(mConnectedSelfManagedDevices, associationId, EVENT_SELF_MANAGED_DISAPPEARED); } @@ -683,6 +688,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene if (association.shouldBindWhenPresent()) { bindApplicationIfNeeded(userId, packageName, association.isSelfManaged()); + mCompanionExemptionProcessor.exemptPackage(userId, packageName, true); } else { return; } @@ -715,6 +721,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene // Check if there are other devices associated to the app that are present. if (!shouldBindPackage(userId, packageName)) { mCompanionAppBinder.unbindCompanionApp(userId, packageName); + mCompanionExemptionProcessor.exemptPackage(userId, packageName, false); } break; default: @@ -940,7 +947,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene mConnectedBtDevices.remove(id); mNearbyBleDevices.remove(id); - mReportedSelfManagedDevices.remove(id); + mConnectedSelfManagedDevices.remove(id); mSimulated.remove(id); synchronized (mBtDisconnectedDevices) { mBtDisconnectedDevices.remove(id); @@ -1100,7 +1107,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene out.append("Companion Device Present: "); if (mConnectedBtDevices.isEmpty() && mNearbyBleDevices.isEmpty() - && mReportedSelfManagedDevices.isEmpty()) { + && mConnectedSelfManagedDevices.isEmpty()) { out.append("<empty>\n"); return; } else { @@ -1130,11 +1137,11 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene } out.append(" Self-Reported Devices: "); - if (mReportedSelfManagedDevices.isEmpty()) { + if (mConnectedSelfManagedDevices.isEmpty()) { out.append("<empty>\n"); } else { out.append("\n"); - for (int associationId : mReportedSelfManagedDevices) { + for (int associationId : mConnectedSelfManagedDevices) { AssociationInfo a = mAssociationStore.getAssociationById(associationId); out.append(" ").append(a.toShortString()).append('\n'); } diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index ea6351baf597..fd18fa856916 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -24,7 +24,8 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; -import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; +import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; @@ -261,24 +262,28 @@ public class ContextualSearchManagerService extends SystemService { } } - private Intent getResolvedLaunchIntent() { + private Intent getResolvedLaunchIntent(int userId) { synchronized (this) { + if(DEBUG_USER) Log.d(TAG, "Attempting to getResolvedLaunchIntent"); // If mTemporaryPackage is not null, use it to get the ContextualSearch intent. String csPkgName = getContextualSearchPackageName(); if (csPkgName.isEmpty()) { // Return null if csPackageName is not specified. + if (DEBUG_USER) Log.w(TAG, "getContextualSearchPackageName is empty"); return null; } Intent launchIntent = new Intent( ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH); launchIntent.setPackage(csPkgName); - ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity( - launchIntent, MATCH_FACTORY_ONLY); + ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser( + launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId); if (resolveInfo == null) { + if (DEBUG_USER) Log.w(TAG, "resolveInfo is null"); return null; } ComponentName componentName = resolveInfo.getComponentInfo().getComponentName(); if (componentName == null) { + if (DEBUG_USER) Log.w(TAG, "componentName is null"); return null; } launchIntent.setComponent(componentName); @@ -286,9 +291,10 @@ public class ContextualSearchManagerService extends SystemService { } } - private Intent getContextualSearchIntent(int entrypoint, CallbackToken mToken) { - final Intent launchIntent = getResolvedLaunchIntent(); + private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) { + final Intent launchIntent = getResolvedLaunchIntent(userId); if (launchIntent == null) { + if (DEBUG_USER) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null"); return null; } @@ -341,6 +347,7 @@ public class ContextualSearchManagerService extends SystemService { TYPE_NAVIGATION_BAR_PANEL, TYPE_POINTER)); } else { + if (DEBUG_USER) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null"); shb = null; } final Bitmap bm = shb != null ? shb.asBitmap() : null; @@ -444,7 +451,7 @@ public class ContextualSearchManagerService extends SystemService { @Override public void startContextualSearch(int entrypoint) { synchronized (this) { - if (DEBUG_USER) Log.d(TAG, "startContextualSearch"); + if (DEBUG_USER) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint); enforcePermission("startContextualSearch"); final int callingUserId = Binder.getCallingUserHandle().getIdentifier(); @@ -455,7 +462,8 @@ public class ContextualSearchManagerService extends SystemService { // server has READ_FRAME_BUFFER permission to get the screenshot and because only // the system server can invoke non-exported activities. Binder.withCleanCallingIdentity(() -> { - Intent launchIntent = getContextualSearchIntent(entrypoint, mToken); + Intent launchIntent = + getContextualSearchIntent(entrypoint, callingUserId, mToken); if (launchIntent != null) { int result = invokeContextualSearchIntent(launchIntent, callingUserId); if (DEBUG_USER) Log.d(TAG, "Launch result: " + result); diff --git a/services/core/Android.bp b/services/core/Android.bp index 6cfd44bb2d1a..3ccad16073a7 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -240,6 +240,7 @@ java_library_static { "aconfig_new_storage_flags_lib", "powerstats_flags_lib", "locksettings_flags_lib", + "profiling_flags_lib", ], javac_shard_size: 50, javacflags: [ @@ -255,6 +256,13 @@ java_library_static { "FlaggedApi", ], }, + jarjar_rules: ":services-jarjar-rules", + apex_available: ["//apex_available:platform"], +} + +filegroup { + name: "services-jarjar-rules", + srcs: ["services-jarjar-rules.txt"], } java_genrule { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 9d27731cecd4..b7bc4e4827ef 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -96,6 +96,7 @@ import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.ParcelableException; +import android.os.PermissionEnforcer; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteCallbackList; @@ -3653,10 +3654,16 @@ class StorageManagerService extends IStorageManager.Stub return mInternalStorageSize; } - @EnforcePermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @Override public int getInternalStorageRemainingLifetime() throws RemoteException { - super.getInternalStorageRemainingLifetime_enforcePermission(); + PermissionEnforcer.fromContext(mContext) + .enforcePermissionAnyOf( + new String[] { + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + android.Manifest.permission.ALLOCATE_AGGRESSIVE + }, + getCallingPid(), + getCallingUid()); return mVold.getStorageRemainingLifetime(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3e7bcb81c47f..b5dcdb1ac287 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -337,6 +337,8 @@ import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; import android.os.Process; +import android.os.ProfilingServiceHelper; +import android.os.ProfilingTrigger; import android.os.RemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -1089,7 +1091,18 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void onReportFullyDrawn(long id, long timestampNanos) { - mProcessList.getAppStartInfoTracker().onActivityReportFullyDrawn(id, timestampNanos); + ApplicationStartInfo startInfo = mProcessList.getAppStartInfoTracker() + .onActivityReportFullyDrawn(id, timestampNanos); + + if (android.os.profiling.Flags.systemTriggeredProfilingNew() + && startInfo != null + && startInfo.getStartType() == ApplicationStartInfo.START_TYPE_COLD + && startInfo.getPackageName() != null) { + ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred( + startInfo.getRealUid(), + startInfo.getPackageName(), + ProfilingTrigger.TRIGGER_TYPE_APP_COLD_START_ACTIVITY); + } } }; @@ -11337,7 +11350,9 @@ public class ActivityManagerService extends IActivityManager.Stub } pw.println(" mFgsStartTempAllowList:"); final long currentTimeNow = System.currentTimeMillis(); - final long elapsedRealtimeNow = SystemClock.elapsedRealtime(); + final long tempAllowlistCurrentTime = + com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist() + ? SystemClock.uptimeMillis() : SystemClock.elapsedRealtime(); mFgsStartTempAllowList.forEach((uid, entry) -> { pw.print(" " + UserHandle.formatUid(uid) + ": "); entry.second.dump(pw); @@ -11345,7 +11360,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Convert entry.mExpirationTime, which is an elapsed time since boot, // to a time since epoch (i.e. System.currentTimeMillis()-based time.) final long expirationInCurrentTime = - currentTimeNow - elapsedRealtimeNow + entry.first; + currentTimeNow - tempAllowlistCurrentTime + entry.first; TimeUtils.dumpTimeWithDelta(pw, expirationInCurrentTime, currentTimeNow); pw.println(); }); diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java index 9fc0bf920969..6d594ac38187 100644 --- a/services/core/java/com/android/server/am/AnrHelper.java +++ b/services/core/java/com/android/server/am/AnrHelper.java @@ -20,7 +20,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import android.content.pm.ApplicationInfo; -import android.os.Process; +import android.os.ProfilingServiceHelper; +import android.os.ProfilingTrigger; import android.os.SystemClock; import android.os.Trace; import android.util.ArraySet; @@ -240,6 +241,15 @@ class AnrHelper { || startTime < SELF_ONLY_AFTER_BOOT_MS; r.appNotResponding(onlyDumpSelf); final long endTime = SystemClock.uptimeMillis(); + + if (android.os.profiling.Flags.systemTriggeredProfilingNew() && r.mAppInfo != null + && r.mAppInfo.packageName != null) { + ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred( + r.mUid, + r.mAppInfo.packageName, + ProfilingTrigger.TRIGGER_TYPE_ANR); + } + Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in " + (endTime - startTime) + "ms, latency " + reportLatency + (onlyDumpSelf ? "ms (expired, only dump ANR app)" : "ms")); diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index aca6d0b0b967..3913d2f2ea92 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -22,6 +22,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ApplicationStartInfo; import android.app.Flags; import android.app.IApplicationStartInfoCompleteListener; @@ -419,23 +420,25 @@ public final class AppStartInfoTracker { * Should only be called for Activity launch sequences from an instance of * {@link ActivityMetricsLaunchObserver}. */ - void onActivityReportFullyDrawn(long id, long timestampNanos) { + @Nullable + ApplicationStartInfo onActivityReportFullyDrawn(long id, long timestampNanos) { synchronized (mLock) { if (!mEnabled) { - return; + return null; } int index = mInProgressRecords.indexOfKey(id); if (index < 0) { - return; + return null; } ApplicationStartInfo info = mInProgressRecords.valueAt(index); if (info == null) { mInProgressRecords.removeAt(index); - return; + return null; } info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN, timestampNanos); mInProgressRecords.removeAt(index); + return info; } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 7c563abea22f..23092ed83809 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -453,7 +453,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub com.android.internal.R.integer.config_accumulatedBatteryUsageStatsSpanSize); mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerAttributor, mPowerProfile, mCpuScalingPolicies, - mPowerStatsStore, accumulatedBatteryUsageStatsSpanSize, Clock.SYSTEM_CLOCK); + mPowerStatsStore, accumulatedBatteryUsageStatsSpanSize, Clock.SYSTEM_CLOCK, + mMonotonicClock); mDumpHelper = new BatteryStatsDumpHelperImpl(mBatteryUsageStatsProvider); mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler); mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config")); diff --git a/services/core/java/com/android/server/am/FgsTempAllowList.java b/services/core/java/com/android/server/am/FgsTempAllowList.java index c28655655765..5569b6db3285 100644 --- a/services/core/java/com/android/server/am/FgsTempAllowList.java +++ b/services/core/java/com/android/server/am/FgsTempAllowList.java @@ -29,7 +29,7 @@ import java.util.function.BiConsumer; /** * List of keys that have expiration time. - * If the expiration time is less than current elapsedRealtime, the key has expired. + * If the expiration time is less than current uptime, the key has expired. * Otherwise it is valid (or allowed). * * <p>This is used for both FGS-BG-start restriction, and FGS-while-in-use permissions check.</p> @@ -42,7 +42,7 @@ public class FgsTempAllowList<E> { private static final int DEFAULT_MAX_SIZE = 100; /** - * The value is Pair type, Pair.first is the expirationTime(an elapsedRealtime), + * The value is Pair type, Pair.first is the expirationTime(in cpu uptime), * Pair.second is the optional information entry about this key. */ private final SparseArray<Pair<Long, E>> mTempAllowList = new SparseArray<>(); @@ -82,7 +82,9 @@ public class FgsTempAllowList<E> { } // The temp allowlist should be a short list with only a few entries in it. // for a very large list, HashMap structure should be used. - final long now = SystemClock.elapsedRealtime(); + final long now = com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist() + ? SystemClock.uptimeMillis() + : SystemClock.elapsedRealtime(); final int size = mTempAllowList.size(); if (size > mMaxSize) { Slog.w(TAG_AM, "FgsTempAllowList length:" + size + " exceeds maxSize" @@ -112,12 +114,15 @@ public class FgsTempAllowList<E> { final int index = mTempAllowList.indexOfKey(uid); if (index < 0) { return null; - } else if (mTempAllowList.valueAt(index).first < SystemClock.elapsedRealtime()) { + } + final long timeNow = com.android.server.deviceidle.Flags.useCpuTimeForTempAllowlist() + ? SystemClock.uptimeMillis() + : SystemClock.elapsedRealtime(); + if (mTempAllowList.valueAt(index).first < timeNow) { mTempAllowList.removeAt(index); return null; - } else { - return mTempAllowList.valueAt(index); } + return mTempAllowList.valueAt(index); } } diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java index ae93991d3945..0855815b67a9 100644 --- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java @@ -65,27 +65,31 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { @Override public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) { + boolean changed; if (restricted) { if (!mGlobalRestrictions.containsKey(clientToken)) { mGlobalRestrictions.put(clientToken, new SparseBooleanArray()); } SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken); Objects.requireNonNull(restrictedCodes); - boolean changed = !restrictedCodes.get(code); + changed = !restrictedCodes.get(code); restrictedCodes.put(code, true); - return changed; } else { SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken); if (restrictedCodes == null) { return false; } - boolean changed = restrictedCodes.get(code); + changed = restrictedCodes.get(code); restrictedCodes.delete(code); if (restrictedCodes.size() == 0) { mGlobalRestrictions.remove(clientToken); } - return changed; } + + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } + return changed; } @Override @@ -104,7 +108,11 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { @Override public boolean clearGlobalRestrictions(Object clientToken) { - return mGlobalRestrictions.remove(clientToken) != null; + boolean changed = mGlobalRestrictions.remove(clientToken) != null; + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } + return changed; } @RequiresPermission(anyOf = { @@ -122,6 +130,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { changed |= putUserRestrictionExclusions(clientToken, userIds[i], excludedPackageTags); } + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } return changed; } @@ -191,6 +202,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { changed |= mUserRestrictions.remove(clientToken) != null; changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null; notifyAllUserRestrictions(allUserRestrictedCodes); + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } return changed; } @@ -244,6 +258,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { } } + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } return changed; } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 702ad9541af8..5e74d67905a6 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -998,6 +998,7 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void onUidModeChanged(int uid, int code, int mode, String persistentDeviceId) { + AppOpsManager.invalidateAppOpModeCache(); mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this, code, uid, false, persistentDeviceId)); @@ -1006,6 +1007,7 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void onPackageModeChanged(String packageName, int userId, int code, int mode) { + AppOpsManager.invalidateAppOpModeCache(); mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForPkg, AppOpsService.this, packageName, code, mode, userId)); @@ -1032,6 +1034,11 @@ public class AppOpsService extends IAppOpsService.Stub { // To migrate storageFile to recentAccessesFile, these reads must be called in this order. readRecentAccesses(); mAppOpsCheckingService.readState(); + // The system property used by the cache is created the first time it is written, that only + // happens inside invalidateCache(). Until the service calls invalidateCache() the property + // will not exist and the nonce will be UNSET. + AppOpsManager.invalidateAppOpModeCache(); + AppOpsManager.disableAppOpModeCache(); } public void publish() { @@ -2830,6 +2837,13 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkOperationRaw(int code, int uid, String packageName, @Nullable String attributionTag) { + if (Binder.getCallingPid() != Process.myPid() + && Flags.appopAccessTrackingLoggingEnabled()) { + FrameworkStatsLog.write( + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code, + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION, + false); + } return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag, Context.DEVICE_ID_DEFAULT, true /*raw*/); } @@ -2837,6 +2851,13 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkOperationRawForDevice(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId) { + if (Binder.getCallingPid() != Process.myPid() + && Flags.appopAccessTrackingLoggingEnabled()) { + FrameworkStatsLog.write( + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code, + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION, + false); + } return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag, virtualDeviceId, true /*raw*/); } @@ -2894,8 +2915,14 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_IGNORED; } } - return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, - virtualDeviceId, raw); + + if (Flags.appopModeCachingEnabled()) { + return getAppOpMode(code, uid, resolvedPackageName, attributionTag, virtualDeviceId, + raw, true); + } else { + return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, + virtualDeviceId, raw); + } } /** @@ -2961,6 +2988,54 @@ public class AppOpsService extends IAppOpsService.Stub { } } + /** + * This method unifies mode checking logic between checkOperationUnchecked and + * noteOperationUnchecked. It can replace those two methods once the flag is fully rolled out. + * + * @param isCheckOp This param is only used in user's op restriction. When checking if a package + * can bypass user's restriction we should account for attributionTag as well. + * But existing checkOp APIs don't accept attributionTag so we added a hack to + * skip attributionTag check for checkOp. After we add an overload of checkOp + * that accepts attributionTag we should remove this param. + */ + private @Mode int getAppOpMode(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, int virtualDeviceId, boolean raw, boolean isCheckOp) { + PackageVerificationResult pvr; + try { + pvr = verifyAndGetBypass(uid, packageName, attributionTag); + } catch (SecurityException e) { + logVerifyAndGetBypassFailure(uid, e, "getAppOpMode"); + return MODE_IGNORED; + } + + if (isOpRestrictedDueToSuspend(code, packageName, uid)) { + return MODE_IGNORED; + } + + synchronized (this) { + if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId, + pvr.bypass, isCheckOp)) { + return MODE_IGNORED; + } + if (isOpAllowedForUid(uid)) { + return MODE_ALLOWED; + } + + int switchCode = AppOpsManager.opToSwitch(code); + int rawUidMode = mAppOpsCheckingService.getUidMode(uid, + getPersistentId(virtualDeviceId), switchCode); + + if (rawUidMode != AppOpsManager.opToDefaultMode(switchCode)) { + return raw ? rawUidMode : evaluateForegroundMode(uid, switchCode, rawUidMode); + } + + int rawPackageMode = mAppOpsCheckingService.getPackageMode(packageName, switchCode, + UserHandle.getUserId(uid)); + return raw ? rawPackageMode : evaluateForegroundMode(uid, switchCode, rawPackageMode); + } + } + + @Override public int checkAudioOperation(int code, int usage, int uid, String packageName) { return mCheckOpsDelegateDispatcher.checkAudioOperation(code, usage, uid, packageName); @@ -3213,7 +3288,6 @@ public class AppOpsService extends IAppOpsService.Stub { PackageVerificationResult pvr; try { pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); - boolean wasNull = attributionTag == null; if (!pvr.isAttributionTagValid) { attributionTag = null; } diff --git a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java index d094629cc57b..d7d1ac96d650 100644 --- a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java +++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java @@ -23,10 +23,10 @@ import android.annotation.Nullable; import android.content.ContentResolver; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; -import android.media.AudioManager; import android.media.AudioSystem; import android.os.UserHandle; import android.util.IntArray; +import android.util.Log; import android.util.SparseIntArray; import java.util.HashSet; @@ -48,7 +48,7 @@ import java.util.Set; // A map between device internal type (e.g. AudioSystem.DEVICE_IN_BUILTIN_MIC) to its input gain // index. private final SparseIntArray mInputGainIndexMap; - private final Set<Integer> mSupportedDeviceTypes; + private final Set<Integer> mSupportedDeviceTypes = new HashSet<>(); InputDeviceVolumeHelper( SettingsAdapter settings, @@ -60,20 +60,16 @@ import java.util.Set; IntArray internalDeviceTypes = new IntArray(); int status = AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS, internalDeviceTypes); - mInputGainIndexMap = - new SparseIntArray( - status == AudioManager.SUCCESS - ? internalDeviceTypes.size() - : AudioSystem.DEVICE_IN_ALL_SET.size()); - - if (status == AudioManager.SUCCESS) { - Set<Integer> supportedDeviceTypes = new HashSet<>(); - for (int i = 0; i < internalDeviceTypes.size(); i++) { - supportedDeviceTypes.add(internalDeviceTypes.get(i)); - } - mSupportedDeviceTypes = supportedDeviceTypes; - } else { - mSupportedDeviceTypes = AudioSystem.DEVICE_IN_ALL_SET; + if (status != AudioSystem.SUCCESS) { + Log.e(TAG, "AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS) failed. status:" + + status); + } + + // Note that in a rare case, if AudioSystem.getSupportedDeviceTypes call fails, both + // mInputGainIndexMap and mSupportedDeviceTypes will be empty. + mInputGainIndexMap = new SparseIntArray(internalDeviceTypes.size()); + for (int i = 0; i < internalDeviceTypes.size(); i++) { + mSupportedDeviceTypes.add(internalDeviceTypes.get(i)); } readSettings(); diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index a40dd7919402..c3d88e3e6eb1 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -50,6 +50,7 @@ import java.util.concurrent.ConcurrentHashMap; * * <p>Note, this class is not thread safe so callers must ensure thread safety. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class CompatChange extends CompatibilityChangeInfo { /** diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 79025d00d128..e89f43bd7196 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -42,6 +42,7 @@ import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.OverrideAllowedState; +import com.android.internal.ravenwood.RavenwoodEnvironment; import com.android.server.compat.config.Change; import com.android.server.compat.config.Config; import com.android.server.compat.overrides.ChangeOverrides; @@ -63,6 +64,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; import javax.xml.datatype.DatatypeConfigurationException; @@ -72,12 +74,16 @@ import javax.xml.datatype.DatatypeConfigurationException; * <p>It stores the default configuration for each change, and any per-package overrides that have * been configured. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass final class CompatConfig { private static final String TAG = "CompatConfig"; private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat"; private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat"; private static final String OVERRIDES_FILE = "compat_framework_overrides.xml"; + private static final String APP_COMPAT_DATA_DIR_RAVENWOOD = "/ravenwood-data/"; + private static final String OVERRIDES_FILE_RAVENWOOD = "compat-config.xml"; + private final ConcurrentHashMap<Long, CompatChange> mChanges = new ConcurrentHashMap<>(); private final OverrideValidatorImpl mOverrideValidator; @@ -98,19 +104,32 @@ final class CompatConfig { static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) { CompatConfig config = new CompatConfig(androidBuildClassifier, context); - config.initConfigFromLib(Environment.buildPath( + config.loadConfigFiles(); + config.initOverrides(); + config.invalidateCache(); + return config; + } + + @android.ravenwood.annotation.RavenwoodReplace + private void loadConfigFiles() { + initConfigFromLib(Environment.buildPath( Environment.getRootDirectory(), "etc", "compatconfig")); - config.initConfigFromLib(Environment.buildPath( + initConfigFromLib(Environment.buildPath( Environment.getRootDirectory(), "system_ext", "etc", "compatconfig")); List<ApexManager.ActiveApexInfo> apexes = ApexManager.getInstance().getActiveApexInfos(); for (ApexManager.ActiveApexInfo apex : apexes) { - config.initConfigFromLib(Environment.buildPath( + initConfigFromLib(Environment.buildPath( apex.apexDirectory, "etc", "compatconfig")); } - config.initOverrides(); - config.invalidateCache(); - return config; + } + + @SuppressWarnings("unused") + private void loadConfigFiles$ravenwood() { + final var configDir = new File( + RavenwoodEnvironment.getInstance().getRavenwoodRuntimePath() + + APP_COMPAT_DATA_DIR_RAVENWOOD); + initConfigFromLib(configDir, (file) -> file.getName().endsWith(OVERRIDES_FILE_RAVENWOOD)); } /** @@ -678,12 +697,25 @@ final class CompatConfig { return changeInfos; } + /** + * Load all config files in a given directory. + */ void initConfigFromLib(File libraryDir) { + initConfigFromLib(libraryDir, (file) -> true); + } + + /** + * Load config files in a given directory, but only the ones that match {@code includingFilter}. + */ + void initConfigFromLib(File libraryDir, Predicate<File> includingFilter) { if (!libraryDir.exists() || !libraryDir.isDirectory()) { Slog.d(TAG, "No directory " + libraryDir + ", skipping"); return; } for (File f : libraryDir.listFiles()) { + if (!includingFilter.test(f)) { + continue; + } Slog.d(TAG, "Found a config file: " + f.getPath()); //TODO(b/138222363): Handle duplicate ids across config files. readConfig(f); diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java index e3b6d032b7f0..362c69797161 100644 --- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java +++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java @@ -45,6 +45,7 @@ import com.android.internal.compat.OverrideAllowedState; /** * Implementation of the policy for allowing compat change overrides. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class OverrideValidatorImpl extends IOverrideValidator.Stub { private AndroidBuildClassifier mAndroidBuildClassifier; diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 8d64383b32b9..97f4a5c570b5 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -36,6 +36,7 @@ import android.content.pm.PackageManagerInternal; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.PermissionEnforcer; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -65,6 +66,7 @@ import java.util.Map; /** * System server internal API for gating and reporting compatibility changes. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PlatformCompat extends IPlatformCompat.Stub { private static final String TAG = "Compatibility"; @@ -75,6 +77,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { private final AndroidBuildClassifier mBuildClassifier; public PlatformCompat(Context context) { + super(PermissionEnforcer.fromContext(context)); mContext = context; mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER); mBuildClassifier = new AndroidBuildClassifier(); @@ -85,6 +88,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { PlatformCompat(Context context, CompatConfig compatConfig, AndroidBuildClassifier buildClassifier, ChangeReporter changeReporter) { + super(PermissionEnforcer.fromContext(context)); mContext = context; mChangeReporter = changeReporter; mCompatConfig = compatConfig; @@ -515,6 +519,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { return appInfo; } + @android.ravenwood.annotation.RavenwoodReplace private void killPackage(String packageName) { int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName, 0, UserHandle.myUserId()); @@ -528,6 +533,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { killUid(UserHandle.getAppId(uid)); } + @SuppressWarnings("unused") + private void killPackage$ravenwood(String packageName) { + // TODO Maybe crash if the package is the self. + Slog.w(TAG, "killPackage() is ignored on Ravenwood: packageName=" + packageName); + } + + @android.ravenwood.annotation.RavenwoodReplace private void killUid(int appId) { final long identity = Binder.clearCallingIdentity(); try { @@ -542,6 +554,12 @@ public class PlatformCompat extends IPlatformCompat.Stub { } } + @SuppressWarnings("unused") + private void killUid$ravenwood(int appId) { + // TODO Maybe crash if the UID is the self. + Slog.w(TAG, "killUid() is ignored on Ravenwood: appId=" + appId); + } + private void checkAllCompatOverridesAreOverridable(Collection<Long> changeIds) { for (Long changeId : changeIds) { if (isKnownChangeId(changeId) && !mCompatConfig.isOverridable(changeId)) { diff --git a/services/core/java/com/android/server/compat/PlatformCompatNative.java b/services/core/java/com/android/server/compat/PlatformCompatNative.java index 5d7af650db0b..7a3feb515706 100644 --- a/services/core/java/com/android/server/compat/PlatformCompatNative.java +++ b/services/core/java/com/android/server/compat/PlatformCompatNative.java @@ -23,6 +23,7 @@ import com.android.internal.compat.IPlatformCompatNative; /** * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class PlatformCompatNative extends IPlatformCompatNative.Stub { private final PlatformCompat mPlatformCompat; diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java index e8762a3e935c..0ec68792a886 100644 --- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java +++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java @@ -46,6 +46,7 @@ import java.util.regex.Pattern; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass final class AppCompatOverridesParser { /** * Flag for specifying all compat change IDs owned by a namespace. See {@link diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java index fe002ce00d32..8637d2dfe565 100644 --- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java +++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java @@ -68,6 +68,7 @@ import java.util.Set; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class AppCompatOverridesService { private static final String TAG = "AppCompatOverridesService"; diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 421145390190..dabef84fec31 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -51,6 +51,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemProperties; import android.util.ArrayMap; @@ -64,6 +65,7 @@ import android.view.SurfaceControl; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -323,7 +325,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private int mWidth; private int mHeight; private int mDensityDpi; - private float mRequestedRefreshRate; + private final float mRequestedRefreshRate; private Surface mSurface; private DisplayDeviceInfo mInfo; private int mDisplayState; @@ -332,7 +334,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private Display.Mode mMode; private int mDisplayIdToMirror; private boolean mIsWindowManagerMirroring; - private DisplayCutout mDisplayCutout; + private final DisplayCutout mDisplayCutout; + private final float mDefaultBrightness; + private float mCurrentBrightness; public VirtualDisplayDevice(IBinder displayToken, IBinder appToken, int ownerUid, String ownerPackageName, Surface surface, int flags, @@ -349,6 +353,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mDensityDpi = virtualDisplayConfig.getDensityDpi(); mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate(); mDisplayCutout = virtualDisplayConfig.getDisplayCutout(); + mDefaultBrightness = virtualDisplayConfig.getDefaultBrightness(); + mCurrentBrightness = mDefaultBrightness; mMode = createMode(mWidth, mHeight, getRefreshRate()); mSurface = surface; mFlags = flags; @@ -457,6 +463,12 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mCallback.dispatchDisplayResumed(); } } + if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower() + && BrightnessUtils.isValidBrightnessValue(brightnessState) + && brightnessState != mCurrentBrightness) { + mCurrentBrightness = brightnessState; + mCallback.dispatchRequestedBrightnessChanged(mCurrentBrightness); + } return null; } @@ -623,6 +635,10 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mInfo.state = mDisplayState; } + mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN; + mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX; + mInfo.brightnessDefault = mDefaultBrightness; + mInfo.ownerUid = mOwnerUid; mInfo.ownerPackageName = mOwnerPackageName; @@ -642,6 +658,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { private static final int MSG_ON_DISPLAY_PAUSED = 0; private static final int MSG_ON_DISPLAY_RESUMED = 1; private static final int MSG_ON_DISPLAY_STOPPED = 2; + private static final int MSG_ON_REQUESTED_BRIGHTNESS_CHANGED = 3; private final IVirtualDisplayCallback mCallback; @@ -663,6 +680,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { case MSG_ON_DISPLAY_STOPPED: mCallback.onStopped(); break; + case MSG_ON_REQUESTED_BRIGHTNESS_CHANGED: + mCallback.onRequestedBrightnessChanged((Float) msg.obj); + break; } } catch (RemoteException e) { Slog.w(TAG, "Failed to notify listener of virtual display event.", e); @@ -677,6 +697,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter { sendEmptyMessage(MSG_ON_DISPLAY_RESUMED); } + public void dispatchRequestedBrightnessChanged(float brightness) { + Message msg = obtainMessage(MSG_ON_REQUESTED_BRIGHTNESS_CHANGED, brightness); + sendMessage(msg); + } + public void dispatchDisplayStopped() { sendEmptyMessage(MSG_ON_DISPLAY_STOPPED); } diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index cf1cdaf55e5c..8cb51ce35a89 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -17,6 +17,8 @@ package com.android.server.input; import static android.hardware.input.InputGestureData.createKeyTrigger; + +import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures; import static com.android.hardware.input.Flags.keyboardA11yShortcutControl; import static com.android.server.flags.Flags.newBugreportKeyboardShortcut; import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut; @@ -209,12 +211,12 @@ final class InputGestureManager { KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY )); } - if (keyboardA11yShortcutControl()) { - systemShortcuts.add(createKeyGesture( - KeyEvent.KEYCODE_T, + if (enableTalkbackAndMagnifierKeyGestures()) { + systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T, KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK - )); + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK)); + } + if (keyboardA11yShortcutControl()) { if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) { systemShortcuts.add(createKeyGesture( KeyEvent.KEYCODE_3, @@ -362,6 +364,22 @@ final class InputGestureManager { } @Nullable + public InputGestureData getCustomGestureForTouchpadGesture(@UserIdInt int userId, + int touchpadGestureType) { + if (touchpadGestureType == InputGestureData.TOUCHPAD_GESTURE_TYPE_UNKNOWN) { + return null; + } + synchronized (mGestureLock) { + Map<InputGestureData.Trigger, InputGestureData> customGestures = + mCustomInputGestures.get(userId); + if (customGestures == null) { + return null; + } + return customGestures.get(InputGestureData.createTouchpadTrigger(touchpadGestureType)); + } + } + + @Nullable public InputGestureData getSystemShortcutForKeyEvent(KeyEvent event) { final int keyCode = event.getKeyCode(); if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 78e3b846f9dc..e0f3a9bd427a 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -64,6 +64,7 @@ import android.hardware.input.IKeyboardBacklightListener; import android.hardware.input.IStickyModifierStateListener; import android.hardware.input.ITabletModeChangedListener; import android.hardware.input.InputDeviceIdentifier; +import android.hardware.input.InputGestureData; import android.hardware.input.InputManager; import android.hardware.input.InputSensorInfo; import android.hardware.input.InputSettings; @@ -2314,7 +2315,8 @@ public class InputManagerService extends IInputManager.Stub // Native callback. @SuppressWarnings("unused") private void notifyTouchpadThreeFingerTap() { - mKeyGestureController.handleTouchpadThreeFingerTap(); + mKeyGestureController.handleTouchpadGesture( + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP); } // Native callback. @@ -2995,35 +2997,35 @@ public class InputManagerService extends IInputManager.Stub @Override @PermissionManuallyEnforced - public int addCustomInputGesture(@NonNull AidlInputGestureData inputGestureData) { + public int addCustomInputGesture(@UserIdInt int userId, + @NonNull AidlInputGestureData inputGestureData) { enforceManageKeyGesturePermission(); Objects.requireNonNull(inputGestureData); - return mKeyGestureController.addCustomInputGesture(UserHandle.getCallingUserId(), - inputGestureData); + return mKeyGestureController.addCustomInputGesture(userId, inputGestureData); } @Override @PermissionManuallyEnforced - public int removeCustomInputGesture(@NonNull AidlInputGestureData inputGestureData) { + public int removeCustomInputGesture(@UserIdInt int userId, + @NonNull AidlInputGestureData inputGestureData) { enforceManageKeyGesturePermission(); Objects.requireNonNull(inputGestureData); - return mKeyGestureController.removeCustomInputGesture(UserHandle.getCallingUserId(), - inputGestureData); + return mKeyGestureController.removeCustomInputGesture(userId, inputGestureData); } @Override @PermissionManuallyEnforced - public void removeAllCustomInputGestures() { + public void removeAllCustomInputGestures(@UserIdInt int userId) { enforceManageKeyGesturePermission(); - mKeyGestureController.removeAllCustomInputGestures(UserHandle.getCallingUserId()); + mKeyGestureController.removeAllCustomInputGestures(userId); } @Override - public AidlInputGestureData[] getCustomInputGestures() { - return mKeyGestureController.getCustomInputGestures(UserHandle.getCallingUserId()); + public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId) { + return mKeyGestureController.getCustomInputGestures(userId); } @Override diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index e0991ec20f9e..fc106404049c 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -847,6 +847,13 @@ final class KeyGestureController { /* appLaunchData = */null); } + private void handleTouchpadGesture(@KeyGestureEvent.KeyGestureType int keyGestureType, + @Nullable AppLaunchData appLaunchData) { + handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, new int[0], /* modifierState= */0, + keyGestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, + Display.DEFAULT_DISPLAY, /* focusedToken = */null, /* flags = */0, appLaunchData); + } + @VisibleForTesting boolean handleKeyGesture(int deviceId, int[] keycodes, int modifierState, @KeyGestureEvent.KeyGestureType int gestureType, int action, int displayId, @@ -897,11 +904,18 @@ final class KeyGestureController { handleKeyGesture(event, null /*focusedToken*/); } - public void handleTouchpadThreeFingerTap() { - // TODO(b/365063048): trigger a custom shortcut based on the three-finger tap. - if (DEBUG) { - Slog.d(TAG, "Three-finger touchpad tap occurred"); + public void handleTouchpadGesture(int touchpadGestureType) { + // Handle custom shortcuts + InputGestureData customGesture; + synchronized (mUserLock) { + customGesture = mInputGestureManager.getCustomGestureForTouchpadGesture(mCurrentUserId, + touchpadGestureType); + } + if (customGesture == null) { + return; } + handleTouchpadGesture(customGesture.getAction().keyGestureType(), + customGesture.getAction().appLaunchData()); } @MainThread @@ -1214,6 +1228,7 @@ final class KeyGestureController { public void dump(IndentingPrintWriter ipw) { ipw.println("KeyGestureController:"); ipw.increaseIndent(); + ipw.println("mCurrentUserId = " + mCurrentUserId); ipw.println("mSystemPid = " + mSystemPid); ipw.println("mPendingMetaAction = " + mPendingMetaAction); ipw.println("mPendingCapsLockToggle = " + mPendingCapsLockToggle); diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java index 146ce1732070..46be1ca0eec2 100644 --- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; +import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -59,8 +60,14 @@ final class AdditionalSubtypeUtils { private static final String NODE_SUBTYPE = "subtype"; private static final String NODE_IMI = "imi"; private static final String ATTR_ID = "id"; + /** The resource ID of the subtype name. */ private static final String ATTR_LABEL = "label"; + /** The untranslatable name of the subtype. */ private static final String ATTR_NAME_OVERRIDE = "nameOverride"; + /** The layout label string resource identifier. */ + private static final String ATTR_LAYOUT_LABEL = "layoutLabel"; + /** The non-localized layout label. */ + private static final String ATTR_LAYOUT_LABEL_NON_LOCALIZED = "layoutLabelNonLocalized"; private static final String ATTR_NAME_PK_LANGUAGE_TAG = "pkLanguageTag"; private static final String ATTR_NAME_PK_LAYOUT_TYPE = "pkLayoutType"; private static final String ATTR_ICON = "icon"; @@ -173,6 +180,11 @@ final class AdditionalSubtypeUtils { out.attributeInt(null, ATTR_ICON, subtype.getIconResId()); out.attributeInt(null, ATTR_LABEL, subtype.getNameResId()); out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString()); + if (Flags.imeSwitcherRevampApi()) { + out.attributeInt(null, ATTR_LAYOUT_LABEL, subtype.getLayoutLabelResource()); + out.attribute(null, ATTR_LAYOUT_LABEL_NON_LOCALIZED, + subtype.getLayoutLabelNonLocalized().toString()); + } ULocale pkLanguageTag = subtype.getPhysicalKeyboardHintLanguageTag(); if (pkLanguageTag != null) { out.attribute(null, ATTR_NAME_PK_LANGUAGE_TAG, @@ -264,6 +276,16 @@ final class AdditionalSubtypeUtils { final int label = parser.getAttributeInt(null, ATTR_LABEL); final String untranslatableName = parser.getAttributeValue(null, ATTR_NAME_OVERRIDE); + final int layoutLabelResource; + final String layoutLabelNonLocalized; + if (Flags.imeSwitcherRevampApi()) { + layoutLabelResource = parser.getAttributeInt(null, ATTR_LAYOUT_LABEL); + layoutLabelNonLocalized = parser.getAttributeValue(null, + ATTR_LAYOUT_LABEL_NON_LOCALIZED); + } else { + layoutLabelResource = 0; + layoutLabelNonLocalized = null; + } final String pkLanguageTag = parser.getAttributeValue(null, ATTR_NAME_PK_LANGUAGE_TAG); final String pkLayoutType = parser.getAttributeValue(null, @@ -283,6 +305,7 @@ final class AdditionalSubtypeUtils { final InputMethodSubtype.InputMethodSubtypeBuilder builder = new InputMethodSubtype.InputMethodSubtypeBuilder() .setSubtypeNameResId(label) + .setLayoutLabelResource(layoutLabelResource) .setPhysicalKeyboardHint( pkLanguageTag == null ? null : new ULocale(pkLanguageTag), pkLayoutType == null ? "" : pkLayoutType) @@ -301,6 +324,9 @@ final class AdditionalSubtypeUtils { if (untranslatableName != null) { builder.setSubtypeNameOverride(untranslatableName); } + if (layoutLabelNonLocalized != null) { + builder.setLayoutLabelNonLocalized(layoutLabelNonLocalized); + } tempSubtypesArray.add(builder.build()); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java index 6abd5aabfabf..9f94905686fe 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java @@ -238,8 +238,8 @@ final class InputMethodMenuControllerNew { prevImeId = imeId; } - menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mImi, - item.mSubtypeIndex)); + menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mLayoutName, + item.mImi, item.mSubtypeIndex)); } return menuItems; @@ -348,6 +348,13 @@ final class InputMethodMenuControllerNew { @Nullable final CharSequence mSubtypeName; + /** + * The name of the subtype's layout, or {@code null} if this item doesn't have a subtype, + * or doesn't specify a layout. + */ + @Nullable + private final CharSequence mLayoutName; + /** The info of the input method. */ @NonNull final InputMethodInfo mImi; @@ -360,10 +367,11 @@ final class InputMethodMenuControllerNew { final int mSubtypeIndex; SubtypeItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName, - @NonNull InputMethodInfo imi, + @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex) { mImeName = imeName; mSubtypeName = subtypeName; + mLayoutName = layoutName; mImi = imi; mSubtypeIndex = subtypeIndex; } @@ -521,6 +529,9 @@ final class InputMethodMenuControllerNew { /** The name of the item. */ @NonNull private final TextView mName; + /** The layout name. */ + @NonNull + private final TextView mLayout; /** Indicator for the selected status of the item. */ @NonNull private final ImageView mCheckmark; @@ -536,6 +547,7 @@ final class InputMethodMenuControllerNew { mContainer = itemView; mName = itemView.requireViewById(com.android.internal.R.id.text); + mLayout = itemView.requireViewById(com.android.internal.R.id.text2); mCheckmark = itemView.requireViewById(com.android.internal.R.id.image); mContainer.setOnClickListener((v) -> { @@ -563,6 +575,9 @@ final class InputMethodMenuControllerNew { // Trigger the ellipsize marquee behaviour by selecting the name. mName.setSelected(isSelected); mName.setText(name); + mLayout.setText(item.mLayoutName); + mLayout.setVisibility( + !TextUtils.isEmpty(item.mLayoutName) ? View.VISIBLE : View.GONE); mCheckmark.setVisibility(isSelected ? View.VISIBLE : View.GONE); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 96b3e084d102..51b85e90c447 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -85,6 +85,12 @@ final class InputMethodSubtypeSwitchingController { public final CharSequence mImeName; @Nullable public final CharSequence mSubtypeName; + /** + * The subtype's layout name, or {@code null} if this item doesn't have a subtype, + * or doesn't specify a layout. + */ + @Nullable + public final CharSequence mLayoutName; @NonNull public final InputMethodInfo mImi; /** @@ -96,10 +102,11 @@ final class InputMethodSubtypeSwitchingController { public final boolean mIsSystemLanguage; ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName, - @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale, - @NonNull String systemLocale) { + @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, int subtypeIndex, + @Nullable String subtypeLocale, @NonNull String systemLocale) { mImeName = imeName; mSubtypeName = subtypeName; + mLayoutName = layoutName; mImi = imi; mSubtypeIndex = subtypeIndex; if (TextUtils.isEmpty(subtypeLocale)) { @@ -252,8 +259,11 @@ final class InputMethodSubtypeSwitchingController { subtype.overridesImplicitlyEnabledSubtype() ? null : subtype .getDisplayName(userAwareContext, imi.getPackageName(), imi.getServiceInfo().applicationInfo); - imList.add(new ImeSubtypeListItem(imeLabel, - subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr)); + final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null + : subtype.getLayoutDisplayName(userAwareContext, + imi.getServiceInfo().applicationInfo); + imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName, + imi, j, subtype.getLocale(), mSystemLocaleStr)); // Removing this subtype from enabledSubtypeSet because we no // longer need to add an entry of this subtype to imList to avoid @@ -262,8 +272,8 @@ final class InputMethodSubtypeSwitchingController { } } } else { - imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null, - mSystemLocaleStr)); + imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */, + null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr)); } } Collections.sort(imList); @@ -311,13 +321,16 @@ final class InputMethodSubtypeSwitchingController { subtype.overridesImplicitlyEnabledSubtype() ? null : subtype .getDisplayName(userAwareContext, imi.getPackageName(), imi.getServiceInfo().applicationInfo); - imList.add(new ImeSubtypeListItem(imeLabel, - subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr)); + final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null + : subtype.getLayoutDisplayName(userAwareContext, + imi.getServiceInfo().applicationInfo); + imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName, + imi, j, subtype.getLocale(), mSystemLocaleStr)); } } } else { - imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null, - mSystemLocaleStr)); + imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */, + null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr)); } } return imList; diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 89555a9f1de4..c8a87994ee16 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -161,6 +161,11 @@ public class MediaSession2Record extends MediaSessionRecordImpl { } @Override + public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) { + // NA as MediaSession2 doesn't support UserEngagementStates for FGS. + } + + @Override public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, KeyEvent ke, int sequenceId, ResultReceiver cb) { // TODO(jaewan): Implement. diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index d752429e64f7..668ee2adbd9f 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -230,51 +230,49 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde private final Runnable mUserEngagementTimeoutExpirationRunnable = () -> { synchronized (mLock) { - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ true, + /* isGlobalPrioritySessionActive= */ false); } }; @GuardedBy("mLock") private @UserEngagementState int mUserEngagementState = USER_DISENGAGED; - @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED}) + @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARILY_ENGAGED, USER_DISENGAGED}) @Retention(RetentionPolicy.SOURCE) private @interface UserEngagementState {} /** - * Indicates that the session is active and in one of the user engaged states. + * Indicates that the session is {@linkplain MediaSession#isActive() active} and in one of the + * {@linkplain PlaybackState#isActive() active states}. * * @see #updateUserEngagedStateIfNeededLocked(boolean) */ private static final int USER_PERMANENTLY_ENGAGED = 0; /** - * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state. + * Indicates that the session is {@linkplain MediaSession#isActive() active} and has recently + * switched to one of the {@linkplain PlaybackState#isActive() inactive states}. * * @see #updateUserEngagedStateIfNeededLocked(boolean) */ - private static final int USER_TEMPORARY_ENGAGED = 1; + private static final int USER_TEMPORARILY_ENGAGED = 1; /** - * Indicates that the session is either not active or in one of the user disengaged states + * Indicates that the session is either not {@linkplain MediaSession#isActive() active} or in + * one of the {@linkplain PlaybackState#isActive() inactive states}. * * @see #updateUserEngagedStateIfNeededLocked(boolean) */ private static final int USER_DISENGAGED = 2; /** - * Indicates the duration of the temporary engaged states, in milliseconds. + * Indicates the duration of the temporary engaged state, in milliseconds. * - * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily - * engaged, meaning the corresponding session is only considered in an engaged state for the - * duration of this timeout, and only if coming from an engaged state. - * - * <p>For example, if a session is transitioning from a user-engaged state {@link - * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link - * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for - * the duration of this timeout, starting at the transition instant. However, a temporary - * user-engaged state is not considered user-engaged when transitioning from a non-user engaged - * state {@link PlaybackState#STATE_STOPPED}. + * <p>When switching to an {@linkplain PlaybackState#isActive() inactive state}, the user is + * treated as temporarily engaged, meaning the corresponding session is only considered in an + * engaged state for the duration of this timeout, and only if coming from an engaged state. */ private static final int TEMP_USER_ENGAGED_TIMEOUT_MS = 600000; @@ -598,7 +596,8 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde mSessionCb.mCb.asBinder().unlinkToDeath(this, 0); mDestroyed = true; mPlaybackState = null; - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ true, /* isGlobalPrioritySessionActive= */ false); mHandler.post(MessageHandler.MSG_DESTROYED); } } @@ -615,6 +614,24 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde mHandler.post(mUserEngagementTimeoutExpirationRunnable); } + @Override + public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) { + mHandler.post( + () -> { + synchronized (mLock) { + if (isGlobalPrioritySessionActive) { + mHandler.removeCallbacks(mUserEngagementTimeoutExpirationRunnable); + } else { + if (mUserEngagementState == USER_TEMPORARILY_ENGAGED) { + mHandler.postDelayed( + mUserEngagementTimeoutExpirationRunnable, + TEMP_USER_ENGAGED_TIMEOUT_MS); + } + } + } + }); + } + /** * Sends media button. * @@ -1063,21 +1080,20 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } @GuardedBy("mLock") - private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) { + private void updateUserEngagedStateIfNeededLocked( + boolean isTimeoutExpired, boolean isGlobalPrioritySessionActive) { if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { return; } int oldUserEngagedState = mUserEngagementState; int newUserEngagedState; - if (!isActive() || mPlaybackState == null || mDestroyed) { + if (!isActive() || mPlaybackState == null) { newUserEngagedState = USER_DISENGAGED; - } else if (isActive() && mPlaybackState.isActive()) { + } else if (mPlaybackState.isActive()) { newUserEngagedState = USER_PERMANENTLY_ENGAGED; - } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) { - newUserEngagedState = - oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired - ? USER_TEMPORARY_ENGAGED - : USER_DISENGAGED; + } else if (oldUserEngagedState == USER_PERMANENTLY_ENGAGED + || (oldUserEngagedState == USER_TEMPORARILY_ENGAGED && !isTimeoutExpired)) { + newUserEngagedState = USER_TEMPORARILY_ENGAGED; } else { newUserEngagedState = USER_DISENGAGED; } @@ -1086,7 +1102,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } mUserEngagementState = newUserEngagedState; - if (newUserEngagedState == USER_TEMPORARY_ENGAGED) { + if (newUserEngagedState == USER_TEMPORARILY_ENGAGED && !isGlobalPrioritySessionActive) { mHandler.postDelayed( mUserEngagementTimeoutExpirationRunnable, TEMP_USER_ENGAGED_TIMEOUT_MS); } else { @@ -1141,9 +1157,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK, callingUid, callingPid); } + boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive(); synchronized (mLock) { mIsActive = active; - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive); } long token = Binder.clearCallingIdentity(); try { @@ -1300,9 +1318,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState) || (!TRANSITION_PRIORITY_STATES.contains(oldState) && TRANSITION_PRIORITY_STATES.contains(newState)); + boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive(); synchronized (mLock) { mPlaybackState = state; - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive); } final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java index 15f90d4fdd0e..6c3b1234935a 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -206,6 +206,10 @@ public abstract class MediaSessionRecordImpl { */ public abstract void expireTempEngaged(); + /** Notifies record that the global priority session active state changed. */ + public abstract void onGlobalPrioritySessionActiveChanged( + boolean isGlobalPrioritySessionActive); + @Override public final boolean equals(Object o) { if (this == o) return true; diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 1ebc856af2d8..2b29fbd9c5b5 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -362,6 +362,7 @@ public class MediaSessionService extends SystemService implements Monitor { + record.isActive()); } user.pushAddressedPlayerChangedLocked(); + mHandler.post(this::notifyGlobalPrioritySessionActiveChanged); } else { if (!user.mPriorityStack.contains(record)) { Log.w(TAG, "Unknown session updated. Ignoring."); @@ -394,11 +395,16 @@ public class MediaSessionService extends SystemService implements Monitor { // Currently only media1 can become global priority session. void setGlobalPrioritySession(MediaSessionRecord record) { + boolean globalPrioritySessionActiveChanged = false; synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(record.getUserId()); if (mGlobalPrioritySession != record) { Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession + " to " + record); + globalPrioritySessionActiveChanged = + (mGlobalPrioritySession == null && record.isActive()) + || (mGlobalPrioritySession != null + && mGlobalPrioritySession.isActive() != record.isActive()); mGlobalPrioritySession = record; if (user != null && user.mPriorityStack.contains(record)) { // Handle the global priority session separately. @@ -409,6 +415,30 @@ public class MediaSessionService extends SystemService implements Monitor { } } } + if (globalPrioritySessionActiveChanged) { + mHandler.post(this::notifyGlobalPrioritySessionActiveChanged); + } + } + + /** Returns whether the global priority session is active. */ + boolean isGlobalPrioritySessionActive() { + synchronized (mLock) { + return isGlobalPriorityActiveLocked(); + } + } + + private void notifyGlobalPrioritySessionActiveChanged() { + if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { + return; + } + synchronized (mLock) { + boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked(); + for (Set<MediaSessionRecordImpl> records : mUserEngagedSessionsForFgs.values()) { + for (MediaSessionRecordImpl record : records) { + record.onGlobalPrioritySessionActiveChanged(isGlobalPriorityActive); + } + } + } } private List<MediaSessionRecord> getActiveSessionsLocked(int userId) { @@ -646,8 +676,11 @@ public class MediaSessionService extends SystemService implements Monitor { if (mGlobalPrioritySession == session) { mGlobalPrioritySession = null; - if (session.isActive() && user != null) { - user.pushAddressedPlayerChangedLocked(); + if (session.isActive()) { + if (user != null) { + user.pushAddressedPlayerChangedLocked(); + } + mHandler.post(this::notifyGlobalPrioritySessionActiveChanged); } } else { if (user != null) { diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java index 6681e36e00ee..5febd5c07e3a 100644 --- a/services/core/java/com/android/server/notification/GroupHelper.java +++ b/services/core/java/com/android/server/notification/GroupHelper.java @@ -25,8 +25,10 @@ import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.VISIBILITY_PRIVATE; import static android.app.Notification.VISIBILITY_PUBLIC; import static android.service.notification.Flags.notificationForceGrouping; +import static android.service.notification.Flags.notificationRegroupOnClassification; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -49,6 +51,9 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -83,10 +88,22 @@ public class GroupHelper { // with less than this value, they will be forced grouped private static final int MIN_CHILD_COUNT_TO_AVOID_FORCE_GROUPING = 3; + // Regrouping needed because the channel was updated, ie. importance changed + static final int REGROUP_REASON_CHANNEL_UPDATE = 0; + // Regrouping needed because of notification bundling + static final int REGROUP_REASON_BUNDLE = 1; + + @IntDef(prefix = { "REGROUP_REASON_" }, value = { + REGROUP_REASON_CHANNEL_UPDATE, + REGROUP_REASON_BUNDLE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RegroupingReason {} private final Callback mCallback; private final int mAutoGroupAtCount; private final int mAutogroupSparseGroupsAtCount; + private final int mAutoGroupRegroupingAtCount; private final Context mContext; private final PackageManager mPackageManager; private boolean mIsTestHarnessExempted; @@ -173,6 +190,11 @@ public class GroupHelper { mContext = context; mPackageManager = packageManager; mAutogroupSparseGroupsAtCount = autoGroupSparseGroupsAtCount; + if (notificationRegroupOnClassification()) { + mAutoGroupRegroupingAtCount = 1; + } else { + mAutoGroupRegroupingAtCount = mAutoGroupAtCount; + } NOTIFICATION_SHADE_SECTIONS = getNotificationShadeSections(); } @@ -865,7 +887,8 @@ public class GroupHelper { } } - regroupNotifications(userId, pkgName, notificationsToCheck); + regroupNotifications(userId, pkgName, notificationsToCheck, + REGROUP_REASON_CHANNEL_UPDATE); } } @@ -883,13 +906,14 @@ public class GroupHelper { ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>(); notificationsToCheck.put(record.getKey(), record); regroupNotifications(record.getUserId(), record.getSbn().getPackageName(), - notificationsToCheck); + notificationsToCheck, REGROUP_REASON_BUNDLE); } } @GuardedBy("mAggregatedNotifications") private void regroupNotifications(int userId, String pkgName, - ArrayMap<String, NotificationRecord> notificationsToCheck) { + ArrayMap<String, NotificationRecord> notificationsToCheck, + @RegroupingReason int regroupingReason) { // The list of notification operations required after the channel update final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>(); @@ -904,12 +928,42 @@ public class GroupHelper { notificationsToMove.addAll( getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck)); + // Handle "grouped correctly" notifications that were re-classified (bundled) + if (notificationRegroupOnClassification()) { + if (regroupingReason == REGROUP_REASON_BUNDLE) { + notificationsToMove.addAll( + getReclassifiedNotificationsMoveOps(userId, pkgName, notificationsToCheck)); + } + } + // Batch move to new section if (!notificationsToMove.isEmpty()) { moveNotificationsToNewSection(userId, pkgName, notificationsToMove); } } + private List<NotificationMoveOp> getReclassifiedNotificationsMoveOps(int userId, + String pkgName, ArrayMap<String, NotificationRecord> notificationsToCheck) { + final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>(); + for (NotificationRecord record : notificationsToCheck.values()) { + if (isChildOfValidAppGroup(record)) { + // Check if section changes + NotificationSectioner sectioner = getSection(record); + if (sectioner != null) { + FullyQualifiedGroupKey newFullAggregateGroupKey = + new FullyQualifiedGroupKey(userId, pkgName, sectioner); + if (DEBUG) { + Slog.v(TAG, "Regroup after classification: " + record + " to: " + + newFullAggregateGroupKey); + } + notificationsToMove.add( + new NotificationMoveOp(record, null, newFullAggregateGroupKey)); + } + } + } + return notificationsToMove; + } + @GuardedBy("mAggregatedNotifications") private List<NotificationMoveOp> getAutogroupedNotificationsMoveOps(int userId, String pkgName, ArrayMap<String, NotificationRecord> notificationsToCheck) { @@ -1010,6 +1064,10 @@ public class GroupHelper { // Bundled operations to apply to groups affected by the channel update ArrayMap<FullyQualifiedGroupKey, GroupUpdateOp> groupsToUpdate = new ArrayMap<>(); + // App-provided (valid) groups of notifications that were classified (bundled). + // Summaries will be canceled if all child notifications have been bundled. + ArrayMap<String, String> originalGroupsOfBundledNotifications = new ArrayMap<>(); + for (NotificationMoveOp moveOp: notificationsToMove) { final NotificationRecord record = moveOp.record; final FullyQualifiedGroupKey oldFullAggregateGroupKey = moveOp.oldGroup; @@ -1035,6 +1093,13 @@ public class GroupHelper { groupsToUpdate.put(oldFullAggregateGroupKey, new GroupUpdateOp(oldFullAggregateGroupKey, record, true)); } + } else { + if (notificationRegroupOnClassification()) { + // Null "old aggregate group" => this notification was re-classified from + // a valid app-provided group => maybe cancel the original summary + // if no children are left + originalGroupsOfBundledNotifications.put(record.getKey(), record.getGroupKey()); + } } // Add moved notifications to the ungrouped list for new group and do grouping @@ -1076,7 +1141,7 @@ public class GroupHelper { NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record; boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary; //Group needs to be created/updated - if (ungrouped.size() >= mAutoGroupAtCount + if (ungrouped.size() >= mAutoGroupRegroupingAtCount || (hasSummary && !aggregatedNotificationsAttrs.isEmpty())) { NotificationSectioner sectioner = getSection(triggeringNotification); if (sectioner == null) { @@ -1092,6 +1157,18 @@ public class GroupHelper { } } } + + if (notificationRegroupOnClassification()) { + // Cancel the summary if it's the last notification of the original app-provided group + for (String triggeringKey : originalGroupsOfBundledNotifications.keySet()) { + NotificationRecord canceledSummary = + mCallback.removeAppProvidedSummaryOnClassification(triggeringKey, + originalGroupsOfBundledNotifications.getOrDefault(triggeringKey, null)); + if (canceledSummary != null) { + cacheCanceledSummary(canceledSummary); + } + } + } } static String getFullAggregateGroupKey(String pkgName, @@ -1113,6 +1190,42 @@ public class GroupHelper { return (record.mOriginalFlags & Notification.FLAG_AUTOGROUP_SUMMARY) != 0; } + private boolean isNotificationAggregatedInSection(NotificationRecord record, + NotificationSectioner sectioner) { + final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey( + record.getUserId(), record.getSbn().getPackageName(), sectioner); + return record.getGroupKey().equals(fullAggregateGroupKey.toString()); + } + + private boolean isChildOfValidAppGroup(NotificationRecord record) { + final StatusBarNotification sbn = record.getSbn(); + if (!sbn.isAppGroup()) { + return false; + } + + if (!sbn.getNotification().isGroupChild()) { + return false; + } + + if (record.isCanceled) { + return false; + } + + final NotificationSectioner sectioner = getSection(record); + if (sectioner == null) { + if (DEBUG) { + Slog.i(TAG, "Skipping autogrouping for " + record + " no valid section found."); + } + return false; + } + + if (isNotificationAggregatedInSection(record, sectioner)) { + return false; + } + + return true; + } + private static int getNumChildrenForGroup(@NonNull final String groupKey, final List<NotificationRecord> notificationList) { //TODO (b/349072751): track grouping state in GroupHelper -> do not use notificationList @@ -1438,6 +1551,48 @@ public class GroupHelper { } } + protected void dump(PrintWriter pw, String prefix) { + synchronized (mAggregatedNotifications) { + if (!mUngroupedAbuseNotifications.isEmpty()) { + pw.println(prefix + "Ungrouped notifications:"); + for (FullyQualifiedGroupKey groupKey: mUngroupedAbuseNotifications.keySet()) { + if (!mUngroupedAbuseNotifications.getOrDefault(groupKey, new ArrayMap<>()) + .isEmpty()) { + pw.println(prefix + prefix + groupKey.toString()); + for (String notifKey : mUngroupedAbuseNotifications.get(groupKey) + .keySet()) { + pw.println(prefix + prefix + prefix + notifKey); + } + } + } + pw.println(""); + } + + if (!mAggregatedNotifications.isEmpty()) { + pw.println(prefix + "Autogrouped notifications:"); + for (FullyQualifiedGroupKey groupKey: mAggregatedNotifications.keySet()) { + if (!mAggregatedNotifications.getOrDefault(groupKey, new ArrayMap<>()) + .isEmpty()) { + pw.println(prefix + prefix + groupKey.toString()); + for (String notifKey : mAggregatedNotifications.get(groupKey).keySet()) { + pw.println(prefix + prefix + prefix + notifKey); + } + } + } + pw.println(""); + } + + if (!mCanceledSummaries.isEmpty()) { + pw.println(prefix + "Cached canceled summaries:"); + for (CachedSummary summary: mCanceledSummaries.values()) { + pw.println(prefix + prefix + prefix + summary.key + " -> " + + summary.originalGroupKey); + } + pw.println(""); + } + } + } + protected static class NotificationSectioner { final String mName; final int mSummaryId; @@ -1551,5 +1706,16 @@ public class GroupHelper { void removeNotificationFromCanceledGroup(int userId, String pkg, String groupKey, int cancelReason); + + /** + * Cancels the group summary of a notification that was regrouped because of classification + * (bundling). Only cancels if the summary is the last notification of the original group. + * @param triggeringKey the triggering child notification key + * @param groupKey the original group key + * @return the canceled group summary or null if the summary was not canceled + */ + @Nullable + NotificationRecord removeAppProvidedSummaryOnClassification(String triggeringKey, + @Nullable String groupKey); } } diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 122836e19d58..93482e769a2b 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -21,9 +21,6 @@ import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT; import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; import static android.content.Context.DEVICE_POLICY_SERVICE; -import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; -import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; -import static android.content.pm.PackageManager.MATCH_INSTANT; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND; @@ -109,8 +106,7 @@ abstract public class ManagedServices { protected final String TAG = getClass().getSimpleName().replace('$', '.'); protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - protected static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000; - protected static final int ON_BINDING_DIED_REBIND_MSG = 1234; + private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000; protected static final String ENABLED_SERVICES_SEPARATOR = ":"; private static final String DB_VERSION_1 = "1"; private static final String DB_VERSION_2 = "2"; @@ -879,21 +875,7 @@ abstract public class ManagedServices { String approvedItem = getApprovedValue(pkgOrComponent); if (approvedItem != null) { - final ComponentName component = ComponentName.unflattenFromString(approvedItem); if (enabled) { - if (Flags.notificationNlsRebind()) { - if (component != null && !isValidService(component, userId)) { - // Only fail if package is available - // If not, it will be validated again in onPackagesChanged - final PackageManager pm = mContext.getPackageManager(); - if (pm.isPackageAvailable(component.getPackageName())) { - Slog.w(TAG, "Skip allowing " + mConfig.caption - + " " + pkgOrComponent + " (userSet: " + userSet - + ") for invalid service"); - return; - } - } - } approved.add(approvedItem); } else { approved.remove(approvedItem); @@ -991,7 +973,7 @@ abstract public class ManagedServices { || isPackageOrComponentAllowed(component.getPackageName(), userId))) { return false; } - return isValidService(component, userId); + return componentHasBindPermission(component, userId); } private boolean componentHasBindPermission(ComponentName component, int userId) { @@ -1238,21 +1220,12 @@ abstract public class ManagedServices { if (!TextUtils.isEmpty(packageName)) { queryIntent.setPackage(packageName); } - - if (Flags.notificationNlsRebind()) { - // Expand the package query - extraFlags |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; - extraFlags |= MATCH_INSTANT; - } - List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser( queryIntent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA | extraFlags, userId); - if (DEBUG) { - Slog.v(TAG, mConfig.serviceInterface + " pkg: " + packageName + " services: " - + installedServices); - } + if (DEBUG) + Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices); if (installedServices != null) { for (int i = 0, count = installedServices.size(); i < count; i++) { ResolveInfo resolveInfo = installedServices.get(i); @@ -1352,12 +1325,11 @@ abstract public class ManagedServices { if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) { final ComponentName component = ComponentName.unflattenFromString( approvedPackageOrComponent); - if (component != null && !isValidService(component, userId)) { + if (component != null && !componentHasBindPermission(component, userId)) { approved.removeAt(j); if (DEBUG) { Slog.v(TAG, "Removing " + approvedPackageOrComponent - + " from approved list; no bind permission or " - + "service interface filter found " + + " from approved list; no bind permission found " + mConfig.bindPermission); } } @@ -1376,15 +1348,6 @@ abstract public class ManagedServices { } } - protected boolean isValidService(ComponentName component, int userId) { - if (Flags.notificationNlsRebind()) { - return componentHasBindPermission(component, userId) && queryPackageForServices( - component.getPackageName(), userId).contains(component); - } else { - return componentHasBindPermission(component, userId); - } - } - protected boolean isValidEntry(String packageOrComponent, int userId) { return hasMatchingServices(packageOrComponent, userId); } @@ -1542,27 +1505,23 @@ abstract public class ManagedServices { * Called when user switched to unbind all services from other users. */ @VisibleForTesting - void unbindOtherUserServices(int switchedToUser) { + void unbindOtherUserServices(int currentUser) { TimingsTraceAndSlog t = new TimingsTraceAndSlog(); - t.traceBegin("ManagedServices.unbindOtherUserServices_current" + switchedToUser); - unbindServicesImpl(switchedToUser, true /* allExceptUser */); + t.traceBegin("ManagedServices.unbindOtherUserServices_current" + currentUser); + unbindServicesImpl(currentUser, true /* allExceptUser */); t.traceEnd(); } - void unbindUserServices(int removedUser) { + void unbindUserServices(int user) { TimingsTraceAndSlog t = new TimingsTraceAndSlog(); - t.traceBegin("ManagedServices.unbindUserServices" + removedUser); - unbindServicesImpl(removedUser, false /* allExceptUser */); + t.traceBegin("ManagedServices.unbindUserServices" + user); + unbindServicesImpl(user, false /* allExceptUser */); t.traceEnd(); } void unbindServicesImpl(int user, boolean allExceptUser) { final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>(); synchronized (mMutex) { - if (Flags.notificationNlsRebind()) { - // Remove enqueued rebinds to avoid rebinding services for a switched user - mHandler.removeMessages(ON_BINDING_DIED_REBIND_MSG); - } final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices(); for (ManagedServiceInfo info : removableBoundServices) { if ((allExceptUser && (info.userid != user)) @@ -1757,7 +1716,6 @@ abstract public class ManagedServices { mServicesRebinding.add(servicesBindingTag); mHandler.postDelayed(() -> reregisterService(name, userid), - ON_BINDING_DIED_REBIND_MSG, ON_BINDING_DIED_REBIND_DELAY_MS); } else { Slog.v(TAG, getCaption() + " not rebinding in user " + userid diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2c45fc89ff0b..4d0c7ec64317 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -82,6 +82,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BA import static android.app.NotificationManager.zenModeFromInterruptionFilter; import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; +import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG; +import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING; import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT; import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; @@ -162,8 +164,6 @@ import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; -import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG; -import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING; import static com.android.server.notification.Flags.expireBitmaps; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT; @@ -474,6 +474,10 @@ public class NotificationManagerService extends SystemService { Adjustment.KEY_TYPE }; + static final Integer[] DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES = new Integer[] { + TYPE_PROMOTION + }; + static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] { RoleManager.ROLE_DIALER, RoleManager.ROLE_EMERGENCY @@ -1929,6 +1933,12 @@ public class NotificationManagerService extends SystemService { hasSensitiveContent, lifespanMs); } + protected void logClassificationChannelAdjustmentReceived(boolean hasPosted, boolean isAlerting, + int classification, int lifespanMs) { + FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_CHANNEL_CLASSIFICATION, + hasPosted, isAlerting, classification, lifespanMs); + } + protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -2998,6 +3008,16 @@ public class NotificationManagerService extends SystemService { groupKey, REASON_APP_CANCEL, SystemClock.elapsedRealtime()); } } + + @Override + @Nullable + public NotificationRecord removeAppProvidedSummaryOnClassification(String triggeringKey, + @Nullable String oldGroupKey) { + synchronized (mNotificationLock) { + return removeAppProvidedSummaryOnClassificationLocked(triggeringKey, + oldGroupKey); + } + } }); } @@ -4189,6 +4209,22 @@ public class NotificationManagerService extends SystemService { } @Override + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public @NonNull int[] getAllowedAdjustmentKeyTypes() { + checkCallerIsSystemOrSystemUiOrShell(); + return mAssistants.getAllowedAdjustmentKeyTypes(); + } + + @Override + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public void setAssistantAdjustmentKeyTypeState(int type, boolean enabled) { + checkCallerIsSystemOrSystemUiOrShell(); + mAssistants.setAssistantAdjustmentKeyTypeState(type, enabled); + + handleSavePolicyFile(); + } + + @Override @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING) public boolean appCanBePromoted(String pkg, int uid) { checkCallerIsSystemOrSystemUiOrShell(); @@ -6977,19 +7013,30 @@ public class NotificationManagerService extends SystemService { if (!mAssistants.isAdjustmentAllowed(potentialKey)) { toRemove.add(potentialKey); } + if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) { + if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) { + toRemove.add(potentialKey); + } + } } for (String removeKey : toRemove) { adjustments.remove(removeKey); } - if (android.service.notification.Flags.notificationClassification() - && adjustments.containsKey(KEY_TYPE)) { + if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) { final NotificationChannel newChannel = getClassificationChannelLocked(r, adjustments); if (newChannel == null || newChannel.getId().equals(r.getChannel().getId())) { adjustments.remove(KEY_TYPE); } else { + // Save the app-provided type for logging. + int classification = adjustments.getInt(KEY_TYPE); // swap app provided type with the real thing adjustments.putParcelable(KEY_TYPE, newChannel); + // Note that this value of isAlerting does not fully indicate whether a notif + // would make a sound or HUN on device; it is an approximation for metrics. + boolean isAlerting = r.getChannel().getImportance() >= IMPORTANCE_DEFAULT; + logClassificationChannelAdjustmentReceived(isPosted, isAlerting, classification, + r.getLifespanMs(System.currentTimeMillis())); } } r.addAdjustment(adjustment); @@ -7114,6 +7161,50 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") + @Nullable + NotificationRecord removeAppProvidedSummaryOnClassificationLocked(String triggeringKey, + @Nullable String oldGroupKey) { + NotificationRecord canceledSummary = null; + NotificationRecord r = mNotificationsByKey.get(triggeringKey); + if (r == null || oldGroupKey == null) { + return null; + } + + if (r.getSbn().isAppGroup() && r.getNotification().isGroupChild()) { + NotificationRecord groupSummary = mSummaryByGroupKey.get(oldGroupKey); + // We only care about app-provided valid groups + if (groupSummary != null && !GroupHelper.isAggregatedGroup(groupSummary)) { + List<NotificationRecord> notificationsInGroup = + findGroupNotificationsLocked(r.getSbn().getPackageName(), + oldGroupKey, r.getUserId()); + // Remove the app-provided summary if only the summary is left in the + // original group, or summary + triggering notification that will be + // regrouped + boolean isOnlySummaryLeft = + (notificationsInGroup.size() <= 1) + || (notificationsInGroup.size() == 2 + && notificationsInGroup.contains(r) + && notificationsInGroup.contains(groupSummary)); + if (isOnlySummaryLeft) { + if (DBG) { + Slog.i(TAG, "Removing app summary (all children bundled): " + + groupSummary); + } + canceledSummary = groupSummary; + mSummaryByGroupKey.remove(oldGroupKey); + cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), + groupSummary.getSbn().getPackageName(), + groupSummary.getSbn().getTag(), + groupSummary.getSbn().getId(), 0, 0, false, groupSummary.getUserId(), + NotificationListenerService.REASON_GROUP_OPTIMIZATION, null); + } + } + } + + return canceledSummary; + } + + @GuardedBy("mNotificationLock") private boolean hasAutoGroupSummaryLocked(NotificationRecord record) { final String autbundledGroupKey; if (notificationForceGrouping()) { @@ -7493,6 +7584,11 @@ public class NotificationManagerService extends SystemService { mTtlHelper.dump(pw, " "); } } + + if (notificationForceGrouping()) { + pw.println("\n GroupHelper:"); + mGroupHelper.dump(pw, " "); + } } } @@ -7770,10 +7866,11 @@ public class NotificationManagerService extends SystemService { // Make Notification silent r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; - // Repost + // Repost as the original app (even if it was posted by a delegate originally + // because the delegate may now be revoked) enqueueNotificationInternal(r.getSbn().getPackageName(), - r.getSbn().getOpPkg(), r.getSbn().getUid(), - r.getSbn().getInitialPid(), r.getSbn().getTag(), + r.getSbn().getPackageName(), r.getSbn().getUid(), + MY_PID, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId(), /* postSilently= */ true, /* byForegroundService= */ false, @@ -8012,7 +8109,6 @@ public class NotificationManagerService extends SystemService { r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg)); boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId); r.setImportanceFixed(isImportanceFixed); - if (notification.isFgsOrUij()) { if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0 || !channel.isUserVisibleTaskShown()) @@ -11552,11 +11648,15 @@ public class NotificationManagerService extends SystemService { private static final String ATT_TYPES = "types"; private static final String ATT_DENIED = "denied_adjustments"; + private static final String ATT_ENABLED_TYPES = "enabled_key_types"; private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments"; private final Object mLock = new Object(); @GuardedBy("mLock") + private Set<Integer> mAllowedAdjustmentKeyTypes = new ArraySet<>(); + + @GuardedBy("mLock") private Set<String> mAllowedAdjustments = new ArraySet<>(); @GuardedBy("mLock") @@ -11639,6 +11739,8 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < DEFAULT_ALLOWED_ADJUSTMENTS.length; i++) { mAllowedAdjustments.add(DEFAULT_ALLOWED_ADJUSTMENTS[i]); } + } else { + mAllowedAdjustmentKeyTypes.addAll(List.of(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES)); } } @@ -11726,6 +11828,42 @@ public class NotificationManagerService extends SystemService { } } + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + protected @NonNull boolean isAdjustmentKeyTypeAllowed(@Adjustment.Types int type) { + synchronized (mLock) { + if (notificationClassification()) { + return mAllowedAdjustmentKeyTypes.contains(type); + } + } + return false; + } + + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + protected @NonNull int[] getAllowedAdjustmentKeyTypes() { + synchronized (mLock) { + if (notificationClassification()) { + return mAllowedAdjustmentKeyTypes.stream() + .mapToInt(Integer::intValue).toArray(); + } + } + return new int[]{}; + } + + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public void setAssistantAdjustmentKeyTypeState(@Adjustment.Types int type, + boolean enabled) { + if (!android.service.notification.Flags.notificationClassification()) { + return; + } + synchronized (mLock) { + if (enabled) { + mAllowedAdjustmentKeyTypes.add(type); + } else { + mAllowedAdjustmentKeyTypes.remove(type); + } + } + } + protected void onNotificationsSeenLocked(ArrayList<NotificationRecord> records) { for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { ArrayList<String> keys = new ArrayList<>(records.size()); @@ -12165,27 +12303,46 @@ public class NotificationManagerService extends SystemService { @Override protected void writeExtraXmlTags(TypedXmlSerializer out) throws IOException { - if (!android.service.notification.Flags.notificationClassification()) { + if (!notificationClassification()) { return; } synchronized (mLock) { out.startTag(null, ATT_DENIED); out.attribute(null, ATT_TYPES, TextUtils.join(",", mDeniedAdjustments)); out.endTag(null, ATT_DENIED); + out.startTag(null, ATT_ENABLED_TYPES); + out.attribute(null, ATT_TYPES, + TextUtils.join(",", mAllowedAdjustmentKeyTypes)); + out.endTag(null, ATT_ENABLED_TYPES); } } @Override protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException { - if (!android.service.notification.Flags.notificationClassification()) { + if (!notificationClassification()) { return; } if (ATT_DENIED.equals(tag)) { - final String types = XmlUtils.readStringAttribute(parser, ATT_TYPES); + final String keys = XmlUtils.readStringAttribute(parser, ATT_TYPES); synchronized (mLock) { mDeniedAdjustments.clear(); + if (!TextUtils.isEmpty(keys)) { + mDeniedAdjustments.addAll(Arrays.asList(keys.split(","))); + } + } + } else if (ATT_ENABLED_TYPES.equals(tag)) { + final String types = XmlUtils.readStringAttribute(parser, ATT_TYPES); + synchronized (mLock) { + mAllowedAdjustmentKeyTypes.clear(); if (!TextUtils.isEmpty(types)) { - mDeniedAdjustments.addAll(Arrays.asList(types.split(","))); + List<String> typeList = Arrays.asList(types.split(",")); + for (String type : typeList) { + try { + mAllowedAdjustmentKeyTypes.add(Integer.parseInt(type)); + } catch (NumberFormatException e) { + Slog.wtf(TAG, "Bad type specified", e); + } + } } } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index d5f13a8ff495..cfeacdf2bb0d 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -2422,7 +2422,7 @@ public class ZenModeHelper { || (mSuppressedEffects & SUPPRESSED_EFFECT_NOTIFICATIONS) != 0; // call restrictions final boolean muteCalls = zenAlarmsOnly - || (zenPriorityOnly && !(allowCalls || allowRepeatCallers)) + || (zenPriorityOnly && (!allowCalls || !allowRepeatCallers)) || (mSuppressedEffects & SUPPRESSED_EFFECT_CALLS) != 0; // alarm restrictions final boolean muteAlarms = zenPriorityOnly && !allowAlarms; diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index c479acfd6228..f79d9ef174ea 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -194,13 +194,3 @@ flag { description: "Enables sound uri with vibration source in notification channel" bug: "351975435" } - -flag { - name: "notification_nls_rebind" - namespace: "systemui" - description: "Check for NLS service intent filter when rebinding services" - bug: "347674739" - metadata { - purpose: PURPOSE_BUGFIX - } -} diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index 015b7fd74211..38f39393a025 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -19,6 +19,7 @@ package com.android.server.om; import android.annotation.NonNull; import android.content.om.OverlayInfo; import android.content.om.OverlayableInfo; +import android.content.res.Flags; import android.net.Uri; import android.os.Process; import android.text.TextUtils; @@ -162,11 +163,15 @@ public class OverlayActorEnforcer { return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE; } - if (targetOverlayable == null) { + // Framework doesn't have <overlayable> declaration by design, and we still want to be able + // to enable its overlays from the packages with the permission. + if (targetOverlayable == null + && !(Flags.rroControlForAndroidNoOverlayable() && targetPackageName.equals( + "android"))) { return ActorState.MISSING_OVERLAYABLE; } - String actor = targetOverlayable.actor; + final String actor = targetOverlayable == null ? null : targetOverlayable.actor; if (TextUtils.isEmpty(actor)) { // If there's no actor defined, fallback to the legacy permission check try { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 5653da07779b..2c0942337b1f 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -88,6 +88,7 @@ import android.content.pm.ShortcutServiceInternal; import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; import android.content.pm.UserInfo; import android.content.pm.UserProperties; +import android.database.ContentObserver; import android.graphics.Rect; import android.multiuser.Flags; import android.net.Uri; @@ -95,6 +96,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IInterface; +import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteCallbackList; @@ -249,6 +251,7 @@ public class LauncherAppsService extends SystemService { private PackageInstallerService mPackageInstallerService; final LauncherAppsServiceInternal mInternal; + private SecureSettingsObserver mSecureSettingsObserver; @NonNull private final RemoteCallbackList<IDumpCallback> mDumpCallbacks = @@ -278,6 +281,7 @@ public class LauncherAppsService extends SystemService { mCallbackHandler = BackgroundThread.getHandler(); mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); mInternal = new LocalService(); + registerSettingsObserver(); } @VisibleForTesting @@ -2312,6 +2316,13 @@ public class LauncherAppsService extends SystemService { } } + void registerSettingsObserver() { + if (Flags.addLauncherUserConfig()) { + mSecureSettingsObserver = new SecureSettingsObserver(); + mSecureSettingsObserver.register(); + } + } + public static class ShortcutChangeHandler implements LauncherApps.ShortcutChangeCallback { private final UserManagerInternal mUserManagerInternal; @@ -2837,5 +2848,84 @@ public class LauncherAppsService extends SystemService { shortcutId, sourceBounds, startActivityOptions, targetUserId); } } + + class SecureSettingsObserver extends ContentObserver { + + SecureSettingsObserver() { + super(new Handler(Looper.getMainLooper())); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + if (uri.equals( + Settings.Secure.getUriFor(Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT))) { + + // This setting key only apply to private profile at the moment + UserHandle privateProfile = getPrivateProfile(); + if (privateProfile.getIdentifier() == UserHandle.USER_NULL) { + return; + } + + final int n = mListeners.beginBroadcast(); + try { + for (int i = 0; i < n; i++) { + final IOnAppsChangedListener listener = + mListeners.getBroadcastItem(i); + final BroadcastCookie cookie = + (BroadcastCookie) mListeners.getBroadcastCookie( + i); + if (!isEnabledProfileOf(cookie, privateProfile, + "onSecureSettingsChange")) { + Log.d(TAG, "onSecureSettingsChange: Skipping - profile not enabled" + + " or not accessible for package=" + cookie.packageName + + ", packageUid=" + cookie.callingUid); + } else { + try { + Log.d(TAG, + "onUserConfigChanged: triggering onUserConfigChanged"); + listener.onUserConfigChanged( + mUserManagerInternal.getLauncherUserInfo( + privateProfile.getIdentifier())); + } catch (RemoteException re) { + Slog.d(TAG, "onUserConfigChanged: Callback failed ", re); + } + } + } + } finally { + mListeners.finishBroadcast(); + } + } + } + + public void register() { + UserHandle privateProfile = getPrivateProfile(); + int parentUserId; + if (privateProfile.getIdentifier() == UserHandle.USER_NULL) { + // No private space available, register the observer for the current user + parentUserId = mContext.getUserId(); + } else { + parentUserId = mUserManagerInternal.getProfileParentId( + privateProfile.getIdentifier()); + } + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT), + true, this, parentUserId); + } + + public void unregister() { + mContext.getContentResolver().unregisterContentObserver(this); + } + + private UserHandle getPrivateProfile() { + UserInfo[] userInfos = mUserManagerInternal.getUserInfos(); + for (UserInfo u : userInfos) { + if (u.isPrivateProfile()) { + return UserHandle.of(u.id); + } + } + return UserHandle.of(UserHandle.USER_NULL); + } + } } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 7ecfe7f64ffe..06e29c2c1408 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -21,6 +21,7 @@ import static android.content.Intent.ACTION_SCREEN_ON; import static android.content.Intent.EXTRA_USER_ID; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.pm.LauncherUserInfo.PRIVATE_SPACE_ENTRYPOINT_HIDDEN; import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE; import static android.content.pm.PackageManager.FEATURE_EMBEDDED; import static android.content.pm.PackageManager.FEATURE_LEANBACK; @@ -32,6 +33,7 @@ import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY; import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN; import static android.os.UserManager.USER_OPERATION_ERROR_USER_RESTRICTED; import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; +import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT; import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID; import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE; @@ -341,6 +343,10 @@ public class UserManagerService extends IUserManager.Stub { private static final String TRON_USER_CREATED = "users_user_created"; private static final String TRON_DEMO_CREATED = "users_demo_created"; + // The boot user strategy for HSUM. + private static final int BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER = 0; + private static final int BOOT_TO_HSU_FOR_PROVISIONED_DEVICE = 1; + private final Context mContext; private final PackageManagerService mPm; @@ -1391,37 +1397,77 @@ public class UserManagerService extends IUserManager.Stub { } if (isHeadlessSystemUserMode()) { - if (mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) { - return UserHandle.USER_SYSTEM; - } - // Return the previous foreground user, if there is one. - final int previousUser = getPreviousFullUserToEnterForeground(); - if (previousUser != UserHandle.USER_NULL) { - Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser); - return previousUser; + final int bootStrategy = mContext.getResources() + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); + switch (bootStrategy) { + case BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER: + return getPreviousOrFirstSwitchableUser(); + case BOOT_TO_HSU_FOR_PROVISIONED_DEVICE: + return getBootUserBasedOnProvisioning(); + default: + Slogf.w(LOG_TAG, "Unknown HSUM boot strategy: %d", bootStrategy); + return getPreviousOrFirstSwitchableUser(); } - // No previous user. Return the first switchable user if there is one. - synchronized (mUsersLock) { - final int userSize = mUsers.size(); - for (int i = 0; i < userSize; i++) { - final UserData userData = mUsers.valueAt(i); - if (userData.info.supportsSwitchToByUser()) { - int firstSwitchable = userData.info.id; - Slogf.i(LOG_TAG, - "Boot user is first switchable user %d", firstSwitchable); - return firstSwitchable; - } - } - } - // No switchable users found. Uh oh! - throw new UserManager.CheckedUserOperationException( - "No switchable users found", USER_OPERATION_ERROR_UNKNOWN); } // Not HSUM, return system user. return UserHandle.USER_SYSTEM; } + private @UserIdInt int getBootUserBasedOnProvisioning() + throws UserManager.CheckedUserOperationException { + final boolean provisioned = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0) != 0; + if (provisioned) { + return UserHandle.USER_SYSTEM; + } else { + final int firstSwitchableFullUser = getFirstSwitchableUser(true); + if (firstSwitchableFullUser != UserHandle.USER_NULL) { + Slogf.i(LOG_TAG, + "Boot user is first switchable full user %d", + firstSwitchableFullUser); + return firstSwitchableFullUser; + } + // No switchable full user found. Uh oh! + throw new UserManager.CheckedUserOperationException( + "No switchable full user found", USER_OPERATION_ERROR_UNKNOWN); + } + } + + private @UserIdInt int getPreviousOrFirstSwitchableUser() + throws UserManager.CheckedUserOperationException { + // Return the previous foreground user, if there is one. + final int previousUser = getPreviousFullUserToEnterForeground(); + if (previousUser != UserHandle.USER_NULL) { + Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser); + return previousUser; + } + // No previous user. Return the first switchable user if there is one. + final int firstSwitchableUser = getFirstSwitchableUser(false); + if (firstSwitchableUser != UserHandle.USER_NULL) { + Slogf.i(LOG_TAG, + "Boot user is first switchable user %d", firstSwitchableUser); + return firstSwitchableUser; + } + // No switchable users found. Uh oh! + throw new UserManager.CheckedUserOperationException( + "No switchable users found", USER_OPERATION_ERROR_UNKNOWN); + } + + private @UserIdInt int getFirstSwitchableUser(boolean fullUserOnly) { + synchronized (mUsersLock) { + final int userSize = mUsers.size(); + for (int i = 0; i < userSize; i++) { + final UserData userData = mUsers.valueAt(i); + if (userData.info.supportsSwitchToByUser() && + (!fullUserOnly || userData.info.isFull())) { + int firstSwitchable = userData.info.id; + return firstSwitchable; + } + } + } + return UserHandle.USER_NULL; + } + @Override public int getPreviousFullUserToEnterForeground() { @@ -7902,11 +7948,25 @@ public class UserManagerService extends IUserManager.Stub { } if (userInfo != null) { final UserTypeDetails userDetails = getUserTypeDetails(userInfo); - final LauncherUserInfo uiInfo = new LauncherUserInfo.Builder( - userDetails.getName(), - userInfo.serialNumber) - .build(); - return uiInfo; + + if (Flags.addLauncherUserConfig()) { + Bundle config = new Bundle(); + if (userInfo.isPrivateProfile()) { + try { + int parentId = getProfileParentIdUnchecked(userId); + config.putBoolean(PRIVATE_SPACE_ENTRYPOINT_HIDDEN, + Settings.Secure.getIntForUser(mContext.getContentResolver(), + HIDE_PRIVATESPACE_ENTRY_POINT, parentId) == 1); + } catch (Settings.SettingNotFoundException e) { + throw new RuntimeException(e); + } + } + return new LauncherUserInfo.Builder(userDetails.getName(), + userInfo.serialNumber, config).build(); + } + + return new LauncherUserInfo.Builder(userDetails.getName(), + userInfo.serialNumber).build(); } else { return null; } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 07fd1cb544f6..acf62dcdd1ce 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -235,6 +235,7 @@ final class DefaultPermissionGrantPolicy { NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN); NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.UWB_RANGING); NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.NEARBY_WIFI_DEVICES); + NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.RANGING); } private static final Set<String> NOTIFICATION_PERMISSIONS = new ArraySet<>(); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 24933cab2a32..5fc3e332b95c 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1015,8 +1015,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); // Finish any started op if some step in the attribution chain failed. - if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED - && result != PermissionChecker.PERMISSION_SOFT_DENIED) { + if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) { if (attributedOp == AppOpsManager.OP_NONE) { finishDataDelivery(AppOpsManager.permissionToOpCode(permission), attributionSource.asState(), fromDatasource); @@ -1245,7 +1244,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE; AttributionSource current = attributionSource; AttributionSource next = null; - AttributionSource prev = null; // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and // every attributionSource in the chain is registered with the system. final boolean isChainStartTrusted = !hasChain || checkPermission(context, @@ -1312,21 +1310,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { selfAccess, singleReceiverFromDatasource, attributedOp, proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); - if (opMode != AppOpsManager.MODE_ALLOWED) { - // Current failed the perm check, so if we are part-way through an attr chain, - // we need to clean up the already started proxy op higher up the chain. Note, - // proxy ops are verified two by two, which means we have to clear the 2nd next - // from the previous iteration (since it is actually curr.next which failed - // to pass the perm check). - if (prev != null) { - final var cutAttrSourceState = prev.asState(); - if (cutAttrSourceState.next.length > 0) { - cutAttrSourceState.next[0].next = new AttributionSourceState[0]; - } - finishDataDelivery(context, attributedOp, - cutAttrSourceState, fromDatasource); - } - if (opMode == AppOpsManager.MODE_ERRORED) { + switch (opMode) { + case AppOpsManager.MODE_ERRORED: { if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) { Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op" + " mode is MODE_ERRORED. Permission check was requested for: " @@ -1334,7 +1319,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { + current); } return PermissionChecker.PERMISSION_HARD_DENIED; - } else { + } + case AppOpsManager.MODE_IGNORED: { return PermissionChecker.PERMISSION_SOFT_DENIED; } } @@ -1349,8 +1335,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { return PermissionChecker.PERMISSION_GRANTED; } - // an attribution we have already possibly started an op for - prev = current; current = next; } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index fc24e62de04e..1af3ec0b4b63 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -84,6 +84,7 @@ import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED; import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled; import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable; +import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures; import static com.android.hardware.input.Flags.keyboardA11yShortcutControl; import static com.android.hardware.input.Flags.modifierShortcutDump; import static com.android.hardware.input.Flags.useKeyGestureEventHandler; @@ -3612,7 +3613,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_T: - if (keyboardA11yShortcutControl()) { + if (enableTalkbackAndMagnifierKeyGestures()) { if (firstDown && event.isMetaPressed() && event.isAltPressed()) { mTalkbackShortcutController.toggleTalkback(mCurrentUserId, TalkbackShortcutController.ShortcutSource.KEYBOARD); @@ -4112,7 +4113,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { return mDefaultDisplayPolicy.isAwake() && mAccessibilityShortcutController .isAccessibilityShortcutAvailable(false); case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK: - return keyboardA11yShortcutControl(); + return enableTalkbackAndMagnifierKeyGestures(); case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS: return InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled() && keyboardA11yShortcutControl(); @@ -4345,7 +4346,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } return true; case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK: - if (keyboardA11yShortcutControl()) { + if (enableTalkbackAndMagnifierKeyGestures()) { if (complete) { mTalkbackShortcutController.toggleTalkback(mCurrentUserId, TalkbackShortcutController.ShortcutSource.KEYBOARD); diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 600fe59215b6..606bd1dd0f3f 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -51,6 +51,7 @@ public class BatteryUsageStatsProvider { private final CpuScalingPolicies mCpuScalingPolicies; private final int mAccumulatedBatteryUsageStatsSpanSize; private final Clock mClock; + private final MonotonicClock mMonotonicClock; private final Object mLock = new Object(); private List<PowerCalculator> mPowerCalculators; private UserPowerCalculator mUserPowerCalculator; @@ -67,7 +68,7 @@ public class BatteryUsageStatsProvider { @NonNull PowerAttributor powerAttributor, @NonNull PowerProfile powerProfile, @NonNull CpuScalingPolicies cpuScalingPolicies, @NonNull PowerStatsStore powerStatsStore, int accumulatedBatteryUsageStatsSpanSize, - @NonNull Clock clock) { + @NonNull Clock clock, @NonNull MonotonicClock monotonicClock) { mContext = context; mPowerAttributor = powerAttributor; mPowerStatsStore = powerStatsStore; @@ -75,6 +76,7 @@ public class BatteryUsageStatsProvider { mCpuScalingPolicies = cpuScalingPolicies; mAccumulatedBatteryUsageStatsSpanSize = accumulatedBatteryUsageStatsSpanSize; mClock = clock; + mMonotonicClock = monotonicClock; mUserPowerCalculator = new UserPowerCalculator(); mPowerStatsStore.addSectionReader(new BatteryUsageStatsSection.Reader()); @@ -213,7 +215,7 @@ public class BatteryUsageStatsProvider { powerStatsSpan.addTimeFrame(accumulatedStats.startMonotonicTime, accumulatedStats.startWallClockTime, accumulatedStats.endMonotonicTime - accumulatedStats.startMonotonicTime); - stats.commitMonotonicClock(); + mMonotonicClock.write(); mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan, accumulatedStats.builder::discard); } @@ -308,23 +310,29 @@ public class BatteryUsageStatsProvider { private void updateAccumulatedBatteryUsageStats(AccumulatedBatteryUsageStats accumulatedStats, BatteryStatsImpl stats, BatteryUsageStatsQuery query) { - // TODO(b/366493365): add the current batteryusagestats directly into - // `accumulatedStats.builder` to avoid allocating a second CursorWindow - BatteryUsageStats.Builder remainingBatteryUsageStats = computeBatteryUsageStats(stats, - query, accumulatedStats.endMonotonicTime, query.getMonotonicEndTime(), - mClock.currentTimeMillis()); + long startMonotonicTime = accumulatedStats.endMonotonicTime; + if (startMonotonicTime == MonotonicClock.UNDEFINED) { + startMonotonicTime = stats.getMonotonicStartTime(); + } + long endWallClockTime = mClock.currentTimeMillis(); + long endMonotonicTime = mMonotonicClock.monotonicTime(); if (accumulatedStats.builder == null) { - accumulatedStats.builder = remainingBatteryUsageStats; + accumulatedStats.builder = new BatteryUsageStats.Builder( + stats.getCustomEnergyConsumerNames(), false, true, true, true, 0); accumulatedStats.startWallClockTime = stats.getStartClockTime(); - accumulatedStats.startMonotonicTime = stats.getMonotonicStartTime(); - accumulatedStats.endMonotonicTime = accumulatedStats.startMonotonicTime - + accumulatedStats.builder.getStatsDuration(); - } else { - accumulatedStats.builder.add(remainingBatteryUsageStats.build()); - accumulatedStats.endMonotonicTime += remainingBatteryUsageStats.getStatsDuration(); - remainingBatteryUsageStats.discard(); + accumulatedStats.builder.setStatsStartTimestamp(accumulatedStats.startWallClockTime); } + + accumulatedStats.endMonotonicTime = endMonotonicTime; + + accumulatedStats.builder.setStatsEndTimestamp(endWallClockTime); + accumulatedStats.builder.setStatsDuration(endWallClockTime - startMonotonicTime); + + mPowerAttributor.estimatePowerConsumption(accumulatedStats.builder, stats.getHistory(), + startMonotonicTime, MonotonicClock.UNDEFINED); + + populateGeneralInfo(accumulatedStats.builder, stats); } private BatteryUsageStats.Builder computeBatteryUsageStats(BatteryStatsImpl stats, diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index cf178046d2e6..4857b02eaf7c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -1725,9 +1725,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { return; } Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task); - if (transit == null) { - transit = task.mTransitionController.getCollectingTransition(); - } if (transit != null) { transit.collectClose(task); if (!task.mTransitionController.useFullReadyTracking()) { @@ -1739,7 +1736,15 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // before anything that may need it to wait (setReady(false)). transit.setReady(task, true); } + } else { + // If we failed to create a transition, there might be already a currently collecting + // transition. Let's use it if possible. + transit = task.mTransitionController.getCollectingTransition(); + if (transit != null) { + transit.collectClose(task); + } } + // Consume the stopping activities immediately so activity manager won't skip killing // the process because it is still foreground state, i.e. RESUMED -> PAUSING set from // removeActivities -> finishIfPossible. diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index db76eb9ac5d9..ebb50db54693 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -164,41 +164,46 @@ final class AppCompatUtils { appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap()); - final Rect bounds = top.getBounds(); - final Rect appBounds = getAppBounds(top); - appCompatTaskInfo.topActivityLetterboxWidth = bounds.width(); - appCompatTaskInfo.topActivityLetterboxHeight = bounds.height(); - appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width(); - appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height(); + final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed(); + appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed); + if (isTopActivityLetterboxed) { + final Rect bounds = top.getBounds(); + final Rect appBounds = getAppBounds(top); + appCompatTaskInfo.topActivityLetterboxWidth = bounds.width(); + appCompatTaskInfo.topActivityLetterboxHeight = bounds.height(); + appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width(); + appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height(); - // We need to consider if letterboxed or pillarboxed. - // TODO(b/336807329) Encapsulate reachability logic - appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides - .isLetterboxDoubleTapEducationEnabled()); - if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) { - if (appCompatTaskInfo.isTopActivityPillarboxed()) { - if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) { - // Pillarboxed. - appCompatTaskInfo.topActivityLetterboxHorizontalPosition = - reachabilityOverrides.getLetterboxPositionForHorizontalReachability(); - } else { - appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); - } - } else { - if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) { - // Letterboxed. - appCompatTaskInfo.topActivityLetterboxVerticalPosition = - reachabilityOverrides.getLetterboxPositionForVerticalReachability(); + // We need to consider if letterboxed or pillarboxed. + // TODO(b/336807329) Encapsulate reachability logic + appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides + .isLetterboxDoubleTapEducationEnabled()); + if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) { + if (appCompatTaskInfo.isTopActivityPillarboxShaped()) { + if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) { + // Pillarboxed. + appCompatTaskInfo.topActivityLetterboxHorizontalPosition = + reachabilityOverrides + .getLetterboxPositionForHorizontalReachability(); + } else { + appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); + } } else { - appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); + if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) { + // Letterboxed. + appCompatTaskInfo.topActivityLetterboxVerticalPosition = + reachabilityOverrides.getLetterboxPositionForVerticalReachability(); + } else { + appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); + } } } } + final boolean eligibleForAspectRatioButton = !info.isTopActivityTransparent && !appCompatTaskInfo.isTopActivityInSizeCompat() && aspectRatioOverrides.shouldEnableUserAspectRatioSettings(); appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton); - appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed()); appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = AppCompatCameraPolicy.getCameraCompatFreeformMode(top); appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index ee07d2e58389..76e8a70768c1 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1736,7 +1736,7 @@ public class DisplayPolicy { // Show IME over the keyguard if the target allows it. final boolean showImeOverKeyguard = - imeTarget != null && imeTarget.isOnScreen() && win.mIsImWindow && ( + imeTarget != null && win.mIsImWindow && imeTarget.isDisplayed() && ( imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard()); if (showImeOverKeyguard) { return false; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 72d45e42125d..a6034664af5a 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -3586,6 +3586,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) { flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; } + final TaskDisplayArea tda = wc.asTaskDisplayArea(); + if (tda != null) { + flags |= TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA; + } final Task task = wc.asTask(); if (task != null) { final ActivityRecord topActivity = task.getTopNonFinishingActivity(); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 5bde8b5a507c..44e237aa27de 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -614,6 +614,12 @@ class WindowToken extends WindowContainer<WindowState> { final int rotation = getRelativeDisplayRotation(); if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash; if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash; + if (ActivityTaskManagerService.isPip2ExperimentEnabled() && asActivityRecord() != null + && mTransitionController.getWindowingModeAtStart( + asActivityRecord()) == WINDOWING_MODE_PINNED) { + // PiP handles fixed rotation animation in Shell, so do not create the rotation leash. + return null; + } final SurfaceControl leash = makeSurface().setContainerLayer() .setParent(getParentSurfaceControl()) diff --git a/services/core/services-jarjar-rules.txt b/services/core/services-jarjar-rules.txt new file mode 100644 index 000000000000..0d296b238c7a --- /dev/null +++ b/services/core/services-jarjar-rules.txt @@ -0,0 +1,2 @@ +# For profiling flags +rule android.os.profiling.** android.internal.os.profiling.@1 diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index aca6f7235714..c6530381443f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -255,7 +255,6 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPR import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; -import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; @@ -462,7 +461,6 @@ import android.permission.PermissionControllerManager; import android.provider.CalendarContract; import android.provider.ContactsContract.QuickContact; import android.provider.ContactsInternal; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Telephony; @@ -908,10 +906,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + "management app's authentication policy"; private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s"; - private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG = - "enable_permission_based_access"; - private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false; - private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3; /** @@ -3486,7 +3480,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @GuardedBy("getLockObject()") private boolean maybeMigrateSuspendedPackagesLocked(String backupId) { Slog.i(LOG_TAG, "Migrating suspended packages to policy engine"); - if (!Flags.unmanagedModeMigration()) { + if (!Flags.suspendPackagesCoexistence()) { return false; } if (mOwners.isSuspendedPackagesMigrated()) { @@ -3557,6 +3551,46 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return true; } + + + @GuardedBy("getLockObject()") + private boolean maybeMigrateMemoryTaggingLocked(String backupId) { + if (!Flags.setMtePolicyCoexistence()) { + Slog.i(LOG_TAG, "Memory Tagging not migrated because coexistence " + + "support is disabled."); + return false; + } + if (mOwners.isMemoryTaggingMigrated()) { + // TODO: Remove log after Flags.setMtePolicyCoexistence full rollout. + Slog.v(LOG_TAG, "Memory Tagging was previously migrated to policy engine."); + return false; + } + + Slog.i(LOG_TAG, "Migrating Memory Tagging to policy engine"); + + // Create backup if none exists + mDevicePolicyEngine.createBackup(backupId); + try { + iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> { + if (admin.mtePolicy != 0) { + Slog.i(LOG_TAG, "Setting Memory Tagging policy"); + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.MEMORY_TAGGING, + enforcingAdmin, + new IntegerPolicyValue(admin.mtePolicy), + true /* No need to re-set system properties */); + } + }); + } catch (Exception e) { + Slog.wtf(LOG_TAG, + "Failed to migrate Memory Tagging to policy engine", e); + } + + Slog.i(LOG_TAG, "Marking Memory Tagging migration complete"); + mOwners.markMemoryTaggingMigrated(); + return true; + } + /** Register callbacks for statsd pulled atoms. */ private void registerStatsCallbacks() { final StatsManager statsManager = mContext.getSystemService(StatsManager.class); @@ -4646,22 +4680,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @GuardedBy("getLockObject()") private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) { if (isSeparateProfileChallengeEnabled(userHandle)) { - - if (isPermissionCheckFlagEnabled()) { - return getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(userHandle); - } // If this user has a separate challenge, only return its restrictions. return getUserDataUnchecked(userHandle).mAdminList; } // If isSeparateProfileChallengeEnabled is false and userHandle points to a managed profile // we need to query the parent user who owns the credential. - if (isPermissionCheckFlagEnabled()) { - return getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(getProfileParentId(userHandle), - (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); - } else { - return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle), - (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); - } + return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle), + (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); } @@ -4684,33 +4709,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { (user) -> mLockPatternUtils.isProfileWithUnifiedChallenge(user.id)); } - /** - * Get the list of active admins for an affected user: - * <ul> - * <li>The active admins associated with the userHandle itself</li> - * <li>The parent active admins for each managed profile associated with the userHandle</li> - * <li>The permission based admin associated with the userHandle itself</li> - * </ul> - * - * @param userHandle the affected user for whom to get the active admins - * @return the list of active admins for the affected user - */ - @GuardedBy("getLockObject()") - private List<ActiveAdmin> getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked( - int userHandle) { - List<ActiveAdmin> list; - - if (isManagedProfile(userHandle)) { - list = getUserDataUnchecked(userHandle).mAdminList; - } - list = getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(userHandle, - /* shouldIncludeProfileAdmins */ (user) -> false); - - if (getUserData(userHandle).mPermissionBasedAdmin != null) { - list.add(getUserData(userHandle).mPermissionBasedAdmin); - } - return list; - } /** * Returns the list of admins on the given user, as well as parent admins for each managed @@ -4763,44 +4761,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mDevicePolicyEngine.getResolvedPolicyAcrossUsers(policyDefinition, users); } - /** - * Returns the list of admins on the given user, as well as parent admins for each managed - * profile associated with the given user. Optionally also include the admin of each managed - * profile. - * <p> Should not be called on a profile user. - */ - @GuardedBy("getLockObject()") - private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(int userHandle, - Predicate<UserInfo> shouldIncludeProfileAdmins) { - ArrayList<ActiveAdmin> admins = new ArrayList<>(); - mInjector.binderWithCleanCallingIdentity(() -> { - for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) { - DevicePolicyData policy = getUserDataUnchecked(userInfo.id); - if (userInfo.id == userHandle) { - admins.addAll(policy.mAdminList); - if (policy.mPermissionBasedAdmin != null) { - admins.add(policy.mPermissionBasedAdmin); - } - } else if (userInfo.isManagedProfile()) { - for (int i = 0; i < policy.mAdminList.size(); i++) { - ActiveAdmin admin = policy.mAdminList.get(i); - if (admin.hasParentActiveAdmin()) { - admins.add(admin.getParentActiveAdmin()); - } - if (shouldIncludeProfileAdmins.test(userInfo)) { - admins.add(admin); - } - } - if (policy.mPermissionBasedAdmin != null - && shouldIncludeProfileAdmins.test(userInfo)) { - admins.add(policy.mPermissionBasedAdmin); - } - } - } - }); - return admins; - } - private boolean isSeparateProfileChallengeEnabled(int userHandle) { return mInjector.binderWithCleanCallingIdentity(() -> mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)); @@ -4893,25 +4853,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(who, "ComponentName is null"); - } + Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkArgumentNonnegative(timeout, "Timeout must be >= 0 ms"); int userHandle = mInjector.userHandleGetCallingUserId(); int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; synchronized (getLockObject()) { ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(who, callerPackageName); - ap = enforcePermissionAndGetEnforcingAdmin( - who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - caller.getPackageName(), affectedUserId) - .getActiveAdmin(); - } else { - ap = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent); - } + ap = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent); // Calling this API automatically bumps the expiration date final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; ap.passwordExpirationDate = expiration; @@ -4972,28 +4922,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean addCrossProfileWidgetProvider(ComponentName admin, String callerPackageName, String packageName) { - CallerIdentity caller; + CallerIdentity caller = getCallerIdentity(admin); - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } - ActiveAdmin activeAdmin; + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isProfileOwner(caller)); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - caller.getUserId()); - activeAdmin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isProfileOwner(caller)); - synchronized (getLockObject()) { - activeAdmin = getProfileOwnerLocked(caller.getUserId()); - } + ActiveAdmin activeAdmin; + synchronized (getLockObject()) { + activeAdmin = getProfileOwnerLocked(caller.getUserId()); } List<String> changedProviders = null; @@ -5026,28 +4962,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean removeCrossProfileWidgetProvider(ComponentName admin, String callerPackageName, String packageName) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } + CallerIdentity caller = getCallerIdentity(admin); - ActiveAdmin activeAdmin; + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isProfileOwner(caller)); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - caller.getUserId()); - activeAdmin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isProfileOwner(caller)); - synchronized (getLockObject()) { - activeAdmin = getProfileOwnerLocked(caller.getUserId()); - } + ActiveAdmin activeAdmin; + synchronized (getLockObject()) { + activeAdmin = getProfileOwnerLocked(caller.getUserId()); } List<String> changedProviders = null; @@ -5080,27 +5002,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public List<String> getCrossProfileWidgetProviders(ComponentName admin, String callerPackageName) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } - ActiveAdmin activeAdmin; + CallerIdentity caller = getCallerIdentity(admin); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - caller.getUserId()); - activeAdmin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isProfileOwner(caller)); - synchronized (getLockObject()) { - activeAdmin = getProfileOwnerLocked(caller.getUserId()); - } + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isProfileOwner(caller)); + + ActiveAdmin activeAdmin; + synchronized (getLockObject()) { + activeAdmin = getProfileOwnerLocked(caller.getUserId()); } synchronized (getLockObject()) { @@ -5449,24 +5358,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforceUserUnlocked(userHandle, parent); synchronized (getLockObject()) { - if (isPermissionCheckFlagEnabled()) { - int affectedUser = parent ? getProfileParentId(userHandle) : userHandle; - enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - callerPackageName, affectedUser); - } else { - // This API can only be called by an active device admin, - // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked( - null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - } + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked( + null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); int credentialOwner = getCredentialOwner(userHandle, parent); DevicePolicyData policy = getUserDataUnchecked(credentialOwner); PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner); final int userToCheck = getProfileParentUserIfRequested(userHandle, parent); - boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked( + return isActivePasswordSufficientForUserLocked( policy.mPasswordValidAtLastCheckpoint, metrics, userToCheck); - return activePasswordSufficientForUserLocked; } } @@ -5622,21 +5524,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller), "Only profile owner, device owner and system may call this method on parent."); } else { - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) - || hasCallingOrSelfPermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS) - || isDefaultDeviceOwner(caller) || isProfileOwner(caller), - "Must have " + REQUEST_PASSWORD_COMPLEXITY + " or " + - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS - + " permissions, or be a profile owner or device owner."); - } else { - Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) - || isDefaultDeviceOwner(caller) || isProfileOwner(caller), - "Must have " + REQUEST_PASSWORD_COMPLEXITY - + " permission, or be a profile owner or device owner."); - } + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) + || isDefaultDeviceOwner(caller) || isProfileOwner(caller), + "Must have " + REQUEST_PASSWORD_COMPLEXITY + + " permission, or be a profile owner or device owner."); } synchronized (getLockObject()) { @@ -5728,26 +5620,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void setRequiredPasswordComplexityPreCoexistence( String callerPackageName, int passwordComplexity, boolean calledOnParent) { CallerIdentity caller = getCallerIdentity(callerPackageName); - if (!isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller)); - Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller)); - } + + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller)); synchronized (getLockObject()) { ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - // TODO: Make sure this returns the parent of the fake admin - // TODO: Deal with null componentname - int affectedUser = calledOnParent - ? getProfileParentId(caller.getUserId()) : caller.getUserId(); - admin = enforcePermissionAndGetEnforcingAdmin( - null, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - caller.getPackageName(), affectedUser).getActiveAdmin(); - } else { - admin = getParentOfAdminIfRequired( - getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent); - } + admin = getParentOfAdminIfRequired( + getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent); if (admin.mPasswordComplexity != passwordComplexity) { // We require the caller to explicitly clear any password quality requirements set @@ -5907,14 +5788,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!isSystemUid(caller)) { // This API can be called by an active device admin or by keyguard code. if (!hasCallingPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)) { - if (isPermissionCheckFlagEnabled()) { - int affectedUser = parent ? getProfileParentId(userHandle) : userHandle; - enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - callerPackageName, affectedUser); - } else { - getActiveAdminForCallerLocked( - null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); - } + getActiveAdminForCallerLocked( + null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); } } @@ -5931,31 +5806,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(who, "ComponentName is null"); - } - + Objects.requireNonNull(who, "ComponentName is null"); int userId = mInjector.userHandleGetCallingUserId(); int affectedUserId = parent ? getProfileParentId(userId) : userId; synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(who, callerPackageName); - ap = enforcePermissionAndGetEnforcingAdmin( - who, - /*permission=*/ MANAGE_DEVICE_POLICY_WIPE_DATA, - /* adminPolicy=*/ DeviceAdminInfo.USES_POLICY_WIPE_DATA, - caller.getPackageName(), affectedUserId).getActiveAdmin(); - } else { - // This API can only be called by an active device admin, - // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent); - ap = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); - } + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent); + ActiveAdmin ap = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); if (ap.maximumFailedPasswordsForWipe != num) { ap.maximumFailedPasswordsForWipe = num; @@ -6210,25 +6072,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(who, "ComponentName is null"); - } + + Objects.requireNonNull(who, "ComponentName is null"); + int userHandle = mInjector.userHandleGetCallingUserId(); int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(who, callerPackageName); - ap = enforcePermissionAndGetEnforcingAdmin( - who, - /*permission=*/ MANAGE_DEVICE_POLICY_LOCK, - /*AdminPolicy=*/DeviceAdminInfo.USES_POLICY_FORCE_LOCK, - caller.getPackageName(), - affectedUserId).getActiveAdmin(); - } else { - ap = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent); - } + ActiveAdmin ap = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent); if (ap.maximumTimeToUnlock != timeMs) { ap.maximumTimeToUnlock = timeMs; @@ -6334,16 +6185,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } + Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number."); - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + // timeoutMs with value 0 means that the admin doesn't participate // timeoutMs is clamped to the interval in case the internal constants change in the future final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs(); @@ -6357,17 +6205,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userHandle = caller.getUserId(); boolean changed = false; synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - int affectedUser = parent - ? getProfileParentId(caller.getUserId()) : caller.getUserId(); - ap = enforcePermissionAndGetEnforcingAdmin( - who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - caller.getPackageName(), affectedUser).getActiveAdmin(); - } else { - ap = getParentOfAdminIfRequired( - getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent); - } + ActiveAdmin ap = getParentOfAdminIfRequired( + getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent); if (ap.strongAuthUnlockTimeout != timeoutMs) { ap.strongAuthUnlockTimeout = timeoutMs; saveSettingsLocked(userHandle); @@ -6664,16 +6503,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization(!isUserSelectable, "The credential " + "management app is not allowed to install a user selectable key pair"); @@ -6733,16 +6565,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( isAliasInCredentialManagementAppPolicy(caller, alias), @@ -6802,13 +6627,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean canInstallCertificates(CallerIdentity caller) { - if (isPermissionCheckFlagEnabled()) { - return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()); - } else { - return isProfileOwner(caller) || isDefaultDeviceOwner(caller) - || isCallerDelegate(caller, DELEGATION_CERT_INSTALL); - } + return isProfileOwner(caller) || isDefaultDeviceOwner(caller) + || isCallerDelegate(caller, DELEGATION_CERT_INSTALL); } private boolean canChooseCertificates(CallerIdentity caller) { @@ -7001,16 +6821,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getPackageName(), caller.getUid())); enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags); } else { - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner( - caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && ( - isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner( + caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && ( + isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( isAliasInCredentialManagementAppPolicy(caller, alias), @@ -7143,16 +6956,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( isAliasInCredentialManagementAppPolicy(caller, alias), @@ -8285,29 +8091,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - if (!isPermissionCheckFlagEnabled()) { - Preconditions.checkNotNull(who, "ComponentName is null"); - } + + Preconditions.checkNotNull(who, "ComponentName is null"); + CallerIdentity caller = getCallerIdentity(who, callerPackageName); - if (!isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); + checkCanExecuteOrThrowUnsafe(DevicePolicyManager .OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY); final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow(); synchronized (getLockObject()) { ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin( - who, MANAGE_DEVICE_POLICY_FACTORY_RESET, caller.getPackageName(), - UserHandle.USER_ALL) - .getActiveAdmin(); - } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); admin.mFactoryResetProtectionPolicy = policy; saveSettingsLocked(caller.getUserId()); } @@ -8347,7 +8145,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || hasCallingPermission(permission.MASTER_CLEAR) || hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET), "Must be called by the FRP management agent on device"); - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked(); + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); } else { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) @@ -10247,15 +10045,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return admin; } - ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked() { - ensureLocked(); - ActiveAdmin doOrPo = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - if (isPermissionCheckFlagEnabled() && doOrPo == null) { - return getUserData(0).mPermissionBasedAdmin; - } - return doOrPo; - } - @Override public void clearDeviceOwner(String packageName) { Objects.requireNonNull(packageName, "packageName is null"); @@ -10998,8 +10787,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * (2.1.1) The caller is the profile owner. * (2.1.2) The caller is from another app in the same user as the profile owner, AND * the caller is the delegated cert installer. - * (3) The caller holds the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission. * * For the device owner case, simply check that the caller is the device owner or the * delegated certificate installer. @@ -11013,24 +10800,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @VisibleForTesting boolean hasDeviceIdAccessUnchecked(String packageName, int uid) { final int userId = UserHandle.getUserId(uid); - // TODO(b/280048070): Introduce a permission to handle device ID access - if (isPermissionCheckFlagEnabled() - && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) { - return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId); - } else { - ComponentName deviceOwner = getDeviceOwnerComponent(true); - if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName) - || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { - return true; - } - ComponentName profileOwner = getProfileOwnerAsUser(userId); - final boolean isCallerProfileOwnerOrDelegate = profileOwner != null - && (profileOwner.getPackageName().equals(packageName) - || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL)); - if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId) - || isUserAffiliatedWithDevice(userId))) { - return true; - } + ComponentName deviceOwner = getDeviceOwnerComponent(true); + if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName) + || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { + return true; + } + ComponentName profileOwner = getProfileOwnerAsUser(userId); + final boolean isCallerProfileOwnerOrDelegate = profileOwner != null + && (profileOwner.getPackageName().equals(packageName) + || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL)); + if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId) + || isUserAffiliatedWithDevice(userId))) { + return true; } return false; } @@ -11731,25 +11512,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setDefaultSmsApplication(ComponentName admin, String callerPackageName, String packageName, boolean parent) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } + CallerIdentity caller = getCallerIdentity(admin); - final int userId; - if (isPermissionCheckFlagEnabled()) { - enforcePermission( - MANAGE_DEVICE_POLICY_DEFAULT_SMS, - caller.getPackageName(), - getAffectedUser(parent)); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); if (!parent && isManagedProfile(caller.getUserId()) && getManagedSubscriptionsPolicy().getPolicyType() @@ -11759,6 +11527,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + "ManagedSubscriptions policy is set"); } + final int userId; if (parent) { userId = getProfileParentId(mInjector.userHandleGetCallingUserId()); mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage( @@ -11957,10 +11726,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(admin, "admin is null"); - } - + Objects.requireNonNull(admin, "admin is null"); Objects.requireNonNull(agent, "agent is null"); PolicySizeVerifier.enforceMaxPackageNameLength(agent.getPackageName()); @@ -11972,19 +11738,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int userHandle = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(admin, callerPackageName); - int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; - ap = enforcePermissionAndGetEnforcingAdmin( - admin, - /*permission=*/MANAGE_DEVICE_POLICY_KEYGUARD, - /*adminPolicy=*/DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, - caller.getPackageName(), affectedUserId).getActiveAdmin(); - } else { - ap = getActiveAdminForCallerLocked(admin, - DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent); - } + ActiveAdmin ap = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent); checkCanExecuteOrThrowUnsafe( DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION); @@ -12080,27 +11835,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void addCrossProfileIntentFilter(ComponentName who, String callerPackageName, IntentFilter filter, int flags) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - } - int callingUserId = caller.getUserId(); + CallerIdentity caller = getCallerIdentity(who); + + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - if (isPermissionCheckFlagEnabled()) { - enforcePermission( - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - callingUserId); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - } synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); try { + int callingUserId = caller.getUserId(); UserInfo parent = mUserManager.getProfileParent(callingUserId); if (parent == null) { Slogf.e(LOG_TAG, "Cannot call addCrossProfileIntentFilter if there is no " @@ -12144,28 +11888,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void clearCrossProfileIntentFilters(ComponentName who, String callerPackageName) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - } - int callingUserId = caller.getUserId(); + CallerIdentity caller = getCallerIdentity(who); - if (isPermissionCheckFlagEnabled()) { - enforcePermission( - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - callingUserId); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - } + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); try { + int callingUserId = caller.getUserId(); UserInfo parent = mUserManager.getProfileParent(callingUserId); if (parent == null) { Slogf.e(LOG_TAG, "Cannot call clearCrossProfileIntentFilter if there is no " @@ -13360,7 +13092,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public String[] setPackagesSuspended(ComponentName who, String callerPackage, String[] packageNames, boolean suspended) { - if (!Flags.unmanagedModeMigration()) { + if (!Flags.suspendPackagesCoexistence()) { return setPackagesSuspendedPreCoexistence(who, callerPackage, packageNames, suspended); } @@ -13450,7 +13182,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - if (Flags.unmanagedModeMigration()) { + if (Flags.suspendPackagesCoexistence()) { enforcePermission( MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), @@ -15166,19 +14898,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - enforcePermission(MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Preconditions.checkNotNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, @@ -15197,16 +14922,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } CallerIdentity caller = getCallerIdentity(who); - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_WIFI, who.getPackageName(), - UserHandle.USER_ALL); - } else { - Preconditions.checkNotNull(who, "ComponentName is null"); - - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); return mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0); @@ -15294,18 +15013,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean setTime(@Nullable ComponentName who, String callerPackageName, long millis) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - // This is a global action. - enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); // Don't allow set time when auto time is on. if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) { @@ -15322,18 +15034,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean setTimeZone(@Nullable ComponentName who, String callerPackageName, String timeZone) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - // This is a global action. - enforcePermission(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); // Don't allow set timezone when auto timezone is on. if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) { @@ -16537,22 +16242,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.validateAgainstPreviousFreezePeriod(record.first, record.second, LocalDate.now()); } - CallerIdentity caller; - synchronized (getLockObject()) { - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) + CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller)); - } - checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY); + checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY); + synchronized (getLockObject()) { if (policy == null) { mOwners.clearSystemUpdatePolicy(); } else { @@ -16699,7 +16397,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mUserManager.getUserInfo(UserHandle.getCallingUserId()).isMain()) { Slogf.w(LOG_TAG, "Only the system update service in the main user can broadcast " + "update information."); - return; } }); @@ -16723,7 +16420,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } // Get running users. - final int runningUserIds[]; + final int[] runningUserIds; try { runningUserIds = mInjector.getIActivityManager().getRunningUserIds(); } catch (RemoteException e) { @@ -16966,10 +16663,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } } - if (!isRuntimePermission(permission)) { - return false; - } - return true; + return isRuntimePermission(permission); } private void enforcePermissionGrantStateOnFinancedDevice( @@ -17384,18 +17078,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public String getWifiMacAddress(ComponentName admin, String callerPackageName) { -// if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(admin, "ComponentName is null"); -// } + Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin, callerPackageName); -// if (isPermissionCheckFlagEnabled()) { -// enforcePermission(MANAGE_DEVICE_POLICY_WIFI, UserHandle.USER_ALL); -// } else { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); -// } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); return mInjector.binderWithCleanCallingIdentity(() -> { String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses(); @@ -17462,25 +17150,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - CallerIdentity caller; - ActiveAdmin admin; message = PolicySizeVerifier.truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH); - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - synchronized (getLockObject()) { - admin = getActiveAdminForUidLocked(who, caller.getUid()); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + + ActiveAdmin admin; + synchronized (getLockObject()) { + admin = getActiveAdminForUidLocked(who, caller.getUid()); } synchronized (getLockObject()) { @@ -17501,23 +17179,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return null; } - CallerIdentity caller; - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - synchronized (getLockObject()) { - admin = getActiveAdminForUidLocked(who, caller.getUid()); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + + ActiveAdmin admin; + synchronized (getLockObject()) { + admin = getActiveAdminForUidLocked(who, caller.getUid()); } return admin.shortSupportMessage; } @@ -17680,26 +17348,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } CallerIdentity caller = getCallerIdentity(who); - ActiveAdmin admin = null; - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); - } + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); text = PolicySizeVerifier.truncateIfLonger(text, MAX_ORG_NAME_LENGTH); synchronized (getLockObject()) { - if (!isPermissionCheckFlagEnabled()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); if (!TextUtils.equals(admin.organizationName, text)) { admin.organizationName = (text == null || text.length() == 0) ? null : text.toString(); @@ -17714,23 +17370,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } CallerIdentity caller = getCallerIdentity(who); - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); - synchronized (getLockObject()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin; + synchronized (getLockObject()) { + admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); } return admin.organizationName; @@ -18214,28 +17861,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final CallerIdentity caller = getCallerIdentity(admin, packageName); - if (isPermissionCheckFlagEnabled()) { - synchronized (getLockObject()) { - Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() - || areAllUsersAffiliatedWithDeviceLocked()); - enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(), - UserHandle.USER_ALL); - } + if (admin != null) { + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller) + || isDefaultDeviceOwner(caller)); } else { - if (admin != null) { - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDefaultDeviceOwner(caller)); - } else { - // A delegate app passes a null admin component, which is expected - Preconditions.checkCallAuthorization( - isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); - } + // A delegate app passes a null admin component, which is expected + Preconditions.checkCallAuthorization( + isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); + } - synchronized (getLockObject()) { - Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() - || areAllUsersAffiliatedWithDeviceLocked()); - } + synchronized (getLockObject()) { + Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() + || areAllUsersAffiliatedWithDeviceLocked()); } DevicePolicyEventLogger @@ -18259,7 +17897,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return new ParceledListSlice<SecurityEvent>(output); } catch (IOException e) { Slogf.w(LOG_TAG, "Fail to read previous events" , e); - return new ParceledListSlice<SecurityEvent>(Collections.<SecurityEvent>emptyList()); + return new ParceledListSlice<SecurityEvent>(Collections.emptyList()); } } @@ -18752,8 +18390,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean hasIncompatibleAccounts(int userId) { - return mHasIncompatibleAccounts == null ? true - : mHasIncompatibleAccounts.getOrDefault(userId, /* default= */ false); + return mHasIncompatibleAccounts == null || mHasIncompatibleAccounts.getOrDefault( + userId, /* default= */ false); } /** @@ -18870,7 +18508,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } } - }; + } private boolean isAdb(CallerIdentity caller) { return isShellUid(caller) || isRootUid(caller); @@ -20168,21 +19806,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void installUpdateFromFile(ComponentName admin, String callerPackageName, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) { - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(admin, "ComponentName is null"); - } + Objects.requireNonNull(admin, "ComponentName is null"); - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(admin); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE); DevicePolicyEventLogger @@ -20752,32 +20381,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setCommonCriteriaModeEnabled(ComponentName who, String callerPackageName, boolean enabled) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - } - final ActiveAdmin admin; + CallerIdentity caller = getCallerIdentity(who); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), - "Common Criteria mode can only be controlled by a device owner or " - + "a profile owner on an organization-owned device."); - synchronized (getLockObject()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } - } + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Common Criteria mode can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); admin.mCommonCriteriaMode = enabled; saveSettingsLocked(caller.getUserId()); } @@ -20809,7 +20421,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // their ActiveAdmin, instead of iterating through all admins. ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - return admin != null ? admin.mCommonCriteriaMode : false; + return admin != null && admin.mCommonCriteriaMode; } } @@ -22209,7 +21821,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else { owner = getDeviceOrProfileOwnerAdminLocked(userId); } - boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false; + boolean canGrant = owner != null && owner.mAdminCanGrantSensorsPermissions; mPolicyCache.setAdminCanGrantSensorsPermissions(canGrant); } } @@ -22408,27 +22020,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setMinimumRequiredWifiSecurityLevel(String callerPackageName, int level) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(callerPackageName); - } else { - caller = getCallerIdentity(); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), - "Wi-Fi minimum security level can only be controlled by a device owner or " - + "a profile owner on an organization-owned device."); - } + CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Wi-Fi minimum security level can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); boolean valueChanged = false; synchronized (getLockObject()) { - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin(/* admin= */ null, - MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), caller.getUserId()) - .getActiveAdmin(); - } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); if (admin.mWifiMinimumSecurityLevel != level) { admin.mWifiMinimumSecurityLevel = level; saveSettingsLocked(caller.getUserId()); @@ -22450,21 +22050,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public WifiSsidPolicy getWifiSsidPolicy(String callerPackageName) { final CallerIdentity caller = getCallerIdentity(); - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_WIFI, callerPackageName, - caller.getUserId()); - } else { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) - || canQueryAdminPolicy(caller), - "SSID policy can only be retrieved by a device owner or " - + "a profile owner on an organization-owned device or " - + "an app with the QUERY_ADMIN_POLICY permission."); - } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller) + || canQueryAdminPolicy(caller), + "SSID policy can only be retrieved by a device owner or " + + "a profile owner on an organization-owned device or " + + "an app with the QUERY_ADMIN_POLICY permission."); synchronized (getLockObject()) { ActiveAdmin admin; - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked(); + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); return admin != null ? admin.mWifiSsidPolicy : null; } } @@ -22485,29 +22080,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setWifiSsidPolicy(String callerPackageName, WifiSsidPolicy policy) { - CallerIdentity caller; - - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(callerPackageName); - } else { - caller = getCallerIdentity(); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), - "SSID denylist can only be controlled by a device owner or " - + "a profile owner on an organization-owned device."); - } + CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "SSID denylist can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); boolean changed = false; synchronized (getLockObject()) { - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin( - /* admin= */ null, MANAGE_DEVICE_POLICY_WIFI, - caller.getPackageName(), - caller.getUserId()).getActiveAdmin(); - } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); if (!Objects.equals(policy, admin.mWifiSsidPolicy)) { admin.mWifiSsidPolicy = policy; changed = true; @@ -22715,7 +22296,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private final class DevicePolicyManagementRoleObserver implements OnRoleHoldersChangedListener { - private RoleManager mRm; + private final RoleManager mRm; private final Executor mExecutor; private final Context mContext; @@ -22732,13 +22313,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { mDevicePolicyEngine.handleRoleChanged(roleName, user.getIdentifier()); - if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) { - handleDevicePolicyManagementRoleChange(user); - return; - } - if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) { - handleFinancedDeviceKioskRoleChange(); - return; + switch (roleName) { + case RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT -> + handleDevicePolicyManagementRoleChange(user); + case RoleManager.ROLE_FINANCED_DEVICE_KIOSK -> + handleFinancedDeviceKioskRoleChange(); } } @@ -23390,26 +22969,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Checks if the calling process has been granted permission to apply a device policy on a - * specific user. - * The given permission will be checked along with its associated cross-user permission if it - * exists and the target user is different to the calling user. - * Returns an {@link EnforcingAdmin} for the caller. - * - * @param admin the component name of the admin. - * @param callerPackageName The package name of the calling application. - * @param permission The name of the permission being checked. - * @param deviceAdminPolicy The userId of the user which the caller needs permission to act on. - * @throws SecurityException if the caller has not been granted the given permission, - * the associated cross-user permission if the caller's user is different to the target user. - */ - private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin, - String permission, int deviceAdminPolicy, String callerPackageName, int targetUserId) { - enforcePermission(permission, deviceAdminPolicy, callerPackageName, targetUserId); - return getEnforcingAdminForCaller(admin, callerPackageName); - } - - /** - * Checks if the calling process has been granted permission to apply a device policy on a * specific user. Only one permission provided in the list needs to be granted to pass this * check. * The given permissions will be checked along with their associated cross-user permissions if @@ -23431,23 +22990,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** - * Checks whether the calling process has been granted permission to query a device policy on - * a specific user. - * The given permission will be checked along with its associated cross-user permission if it - * exists and the target user is different to the calling user. - * - * @param permission The name of the permission being checked. - * @param targetUserId The userId of the user which the caller needs permission to act on. - * @throws SecurityException if the caller has not been granted the given permission, - * the associated cross-user permission if the caller's user is different to the target user. - */ - private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin, - String permission, String callerPackageName, int targetUserId) { - enforceCanQuery(permission, callerPackageName, targetUserId); - return getEnforcingAdminForCaller(admin, callerPackageName); - } - - /** * Checks if the calling process has been granted permission to apply a device policy. * * @param callerPackageName The package name of the calling application. @@ -23754,13 +23296,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return NOT_A_DPC; } - private boolean isPermissionCheckFlagEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE_DEVICE_POLICY_MANAGER, - PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG, - DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG); - } - private static boolean isSetStatusBarDisabledCoexistenceEnabled() { return false; } @@ -23837,58 +23372,83 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); } - if (isPermissionCheckFlagEnabled()) { + if (Flags.setMtePolicyCoexistence()) { enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(), UserHandle.USER_ALL); } else { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + || isProfileOwnerOfOrganizationOwnedDevice(caller)); } + synchronized (getLockObject()) { - ActiveAdmin admin = + if (Flags.setMtePolicyCoexistence()) { + final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null, + MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId()); + if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.MEMORY_TAGGING, + admin, + new IntegerPolicyValue(flags)); + } else { + mDevicePolicyEngine.removeGlobalPolicy( + PolicyDefinition.MEMORY_TAGGING, + admin); + } + } else { + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - - if (admin != null) { - final String memtagProperty = "arm64.memtag.bootctl"; - if (flags == DevicePolicyManager.MTE_ENABLED) { - mInjector.systemPropertiesSet(memtagProperty, "memtag"); - } else if (flags == DevicePolicyManager.MTE_DISABLED) { - mInjector.systemPropertiesSet(memtagProperty, "memtag-off"); - } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { - if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { - mInjector.systemPropertiesSet(memtagProperty, "default"); + if (admin != null) { + final String memtagProperty = "arm64.memtag.bootctl"; + if (flags == DevicePolicyManager.MTE_ENABLED) { + mInjector.systemPropertiesSet(memtagProperty, "memtag"); + } else if (flags == DevicePolicyManager.MTE_DISABLED) { + mInjector.systemPropertiesSet(memtagProperty, "memtag-off"); + } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { + if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { + mInjector.systemPropertiesSet(memtagProperty, "default"); + } } + admin.mtePolicy = flags; + saveSettingsLocked(caller.getUserId()); } - admin.mtePolicy = flags; - saveSettingsLocked(caller.getUserId()); - - DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY) - .setInt(flags) - .setAdmin(caller.getPackageName()) - .write(); } + + DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY) + .setInt(flags) + .setAdmin(caller.getPackageName()) + .write(); } } @Override public int getMtePolicy(String callerPackageName) { final CallerIdentity caller = getCallerIdentity(callerPackageName); - if (isPermissionCheckFlagEnabled()) { + if (Flags.setMtePolicyCoexistence()) { enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(), UserHandle.USER_ALL); } else { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) - || isSystemUid(caller)); + || isProfileOwnerOfOrganizationOwnedDevice(caller) + || isSystemUid(caller)); } + synchronized (getLockObject()) { - ActiveAdmin admin = + if (Flags.setMtePolicyCoexistence()) { + final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null, + MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId()); + final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.MEMORY_TAGGING, admin); + return (policyFromAdmin != null ? policyFromAdmin + : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY); + } else { + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - return admin != null - ? admin.mtePolicy - : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; + return admin != null + ? admin.mtePolicy + : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; + } } } @@ -24235,21 +23795,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { maybeMigrateSecurityLoggingPolicyLocked(); // ID format: <sdk-int>.<auto_increment_id>.<descriptions>' String unmanagedBackupId = "35.1.unmanaged-mode"; - boolean unmanagedMigrated = false; - unmanagedMigrated = - unmanagedMigrated | maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId); - unmanagedMigrated = - unmanagedMigrated | maybeMigrateSuspendedPackagesLocked(unmanagedBackupId); + boolean unmanagedMigrated = maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId); if (unmanagedMigrated) { Slogf.i(LOG_TAG, "Backup made: " + unmanagedBackupId); } String supervisionBackupId = "36.2.supervision-support"; boolean supervisionMigrated = maybeMigrateResetPasswordTokenLocked(supervisionBackupId); + supervisionMigrated |= maybeMigrateSuspendedPackagesLocked(supervisionBackupId); if (supervisionMigrated) { Slogf.i(LOG_TAG, "Backup made: " + supervisionBackupId); } + String memoryTaggingBackupId = "36.3.memory-tagging"; + boolean memoryTaggingMigrated = maybeMigrateMemoryTaggingLocked(memoryTaggingBackupId); + if (memoryTaggingMigrated) { + Slogf.i(LOG_TAG, "Backup made: " + memoryTaggingBackupId); + } + // Additional migration steps should repeat the pattern above with a new backupId. } @@ -24666,7 +24229,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isCallerDevicePolicyManagementRoleHolder(caller) || isCallerSystemSupervisionRoleHolder(caller)); return getFinancedDeviceKioskRoleHolderOnAnyUser() != null; - }; + } @Override public String getFinancedDeviceKioskRoleHolder(String callerPackageName) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index b3c8408ff54b..be4eea42f09e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -682,6 +682,19 @@ class Owners { } } + void markMemoryTaggingMigrated() { + synchronized (mData) { + mData.mMemoryTaggingMigrated = true; + mData.writeDeviceOwner(); + } + } + + boolean isMemoryTaggingMigrated() { + synchronized (mData) { + return mData.mMemoryTaggingMigrated; + } + } + @GuardedBy("mData") void pushToAppOpsLocked() { if (!mSystemReady) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 10e43d955fab..1cae924a8cd1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -93,6 +93,9 @@ class OwnersData { private static final String ATTR_SUSPENDED_PACKAGES_MIGRATED = "suspendedPackagesMigrated"; private static final String ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED = "resetPasswordWithTokenMigrated"; + private static final String ATTR_MEMORY_TAGGING_MIGRATED = + "memoryTaggingMigrated"; + private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade"; // Internal state for the device owner package. @@ -125,6 +128,7 @@ class OwnersData { boolean mRequiredPasswordComplexityMigrated = false; boolean mSuspendedPackagesMigrated = false; boolean mResetPasswordWithTokenMigrated = false; + boolean mMemoryTaggingMigrated = false; boolean mPoliciesMigratedPostUpdate = false; @@ -416,6 +420,8 @@ class OwnersData { if (Flags.unmanagedModeMigration()) { out.attributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, mRequiredPasswordComplexityMigrated); + } + if (Flags.suspendPackagesCoexistence()) { out.attributeBoolean(null, ATTR_SUSPENDED_PACKAGES_MIGRATED, mSuspendedPackagesMigrated); @@ -424,6 +430,10 @@ class OwnersData { out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, mResetPasswordWithTokenMigrated); } + if (Flags.setMtePolicyCoexistence()) { + out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED, + mMemoryTaggingMigrated); + } out.endTag(null, TAG_POLICY_ENGINE_MIGRATION); } @@ -491,13 +501,15 @@ class OwnersData { mRequiredPasswordComplexityMigrated = Flags.unmanagedModeMigration() && parser.getAttributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, false); - mSuspendedPackagesMigrated = Flags.unmanagedModeMigration() + mSuspendedPackagesMigrated = Flags.suspendPackagesCoexistence() && parser.getAttributeBoolean(null, ATTR_SUSPENDED_PACKAGES_MIGRATED, false); mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence() && parser.getAttributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false); - + mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence() + && parser.getAttributeBoolean(null, + ATTR_MEMORY_TAGGING_MIGRATED, false); break; default: Slog.e(TAG, "Unexpected tag: " + tag); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 6cb1756f93eb..f1711f5f8c0b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -337,6 +337,13 @@ final class PolicyDefinition<V> { PolicyEnforcerCallbacks::noOp, new PackageSetPolicySerializer()); + static PolicyDefinition<Integer> MEMORY_TAGGING = new PolicyDefinition<>( + new NoArgsPolicyKey( + DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY), + new TopPriority<>(List.of(EnforcingAdmin.DPC_AUTHORITY)), + PolicyEnforcerCallbacks::setMtePolicy, + new IntegerPolicySerializer()); + private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>(); private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>(); @@ -383,6 +390,8 @@ final class PolicyDefinition<V> { PASSWORD_COMPLEXITY); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY, PACKAGES_SUSPENDED); + POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY, + MEMORY_TAGGING); // User Restriction Policies USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 145416215cb0..fdc0ec1a0471 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -47,6 +47,7 @@ import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.UserHandle; import android.permission.AdminPermissionControlParams; import android.permission.PermissionControllerManager; @@ -210,6 +211,7 @@ final class PolicyEnforcerCallbacks { private static class BlockingCallback { private final CountDownLatch mLatch = new CountDownLatch(1); private final AtomicReference<Boolean> mValue = new AtomicReference<>(); + public void trigger(Boolean value) { mValue.set(value); mLatch.countDown(); @@ -435,4 +437,43 @@ final class PolicyEnforcerCallbacks { return AndroidFuture.completedFuture(true); }); } + + static CompletableFuture<Boolean> setMtePolicy( + @Nullable Integer mtePolicy, @NonNull Context context, int userId, + @NonNull PolicyKey policyKey) { + if (mtePolicy == null) { + mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; + } + final Set<Integer> allowedModes = + Set.of( + DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY, + DevicePolicyManager.MTE_DISABLED, + DevicePolicyManager.MTE_ENABLED); + if (!allowedModes.contains(mtePolicy)) { + Slog.wtf(LOG_TAG, "MTE policy is not a known one: " + mtePolicy); + return AndroidFuture.completedFuture(false); + } + + final String mteDpmSystemProperty = + "ro.arm64.memtag.bootctl_device_policy_manager"; + final String mteSettingsSystemProperty = + "ro.arm64.memtag.bootctl_settings_toggle"; + final String mteControlProperty = "arm64.memtag.bootctl"; + + final boolean isAvailable = SystemProperties.getBoolean(mteDpmSystemProperty, + SystemProperties.getBoolean(mteSettingsSystemProperty, false)); + if (!isAvailable) { + return AndroidFuture.completedFuture(false); + } + + if (mtePolicy == DevicePolicyManager.MTE_ENABLED) { + SystemProperties.set(mteControlProperty, "memtag"); + } else if (mtePolicy == DevicePolicyManager.MTE_DISABLED) { + SystemProperties.set(mteControlProperty, "memtag-off"); + } else if (mtePolicy == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) { + SystemProperties.set(mteControlProperty, "default"); + } + + return AndroidFuture.completedFuture(true); + } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index a804f24acc8f..c30ab738b098 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -101,13 +101,13 @@ public final class InputMethodSubtypeSwitchingControllerTest { TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID, TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME); if (subtypes == null) { - items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, - NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE)); + items.add(new ImeSubtypeListItem(imeName, null /* subtypeName */, null /* layoutName */, + imi, NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE)); } else { for (int i = 0; i < subtypes.size(); ++i) { final String subtypeLocale = subtypeLocales.get(i); - items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, - SYSTEM_LOCALE)); + items.add(new ImeSubtypeListItem(imeName, subtypeLocale, null /* layoutName */, + imi, i, subtypeLocale, SYSTEM_LOCALE)); } } } @@ -138,8 +138,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME, TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID, TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, TEST_IS_VR_IME); - return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale, - SYSTEM_LOCALE); + return new ImeSubtypeListItem(imeName, subtypeName, null /* layoutName */, imi, + subtypeIndex, subtypeLocale, SYSTEM_LOCALE); } @NonNull diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java index 3ac7fb0dbe53..e0b0fec380dd 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java @@ -335,6 +335,10 @@ public class VirtualDisplayAdapterTest { @Override public void onStopped() { } + + @Override + public void onRequestedBrightnessChanged(float brightness) { + } }; } } diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index c81d6be43223..9a300fb3c9fd 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. java_defaults { - name: "FrameworkMockingServicesTests-jni-defaults", + name: "FrameworksMockingServicesTests-jni-defaults", jni_libs: [ "libmockingservicestestjni", ], @@ -30,7 +30,7 @@ package { android_test { name: "FrameworksMockingServicesTests", defaults: [ - "FrameworkMockingServicesTests-jni-defaults", + "FrameworksMockingServicesTests-jni-defaults", "modules-utils-testable-device-config-defaults", ], diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index cbc8538cf9fb..37d1c30f76f5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -15,7 +15,12 @@ */ package com.android.server; +import static android.os.PowerExemptionManager.REASON_OTHER; +import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT; + import static androidx.test.InstrumentationRegistry.getContext; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -31,6 +36,7 @@ import static com.android.server.DeviceIdleController.LIGHT_STATE_INACTIVE; import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE; import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK; import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS; +import static com.android.server.DeviceIdleController.MSG_TEMP_APP_WHITELIST_TIMEOUT; import static com.android.server.DeviceIdleController.STATE_ACTIVE; import static com.android.server.DeviceIdleController.STATE_IDLE; import static com.android.server.DeviceIdleController.STATE_IDLE_MAINTENANCE; @@ -41,6 +47,7 @@ import static com.android.server.DeviceIdleController.STATE_QUICK_DOZE_DELAY; import static com.android.server.DeviceIdleController.STATE_SENSING; import static com.android.server.DeviceIdleController.lightStateToString; import static com.android.server.DeviceIdleController.stateToString; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -83,6 +90,8 @@ import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.SystemClock; import android.os.WearModeManagerInternal; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; @@ -90,12 +99,16 @@ import android.telephony.emergency.EmergencyNumber; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.app.IBatteryStats; +import com.android.server.am.BatteryStatsService; import com.android.server.deviceidle.ConstraintController; +import com.android.server.deviceidle.Flags; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; 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; @@ -115,6 +128,9 @@ import java.util.concurrent.Executor; @SuppressWarnings("GuardedBy") @RunWith(AndroidJUnit4.class) public class DeviceIdleControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT); + private DeviceIdleController mDeviceIdleController; private DeviceIdleController.MyHandler mHandler; private AnyMotionDetectorForTest mAnyMotionDetector; @@ -157,7 +173,8 @@ public class DeviceIdleControllerTest { LocationManager locationManager; ConstraintController constraintController; // Freeze time for testing. - long nowElapsed; + volatile long nowElapsed; + volatile long nowUptime; boolean useMotionSensor = true; boolean isLocationPrefetchEnabled = true; @@ -193,6 +210,11 @@ public class DeviceIdleControllerTest { } @Override + long getUptimeMillis() { + return nowUptime; + } + + @Override LocationManager getLocationManager() { return locationManager; } @@ -314,11 +336,13 @@ public class DeviceIdleControllerTest { mMockingSession = mockitoSession() .initMocks(this) .strictness(Strictness.LENIENT) + .mockStatic(BatteryStatsService.class) .spyStatic(DeviceConfig.class) .spyStatic(LocalServices.class) .startMocking(); spyOn(getContext()); doReturn(null).when(getContext()).registerReceiver(any(), any()); + doReturn(mock(IBatteryStats.class)).when(() -> BatteryStatsService.getService()); doReturn(mock(ActivityManagerInternal.class)) .when(() -> LocalServices.getService(ActivityManagerInternal.class)); doReturn(mock(ActivityTaskManagerInternal.class)) @@ -401,6 +425,46 @@ public class DeviceIdleControllerTest { } @Test + @EnableFlags(Flags.FLAG_USE_CPU_TIME_FOR_TEMP_ALLOWLIST) + public void testTempAllowlistCountsUptime() { + doNothing().when(getContext()).sendBroadcastAsUser(any(), any(), any(), any()); + final int testUid = 12345; + final long durationMs = 4300; + final long startTime = 100; // Arbitrary starting point in time. + mInjector.nowUptime = mInjector.nowElapsed = startTime; + + mDeviceIdleController.addPowerSaveTempWhitelistAppDirectInternal(0, testUid, durationMs, + TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, true, REASON_OTHER, "test"); + + assertEquals(startTime + durationMs, + mDeviceIdleController.mTempWhitelistAppIdEndTimes.get(testUid).first.value); + + final InOrder inorder = inOrder(mHandler); + // mHandler is already stubbed to do nothing on handleMessage. + inorder.verify(mHandler).sendMessageDelayed( + argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid), + eq(durationMs)); + + mInjector.nowElapsed += durationMs; + mInjector.nowUptime += 2; + // Elapsed time moved past the expiration but not uptime. The check should be rescheduled. + mDeviceIdleController.checkTempAppWhitelistTimeout(testUid); + inorder.verify(mHandler).sendMessageDelayed( + argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid), + eq(durationMs - 2)); + assertEquals(startTime + durationMs, + mDeviceIdleController.mTempWhitelistAppIdEndTimes.get(testUid).first.value); + + mInjector.nowUptime += durationMs; + // Uptime moved past the expiration time. Uid should be removed from the temp allowlist. + mDeviceIdleController.checkTempAppWhitelistTimeout(testUid); + inorder.verify(mHandler, never()).sendMessageDelayed( + argThat(m -> m.what == MSG_TEMP_APP_WHITELIST_TIMEOUT && m.arg1 == testUid), + anyLong()); + assertFalse(mDeviceIdleController.mTempWhitelistAppIdEndTimes.contains(testUid)); + } + + @Test public void testUpdateInteractivityLocked() { doReturn(false).when(mPowerManager).isInteractive(); mDeviceIdleController.updateInteractivityLocked(); diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java index 197342874b2a..f7c2e8b72d6b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java @@ -21,6 +21,7 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION; import static org.junit.Assert.assertEquals; +import android.app.PropertyInvalidatedCache; import android.content.Context; import android.os.Handler; @@ -63,6 +64,7 @@ public class AppOpsLegacyRestrictionsTest { @Before public void setUp() { + PropertyInvalidatedCache.disableForTestMode(); mSession = ExtendedMockito.mockitoSession() .initMocks(this) .strictness(Strictness.LENIENT) 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 4e1f741b1398..dd7ce21e3628 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -2351,6 +2351,7 @@ public class JobSchedulerServiceTest { /** Tests that jobs are removed from the pending list if the user stops the app. */ @Test + @RequiresFlagsDisabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API) public void testUserStopRemovesPending() { spyOn(mService); @@ -2402,6 +2403,60 @@ public class JobSchedulerServiceTest { assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b)); } + /** Tests that jobs are removed from the pending list if the user stops the app. */ + @Test + @RequiresFlagsEnabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API) + public void testUserStopRemovesPending_withPendingJobReasonsApi() { + spyOn(mService); + + JobStatus job1a = createJobStatus("testUserStopRemovesPending", + createJobInfo(1), 1, "pkg1"); + JobStatus job1b = createJobStatus("testUserStopRemovesPending", + createJobInfo(2), 1, "pkg1"); + JobStatus job2a = createJobStatus("testUserStopRemovesPending", + createJobInfo(1), 2, "pkg2"); + JobStatus job2b = createJobStatus("testUserStopRemovesPending", + createJobInfo(2), 2, "pkg2"); + doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0); + doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1); + doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0); + + mService.getPendingJobQueue().clear(); + mService.getPendingJobQueue().add(job1a); + mService.getPendingJobQueue().add(job1b); + mService.getPendingJobQueue().add(job2a); + mService.getPendingJobQueue().add(job2b); + mService.getJobStore().add(job1a); + mService.getJobStore().add(job1b); + mService.getJobStore().add(job2a); + mService.getJobStore().add(job2b); + + mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test"); + assertEquals(4, mService.getPendingJobQueue().size()); + assertTrue(mService.getPendingJobQueue().contains(job1a)); + assertTrue(mService.getPendingJobQueue().contains(job1b)); + assertTrue(mService.getPendingJobQueue().contains(job2a)); + assertTrue(mService.getPendingJobQueue().contains(job2b)); + + mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test"); + assertEquals(2, mService.getPendingJobQueue().size()); + assertFalse(mService.getPendingJobQueue().contains(job1a)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1a)[0]); + assertFalse(mService.getPendingJobQueue().contains(job1b)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1b)[0]); + assertTrue(mService.getPendingJobQueue().contains(job2a)); + assertTrue(mService.getPendingJobQueue().contains(job2b)); + + mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test"); + assertEquals(0, mService.getPendingJobQueue().size()); + assertFalse(mService.getPendingJobQueue().contains(job1a)); + assertFalse(mService.getPendingJobQueue().contains(job1b)); + assertFalse(mService.getPendingJobQueue().contains(job2a)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2a)[0]); + assertFalse(mService.getPendingJobQueue().contains(job2b)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2b)[0]); + } + /** * Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with single {@link * JobRestriction} registered. diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 1cba3c574543..8a10040f986f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -195,12 +195,12 @@ public final class UserManagerServiceTest { doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any()); mockIsLowRamDevice(false); - // Called when getting boot user. config_bootToHeadlessSystemUser is false by default. + // Called when getting boot user. config_bootToHeadlessSystemUser is 0 by default. mSpyResources = spy(mSpiedContext.getResources()); when(mSpiedContext.getResources()).thenReturn(mSpyResources); - doReturn(false) + doReturn(0) .when(mSpyResources) - .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser); + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); // Must construct UserManagerService in the UiThread mTestDir = new File(mRealContext.getDataDir(), "umstest"); @@ -859,15 +859,50 @@ public final class UserManagerServiceTest { } @Test - public void testGetBootUser_enableBootToHeadlessSystemUser() { + public void testGetBootUser_Headless_BootToSystemUserWhenDeviceIsProvisioned() { setSystemUserHeadless(true); - doReturn(true) + addUser(USER_ID); + addUser(OTHER_USER_ID); + mockProvisionedDevice(true); + doReturn(1) .when(mSpyResources) - .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser); + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM); } + @Test + public void testGetBootUser_Headless_BootToFirstSwitchableFullUserWhenDeviceNotProvisioned() { + setSystemUserHeadless(true); + addUser(USER_ID); + addUser(OTHER_USER_ID); + mockProvisionedDevice(false); + doReturn(1) + .when(mSpyResources) + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); + // Even if the headless system user switchable flag is true, the boot user should be the + // first switchable full user. + doReturn(true) + .when(mSpyResources) + .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser); + + assertThat(mUms.getBootUser()).isEqualTo(USER_ID); + } + + @Test + public void testGetBootUser_Headless_ThrowsIfBootFailsNoFullUserWhenDeviceNotProvisioned() + throws Exception { + setSystemUserHeadless(true); + removeNonSystemUsers(); + mockProvisionedDevice(false); + doReturn(1) + .when(mSpyResources) + .getInteger(com.android.internal.R.integer.config_hsumBootStrategy); + + assertThrows(ServiceSpecificException.class, + () -> mUms.getBootUser()); + } + /** * Returns true if the user's XML file has Default restrictions * @param userId Id of the user. @@ -935,6 +970,11 @@ public final class UserManagerServiceTest { any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt())); } + private void mockProvisionedDevice(boolean isProvisionedDevice) { + doReturn(isProvisionedDevice ? 1 : 0).when(() -> Settings.Global.getInt( + any(), eq(android.provider.Settings.Global.DEVICE_PROVISIONED), anyInt())); + } + private void mockIsLowRamDevice(boolean isLowRamDevice) { doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java index f02a389a160e..d83dc110800a 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java @@ -67,6 +67,7 @@ import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; import com.android.internal.os.KernelSingleUidTimeReader; import com.android.internal.os.LongArrayMultiStateCounter; +import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import com.android.server.power.feature.flags.Flags; @@ -120,6 +121,7 @@ public class BatteryStatsImplTest { }}); private final MockClock mMockClock = new MockClock(); + private final MonotonicClock mMonotonicClock = new MonotonicClock(777666, mMockClock); private MockBatteryStatsImpl mBatteryStatsImpl; private Handler mHandler; private PowerStatsStore mPowerStatsStore; @@ -160,7 +162,7 @@ public class BatteryStatsImplTest { mPowerStatsStore = new PowerStatsStore(systemDir, mHandler); mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerAttributor, mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore, 0, - mMockClock); + mMockClock, mMonotonicClock); } @Test diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java index 813dd841b2b9..5d50e6c9f715 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java @@ -191,7 +191,7 @@ public class BatteryUsageStatsAtomTest { "cpu", 1650.0f, 9300.0f, - 8400L + 8300L ); verify(statsLogger).buildStatsEvent( 1000L, @@ -205,7 +205,7 @@ public class BatteryUsageStatsAtomTest { "cpu", 1650.0f, 9400.0f, - 0L + 8400L ); verify(statsLogger).buildStatsEvent( 1000L, @@ -502,17 +502,17 @@ public class BatteryUsageStatsAtomTest { .setPackageWithHighestDrain("myPackage0") .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1000) .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 2000) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_SCREEN, 300) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_CPU, 400) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 450) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 500) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.POWER_COMPONENT_CPU, 600) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1, 800); final BatteryConsumer.Key keyFg = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU, @@ -524,14 +524,14 @@ public class BatteryUsageStatsAtomTest { final BatteryConsumer.Key keyCached = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_CACHED); - uidBuilder.setConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(keyFg, 8100) - .setConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) - .setUsageDurationMillis(keyBg, 8200) - .setConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) - .setUsageDurationMillis(keyFgs, 8300) - .setConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) - .setUsageDurationMillis(keyFgs, 8400); + uidBuilder.addConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE) + .addUsageDurationMillis(keyFg, 8100) + .addConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) + .addUsageDurationMillis(keyBg, 8200) + .addConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) + .addUsageDurationMillis(keyFgs, 8300) + .addConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) + .addUsageDurationMillis(keyCached, 8400); final BatteryConsumer.Key keyCustomFg = uidBuilder.getKey( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, @@ -539,9 +539,9 @@ public class BatteryUsageStatsAtomTest { final BatteryConsumer.Key keyCustomBg = uidBuilder.getKey( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, BatteryConsumer.PROCESS_STATE_BACKGROUND); - uidBuilder.setConsumedPower( + uidBuilder.addConsumedPower( keyCustomFg, 100, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION); - uidBuilder.setConsumedPower( + uidBuilder.addConsumedPower( keyCustomBg, 350, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION); builder.getOrCreateUidBatteryConsumerBuilder(UID_1) @@ -549,36 +549,36 @@ public class BatteryUsageStatsAtomTest { .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 1234); builder.getOrCreateUidBatteryConsumerBuilder(UID_2) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 766); builder.getOrCreateUidBatteryConsumerBuilder(UID_3); builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) - .setConsumedPower(30000) - .setConsumedPower( + .addConsumedPower(30000) + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_CPU, 20100, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_AUDIO, 0, BatteryConsumer.POWER_MODEL_POWER_PROFILE) // Empty - .setConsumedPower( + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_CAMERA, 20150, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.POWER_COMPONENT_CPU, 20300) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20400); // Not used; just to make sure extraneous data doesn't mess things up. builder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_CPU, 10100, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200); return builder.build(); @@ -596,8 +596,8 @@ public class BatteryUsageStatsAtomTest { BatteryConsumer.PROCESS_STATE_FOREGROUND, 1 * 60 * 1000) .setTimeInProcessStateMs( BatteryConsumer.PROCESS_STATE_BACKGROUND, 2 * 60 * 1000) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40); + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30) + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40); } // Add a UID with much larger battery footprint @@ -605,16 +605,16 @@ public class BatteryUsageStatsAtomTest { builder.getOrCreateUidBatteryConsumerBuilder(largeConsumerUid) .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 10 * 60 * 1000) .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 20 * 60 * 1000) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400); + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300) + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400); // Add a UID with much larger usage duration final int highUsageUid = 3002; builder.getOrCreateUidBatteryConsumerBuilder(highUsageUid) .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND, 60 * 60 * 1000) .setTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND, 120 * 60 * 1000) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4); + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3) + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4); BatteryUsageStats batteryUsageStats = builder.build(); final byte[] bytes = batteryUsageStats.getStatsProto(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index 8239e0955032..709f83ba907d 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java @@ -47,6 +47,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BatteryStatsHistoryIterator; +import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import com.android.server.power.stats.processor.MultiStatePowerAttributor; @@ -80,6 +81,7 @@ public class BatteryUsageStatsProviderTest { .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0); private MockClock mMockClock = mStatsRule.getMockClock(); + private MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock); private Context mContext; @Before @@ -146,7 +148,8 @@ public class BatteryUsageStatsProviderTest { BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, mock(PowerAttributor.class), mStatsRule.getPowerProfile(), - mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock); + mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock, + mMonotonicClock); final BatteryUsageStats batteryUsageStats = provider.getBatteryUsageStats(batteryStats, @@ -273,7 +276,8 @@ public class BatteryUsageStatsProviderTest { BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, powerAttributor, mStatsRule.getPowerProfile(), - mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock); + mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock, + mMonotonicClock); return provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT); } @@ -303,7 +307,8 @@ public class BatteryUsageStatsProviderTest { BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, mock(PowerAttributor.class), mStatsRule.getPowerProfile(), - mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock); + mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock, + mMonotonicClock); final BatteryUsageStats batteryUsageStats = provider.getBatteryUsageStats(batteryStats, @@ -396,7 +401,8 @@ public class BatteryUsageStatsProviderTest { BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, mock(PowerAttributor.class), mStatsRule.getPowerProfile(), - mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock); + mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock, + mMonotonicClock); final BatteryUsageStats batteryUsageStats = provider.getBatteryUsageStats(batteryStats, @@ -487,7 +493,8 @@ public class BatteryUsageStatsProviderTest { BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, mock(PowerAttributor.class), mStatsRule.getPowerProfile(), - mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock); + mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock, + mMonotonicClock); batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore, /* accumulateBatteryUsageStats */ false); @@ -590,7 +597,10 @@ public class BatteryUsageStatsProviderTest { private void accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize, int expectedNumberOfUpdates) throws Throwable { - BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + BatteryStatsImpl batteryStats = spy(mStatsRule.getBatteryStats()); + // Note - these two are in microseconds + when(batteryStats.computeBatteryTimeRemaining(anyLong())).thenReturn(111_000L); + when(batteryStats.computeChargeTimeRemaining(anyLong())).thenReturn(777_000L); batteryStats.forceRecordAllHistory(); setTime(5 * MINUTE_IN_MS); @@ -623,7 +633,7 @@ public class BatteryUsageStatsProviderTest { BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, powerAttributor, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore, - accumulatedBatteryUsageStatsSpanSize, mMockClock); + accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock); provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler()); @@ -677,9 +687,14 @@ public class BatteryUsageStatsProviderTest { BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, new BatteryUsageStatsQuery.Builder().accumulated().build()); + assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS); + assertThat(stats.getBatteryTimeRemainingMs()).isEqualTo(111); + assertThat(stats.getChargeTimeRemainingMs()).isEqualTo(777); + assertThat(stats.getBatteryCapacity()).isEqualTo(4000); // from PowerProfile + // Total: 10 + 20 + 30 = 60 assertThat(stats.getAggregateBatteryConsumer( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) @@ -729,7 +744,8 @@ public class BatteryUsageStatsProviderTest { BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, mock(PowerAttributor.class), mStatsRule.getPowerProfile(), - mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock); + mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock, + mMonotonicClock); PowerStatsStore powerStatsStore = mock(PowerStatsStore.class); doAnswer(invocation -> { @@ -796,7 +812,8 @@ public class BatteryUsageStatsProviderTest { BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, mock(PowerAttributor.class), mStatsRule.getPowerProfile(), - mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock); + mStatsRule.getCpuScalingPolicies(), powerStatsStore, 0, mMockClock, + mMonotonicClock); BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() .aggregateSnapshots(0, 3000) diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java index 1b6b8c49461e..9771da590e37 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java @@ -119,7 +119,7 @@ public class BatteryUsageStatsTest { BatteryStatsImpl.Uid mockUid = mock(BatteryStatsImpl.Uid.class); when(mockUid.getUid()).thenReturn(i); builder.getOrCreateUidBatteryConsumerBuilder(mockUid) - .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, i * 100); + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, i * 100); } BatteryUsageStats outBatteryUsageStats = builder.build(); @@ -355,13 +355,13 @@ public class BatteryUsageStatsTest { if (includeUserBatteryConsumer) { builder.getOrCreateUserBatteryConsumerBuilder(USER_ID) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_CPU, 10) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.POWER_COMPONENT_CPU, 30) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 40); } return builder; @@ -422,15 +422,15 @@ public class BatteryUsageStatsTest { .setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, timeInProcessStateBackground) .setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE, timeInProcessStateForegroundService) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_CPU, cpuPower, cpuPowerModel) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration); if (builder.isProcessStateDataNeeded()) { final BatteryConsumer.Key cpuFgKey = builder.isScreenStateDataNeeded() @@ -461,21 +461,21 @@ public class BatteryUsageStatsTest { BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, BatteryConsumer.PROCESS_STATE_BACKGROUND); uidBuilder - .setConsumedPower(cpuFgKey, cpuPowerForeground, + .addConsumedPower(cpuFgKey, cpuPowerForeground, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuFgKey, cpuDurationForeground) - .setConsumedPower(cpuBgKey, cpuPowerBackground, + .addUsageDurationMillis(cpuFgKey, cpuDurationForeground) + .addConsumedPower(cpuBgKey, cpuPowerBackground, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuBgKey, cpuDurationBackground) - .setConsumedPower(cpuFgsKey, cpuPowerFgs, + .addUsageDurationMillis(cpuBgKey, cpuDurationBackground) + .addConsumedPower(cpuFgsKey, cpuPowerFgs, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuFgsKey, cpuDurationFgs) - .setConsumedPower(cachedKey, cpuPowerCached, + .addUsageDurationMillis(cpuFgsKey, cpuDurationFgs) + .addConsumedPower(cachedKey, cpuPowerCached, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cachedKey, cpuDurationCached) - .setConsumedPower(customBgKey, customComponentPower, + .addUsageDurationMillis(cachedKey, cpuDurationCached) + .addConsumedPower(customBgKey, customComponentPower, BatteryConsumer.POWER_MODEL_UNDEFINED) - .setUsageDurationMillis(customBgKey, customComponentDuration); + .addUsageDurationMillis(customBgKey, customComponentDuration); } } @@ -486,15 +486,15 @@ public class BatteryUsageStatsTest { long cpuDurationChgScrOn, double cpuPowerChgScrOff, long cpuDurationChgScrOff) { final AggregateBatteryConsumer.Builder aggBuilder = builder.getAggregateBatteryConsumerBuilder(scope) - .setConsumedPower(consumedPower) - .setConsumedPower( + .addConsumedPower(consumedPower) + .addConsumedPower( BatteryConsumer.POWER_COMPONENT_CPU, cpuPower) - .setConsumedPower( + .addConsumedPower( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.POWER_COMPONENT_CPU, cpuDuration) - .setUsageDurationMillis( + .addUsageDurationMillis( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration); if (builder.isPowerStateDataNeeded() || builder.isScreenStateDataNeeded()) { @@ -519,18 +519,18 @@ public class BatteryUsageStatsTest { BatteryConsumer.SCREEN_STATE_OTHER, BatteryConsumer.POWER_STATE_OTHER); aggBuilder - .setConsumedPower(cpuBatScrOn, cpuPowerBatScrOn, + .addConsumedPower(cpuBatScrOn, cpuPowerBatScrOn, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn) - .setConsumedPower(cpuBatScrOff, cpuPowerBatScrOff, + .addUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn) + .addConsumedPower(cpuBatScrOff, cpuPowerBatScrOff, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff) - .setConsumedPower(cpuChgScrOn, cpuPowerChgScrOn, + .addUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff) + .addConsumedPower(cpuChgScrOn, cpuPowerChgScrOn, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn) - .setConsumedPower(cpuChgScrOff, cpuPowerChgScrOff, + .addUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn) + .addConsumedPower(cpuChgScrOff, cpuPowerChgScrOff, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff); + .addUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff); } } diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 359755a7ad08..5b35af1de88d 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -157,21 +157,49 @@ android_test { resource_zips: [":FrameworksServicesTests_apks_as_resources"], } -android_ravenwood_test { - name: "FrameworksServicesTestsRavenwood", +java_defaults { + name: "FrameworksServicesTestsRavenwood-defaults", libs: [ "android.test.mock.stubs.system", ], static_libs: [ "androidx.annotation_annotation", "androidx.test.rules", - "services.core", "flag-junit", ], + auto_gen_config: true, +} + +// Unit tests for UriGrantManager, running on ravenwood. +// Note UriGrantManager does not support Ravenwood (yet). We're just running the original +// unit tests as is on Ravenwood. So here, we use the original "services.core", because +// "services.core.ravenwood" doesn't have the target code. +// (Compare to FrameworksServicesTestsRavenwood_Compat, which does support Ravenwood.) +android_ravenwood_test { + name: "FrameworksServicesTestsRavenwood_Uri", + defaults: ["FrameworksServicesTestsRavenwood-defaults"], + team: "trendy_team_ravenwood", + static_libs: [ + "services.core", + ], srcs: [ "src/com/android/server/uri/**/*.java", ], - auto_gen_config: true, +} + +// Unit tests for compat-framework. +// Compat-framework does support Ravenwood, and it uses the ravenwood anottations, +// so we link "services.core.ravenwood". +android_ravenwood_test { + name: "FrameworksServicesTestsRavenwood_Compat", + defaults: ["FrameworksServicesTestsRavenwood-defaults"], + team: "trendy_team_ravenwood", + static_libs: [ + "services.core.ravenwood", + ], + srcs: [ + "src/com/android/server/compat/**/*.java", + ], } java_library { diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java index c4b3c149bd8d..5d7ffe91e67d 100644 --- a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import android.app.AppOpsManager; +import android.app.PropertyInvalidatedCache; import android.companion.virtual.VirtualDeviceManager; import android.content.Context; import android.os.FileUtils; @@ -69,6 +70,7 @@ public class AppOpsRecentAccessPersistenceTest { @Before public void setUp() { + PropertyInvalidatedCache.disableForTestMode(); when(mAppOpCheckingService.addAppOpsModeChangedListener(any())).thenReturn(true); LocalServices.addService(AppOpsCheckingServiceInterface.class, mAppOpCheckingService); diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java index c9e9f00985f1..c418151b9946 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.audio; import static android.media.AudioDeviceInfo.TYPE_BUILTIN_MIC; +import static android.media.AudioManager.GET_DEVICES_INPUTS; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -34,7 +35,9 @@ import android.media.AudioSystem; import android.os.Looper; import android.os.PermissionEnforcer; import android.os.UserHandle; +import android.os.test.TestLooper; import android.platform.test.annotations.EnableFlags; +import android.util.IntArray; import android.util.Log; import androidx.test.InstrumentationRegistry; @@ -80,12 +83,15 @@ public class AudioServiceTest { private static boolean sLooperPrepared = false; + private TestLooper mTestLooper; + @Before public void setUp() throws Exception { if (!sLooperPrepared) { Looper.prepare(); sLooperPrepared = true; } + mTestLooper = new TestLooper(); mContext = InstrumentationRegistry.getTargetContext(); mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSettingsAdapter = new NoOpSettingsAdapter(); @@ -93,8 +99,11 @@ public class AudioServiceTest { when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString())) .thenReturn(AppOpsManager.MODE_ALLOWED); mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer, - mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null, - mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run()); + mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, + mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer, + mMockPermissionProvider, r -> r.run()); + + mTestLooper.dispatchAll(); } /** @@ -216,7 +225,19 @@ public class AudioServiceTest { public void testInputGainIndex() throws Exception { Log.i(TAG, "running testInputGainIndex"); Assert.assertNotNull(mAudioService); - Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); // wait for full AudioService initialization + + IntArray internalDeviceTypes = new IntArray(); + int status = AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS, internalDeviceTypes); + if (status != AudioSystem.SUCCESS) { + Log.e(TAG, "AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS) failed. status:" + + status); + } + + // Make sure TYPE_BUILTIN_MIC, aka DEVICE_IN_BUILTIN_MIC in terms of internal device type, + // is supported. + if (!internalDeviceTypes.contains(AudioSystem.DEVICE_IN_BUILTIN_MIC)) { + return; + } AudioDeviceAttributes ada = new AudioDeviceAttributes( @@ -229,6 +250,8 @@ public class AudioServiceTest { int inputGainIndex = 20; mAudioService.setInputGainIndex(ada, inputGainIndex); + mTestLooper.dispatchAll(); + Assert.assertEquals( "input gain index reporting wrong value", inputGainIndex, diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index 36b163ec84b6..3d695a68f1f9 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -18,12 +18,12 @@ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; import android.app.compat.ChangeIdStateCache; import android.app.compat.PackageOverride; diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index 1d075401832d..95d601f69344 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -18,6 +18,7 @@ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; @@ -26,9 +27,9 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; -import static org.testng.Assert.assertThrows; import android.compat.Compatibility.ChangeConfig; +import android.content.AttributionSource; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -39,6 +40,8 @@ import android.os.Process; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; +import android.os.PermissionEnforcer; +import android.permission.PermissionCheckerManager; import androidx.test.runner.AndroidJUnit4; @@ -90,6 +93,22 @@ public class PlatformCompatTest { .thenReturn(-1); when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) .thenThrow(new PackageManager.NameNotFoundException()); + + var allGrantingPermissionEnforcer = new PermissionEnforcer() { + @Override + protected int checkPermission(String permission, AttributionSource source) { + return PermissionCheckerManager.PERMISSION_GRANTED; + } + + @Override + protected int checkPermission(String permission, int pid, int uid) { + return PermissionCheckerManager.PERMISSION_GRANTED; + } + }; + + when(mContext.getSystemService(eq(Context.PERMISSION_ENFORCER_SERVICE))) + .thenReturn(allGrantingPermissionEnforcer); + mCompatConfig = new CompatConfig(mBuildClassifier, mContext); mPlatformCompat = new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter); diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java index 24abc183cad1..f5494534716a 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java @@ -61,7 +61,6 @@ import android.os.Process; import android.os.UserHandle; import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; -import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArraySet; import org.junit.Before; @@ -77,9 +76,6 @@ import java.util.Set; @RunWith(Parameterized.class) public class UriGrantsManagerServiceTest { - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - /** * Why this class needs to test all combinations of * {@link android.security.Flags#FLAG_CONTENT_URI_PERMISSION_APIS}: diff --git a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java index 611c51463246..fe66f738487d 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java @@ -37,18 +37,13 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import android.os.SystemClock; -import android.platform.test.ravenwood.RavenwoodRule; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class UriPermissionTest { - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - @Mock private UriGrantsManagerInternal mService; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index 22a4f85758eb..0b89c11a11f4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -34,10 +34,12 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION; import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING; +import static android.service.notification.Flags.FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS; +import static com.android.server.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS; import static com.android.server.notification.GroupHelper.AGGREGATE_GROUP_KEY; import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY; import static com.android.server.notification.GroupHelper.BASE_FLAGS; @@ -2217,6 +2219,7 @@ public class GroupHelperTest extends UiServiceTestCase { @Test @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING) + @DisableFlags(FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION) public void testMoveAggregateGroups_updateChannel_multipleChannels() { final String pkg = "package"; final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg, @@ -2265,16 +2268,17 @@ public class GroupHelperTest extends UiServiceTestCase { mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1, notificationList); - // Check that channel1's notifications are moved to the silent section group - // But not enough to auto-group => remove override group key - verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), - anyString(), anyInt(), any()); - verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean()); + // Check that the override group key was cleared for (NotificationRecord record: notificationList) { if (record.getChannel().getId().equals(channel1.getId())) { assertThat(record.getSbn().getOverrideGroupKey()).isNull(); } } + // Check that channel1's notifications are moved to the silent section group + // and a group summary is created + notifications are added to the group + verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), anyString(), + anyInt(), any()); + verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean()); // Check that the alerting section group is not removed, only updated expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS, @@ -2287,6 +2291,357 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testMoveAggregateGroups_updateChannel_multipleChannels_regroupOnClassifEnabled() { + final String pkg = "package"; + final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg, + AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier()); + int numNotificationChannel1 = 0; + final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1", + "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT); + final NotificationChannel channel2 = new NotificationChannel("TEST_CHANNEL_ID2", + "TEST_CHANNEL_ID2", IMPORTANCE_DEFAULT); + final List<NotificationRecord> notificationList = new ArrayList<>(); + final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>(); + // Post notifications with different channels that autogroup within the same section + NotificationRecord r; + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + if (i % 2 == 0) { + r = getNotificationRecord(pkg, i, String.valueOf(i), + UserHandle.SYSTEM, "testGrp " + i, false, channel1); + numNotificationChannel1++; + } else { + r = getNotificationRecord(pkg, i, String.valueOf(i), + UserHandle.SYSTEM, "testGrp " + i, false, channel2); + } + notificationList.add(r); + mGroupHelper.onNotificationPostedWithDelay(r, notificationList, summaryByGroup); + } + NotificationAttributes expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS, + mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, + "TEST_CHANNEL_ID1"); + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(expectedGroupKey_alerting), anyInt(), eq(expectedSummaryAttr)); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), + eq(expectedGroupKey_alerting), eq(true)); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(), + any()); + Mockito.reset(mCallback); + + // Update channel1's importance + final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg, + AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier()); + channel1.setImportance(IMPORTANCE_LOW); + for (NotificationRecord record: notificationList) { + if (record.getChannel().getId().equals(channel1.getId())) { + record.updateNotificationChannel(channel1); + } + } + mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1, + notificationList); + + // Check that the override group key was cleared + for (NotificationRecord record: notificationList) { + if (record.getChannel().getId().equals(channel1.getId())) { + assertThat(record.getSbn().getOverrideGroupKey()).isNull(); + } + } + // Check that channel1's notifications are moved to the silent section group + // and a group summary is created + notifications are added to the group + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(expectedGroupKey_silent), anyInt(), any()); + verify(mCallback, times(numNotificationChannel1)).addAutoGroup(anyString(), + eq(expectedGroupKey_silent), anyBoolean()); + + // Check that the alerting section group is not removed, only updated + expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS, + mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, + "TEST_CHANNEL_ID2"); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), eq(pkg), + eq(expectedGroupKey_alerting)); + verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), eq(pkg), + eq(expectedGroupKey_alerting), eq(expectedSummaryAttr)); + } + + @Test + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testMoveSections_notificationBundled() { + final List<NotificationRecord> notificationList = new ArrayList<>(); + final String pkg = "package"; + final int summaryId = 0; + final int numChildNotif = 4; + + // Create an app-provided group: summary + child notifications + final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1", + "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT); + NotificationRecord summary = getNotificationRecord(pkg, summaryId, + String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp " + summaryId, + true, channel1); + notificationList.add(summary); + final String originalAppGroupKey = summary.getGroupKey(); + for (int i = 0; i < numChildNotif; i++) { + NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42), + UserHandle.SYSTEM, "testGrp " + summaryId, false, channel1); + notificationList.add(child); + } + + // Classify/bundle child notifications + final NotificationChannel socialChannel = new NotificationChannel( + NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID, + IMPORTANCE_DEFAULT); + final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg, + AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier()); + final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes( + BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, + NotificationChannel.SOCIAL_MEDIA_ID); + final NotificationChannel newsChannel = new NotificationChannel( + NotificationChannel.NEWS_ID, NotificationChannel.NEWS_ID, + IMPORTANCE_DEFAULT); + final String expectedGroupKey_news = GroupHelper.getFullAggregateGroupKey(pkg, + AGGREGATE_GROUP_KEY + "NewsSection", UserHandle.SYSTEM.getIdentifier()); + final NotificationAttributes expectedSummaryAttr_news = new NotificationAttributes( + BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, + NotificationChannel.NEWS_ID); + for (NotificationRecord record: notificationList) { + if (record.getChannel().getId().equals(channel1.getId()) + && record.getSbn().getId() % 2 == 0) { + record.updateNotificationChannel(socialChannel); + mGroupHelper.onChannelUpdated(record); + } + if (record.getChannel().getId().equals(channel1.getId()) + && record.getSbn().getId() % 2 != 0) { + record.updateNotificationChannel(newsChannel); + mGroupHelper.onChannelUpdated(record); + } + } + + // Check that 2 autogroup summaries were created for the news & social sections + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social)); + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(expectedGroupKey_news), anyInt(), eq(expectedSummaryAttr_news)); + // Check that half of the child notifications were grouped in each new section + verify(mCallback, times(numChildNotif / 2)).addAutoGroup(anyString(), + eq(expectedGroupKey_news), eq(true)); + verify(mCallback, times(numChildNotif / 2)).addAutoGroup(anyString(), + eq(expectedGroupKey_social), eq(true)); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString()); + verify(mCallback, times(numChildNotif / 2)).updateAutogroupSummary(anyInt(), anyString(), + anyString(), any()); + verify(mCallback, times(numChildNotif)).removeAppProvidedSummaryOnClassification( + anyString(), eq(originalAppGroupKey)); + } + + @Test + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testCacheAndCancelAppSummary_notificationBundled() { + // check that the original app summary is canceled & cached on classification regrouping + final List<NotificationRecord> notificationList = new ArrayList<>(); + final String pkg = "package"; + final int summaryId = 0; + final int numChildNotif = 4; + + // Create an app-provided group: summary + child notifications + final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1", + "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT); + NotificationRecord summary = getNotificationRecord(pkg, summaryId, + String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp " + summaryId, + true, channel1); + notificationList.add(summary); + final String originalAppGroupKey = summary.getGroupKey(); + final String originalAppGroupName = summary.getNotification().getGroup(); + for (int i = 0; i < numChildNotif; i++) { + NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42), + UserHandle.SYSTEM, "testGrp " + summaryId, false, channel1); + notificationList.add(child); + } + + // Last regrouped notification will trigger summary cancellation in NMS + when(mCallback.removeAppProvidedSummaryOnClassification(anyString(), + eq(originalAppGroupKey))).thenReturn(summary); + + // Classify/bundle child notifications + final NotificationChannel socialChannel = new NotificationChannel( + NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID, + IMPORTANCE_DEFAULT); + for (NotificationRecord record: notificationList) { + if (record.getChannel().getId().equals(channel1.getId())) { + record.updateNotificationChannel(socialChannel); + mGroupHelper.onChannelUpdated(record); + } + } + + // Check that the original app summary was cached + CachedSummary cachedSummary = mGroupHelper.findCanceledSummary(pkg, + String.valueOf(summaryId), summaryId, UserHandle.SYSTEM.getIdentifier()); + assertThat(cachedSummary.originalGroupKey()).isEqualTo(originalAppGroupName); + assertThat(cachedSummary.key()).isEqualTo(summary.getKey()); + + // App cancels the original summary + reset(mCallback); + mGroupHelper.maybeCancelGroupChildrenForCanceledSummary(pkg, String.valueOf(summaryId), + summaryId, UserHandle.SYSTEM.getIdentifier(), REASON_APP_CANCEL); + // Check that child notifications are removed and cache is cleared + verify(mCallback, times(1)).removeNotificationFromCanceledGroup( + eq(UserHandle.SYSTEM.getIdentifier()), eq(pkg), eq(originalAppGroupName), + eq(REASON_APP_CANCEL)); + cachedSummary = mGroupHelper.findCanceledSummary(pkg, String.valueOf(summaryId), summaryId, + UserHandle.SYSTEM.getIdentifier()); + assertThat(cachedSummary).isNull(); + } + + @Test + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION, + FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS}) + public void testSingletonGroupsRegrouped_notificationBundledBeforeDelayTimeout() { + // Check that singleton group notifications are regrouped if classification is done + // before onNotificationPostedWithDelay + final List<NotificationRecord> notificationList = new ArrayList<>(); + final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>(); + final String pkg = "package"; + + // Post singleton groups, above forced group limit + for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT; i++) { + NotificationRecord summary = getNotificationRecord(pkg, i, + String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true); + notificationList.add(summary); + NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42), + UserHandle.SYSTEM, "testGrp " + i, false); + notificationList.add(child); + summaryByGroup.put(summary.getGroupKey(), summary); + } + + // Classify/bundle child notifications + final NotificationChannel socialChannel = new NotificationChannel( + NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID, + IMPORTANCE_DEFAULT); + final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg, + AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier()); + final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes( + BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, + NotificationChannel.SOCIAL_MEDIA_ID); + for (NotificationRecord record: notificationList) { + if (record.getOriginalGroupKey().contains("testGrp")) { + record.updateNotificationChannel(socialChannel); + mGroupHelper.onChannelUpdated(record); + } + } + + // Check that notifications are forced grouped and app-provided summaries are canceled + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social)); + verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(), + eq(expectedGroupKey_social), eq(true)); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString()); + verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(), + any()); + verify(mCallback, times(2)).removeAppProvidedSummaryOnClassification( + anyString(), anyString()); + + // Adjust group key and cancel summaries + for (NotificationRecord record: notificationList) { + if (record.getNotification().isGroupSummary()) { + record.isCanceled = true; + } else { + record.setOverrideGroupKey(expectedGroupKey_social); + } + } + + // Check that after onNotificationPostedWithDelay there is no change in the grouping + reset(mCallback); + for (NotificationRecord record: notificationList) { + mGroupHelper.onNotificationPostedWithDelay(record, notificationList, summaryByGroup); + } + + verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any()); + verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(), + any()); + } + + @Test + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION, + FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS}) + public void testSingletonGroupsRegrouped_notificationBundledAfterDelayTimeout() { + // Check that singleton group notifications are regrouped if classification is done + // after onNotificationPostedWithDelay + final List<NotificationRecord> notificationList = new ArrayList<>(); + final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>(); + final String pkg = "package"; + final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg, + AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier()); + String expectedTriggeringKey = null; + // Post singleton groups, above forced group limit + for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT; i++) { + NotificationRecord summary = getNotificationRecord(pkg, i, + String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true); + notificationList.add(summary); + NotificationRecord child = getNotificationRecord(pkg, i + 42, + String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false); + notificationList.add(child); + expectedTriggeringKey = child.getKey(); + summaryByGroup.put(summary.getGroupKey(), summary); + mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup); + summary.isCanceled = true; // simulate removing the app summary + mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup); + } + + // Check that notifications are forced grouped and app-provided summaries are canceled + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), + eq(expectedTriggeringKey), eq(expectedGroupKey_alerting), anyInt(), + eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(), + eq(expectedGroupKey_alerting), eq(true)); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(), + any()); + verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).removeAppProvidedSummary( + anyString()); + assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(0), 0, + UserHandle.SYSTEM.getIdentifier())).isNotNull(); + assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(1), 1, + UserHandle.SYSTEM.getIdentifier())).isNotNull(); + + // Classify/bundle child notifications + reset(mCallback); + final NotificationChannel socialChannel = new NotificationChannel( + NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID, + IMPORTANCE_DEFAULT); + final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg, + AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier()); + final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes( + BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, + NotificationChannel.SOCIAL_MEDIA_ID); + for (NotificationRecord record: notificationList) { + if (record.getOriginalGroupKey().contains("testGrp")) { + record.updateNotificationChannel(socialChannel); + mGroupHelper.onChannelUpdated(record); + } + } + + // Check that all notifications are moved to the social section group + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social)); + verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(), + eq(expectedGroupKey_social), eq(true)); + // Check that the alerting section group is removed + verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg), + eq(expectedGroupKey_alerting)); + verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).updateAutogroupSummary(anyInt(), + anyString(), anyString(), any()); + } + + @Test @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING) public void testMoveAggregateGroups_updateChannel_groupsUngrouped() { final String pkg = "package"; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index b5724b5c0cc8..48bc9d7c51a1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -21,10 +21,8 @@ import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; -import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND; -import static com.android.server.notification.Flags.FLAG_NOTIFICATION_NLS_REBIND; import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT; import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE; import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled; @@ -65,14 +63,11 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.IInterface; -import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.EnableFlags; -import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; -import android.testing.TestableLooper; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -87,9 +82,7 @@ import com.android.server.UiServiceTestCase; import com.google.android.collect.Lists; -import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -110,10 +103,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; - public class ManagedServicesTest extends UiServiceTestCase { - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @Mock private IPackageManager mIpm; @@ -125,7 +115,6 @@ public class ManagedServicesTest extends UiServiceTestCase { private ManagedServices.UserProfiles mUserProfiles; @Mock private DevicePolicyManager mDpm; Object mLock = new Object(); - private TestableLooper mTestableLooper; UserInfo mZero = new UserInfo(0, "zero", 0); UserInfo mTen = new UserInfo(10, "ten", 0); @@ -153,7 +142,6 @@ public class ManagedServicesTest extends UiServiceTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mTestableLooper = new TestableLooper(Looper.getMainLooper()); mContext.setMockPackageManager(mPm); mContext.addMockSystemService(Context.USER_SERVICE, mUm); @@ -211,11 +199,6 @@ public class ManagedServicesTest extends UiServiceTestCase { mIpm, APPROVAL_BY_COMPONENT); } - @After - public void tearDown() throws Exception { - mTestableLooper.destroy(); - } - @Test public void testBackupAndRestore_migration() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { @@ -905,7 +888,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); - mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>()); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a", 0, true); service.reregisterService(cn, 0); @@ -936,7 +919,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); - mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>()); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a", 0, false); service.reregisterService(cn, 0); @@ -967,7 +950,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); - mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>()); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a/a", 0, true); service.reregisterService(cn, 0); @@ -998,7 +981,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); - mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>()); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a/a", 0, false); service.reregisterService(cn, 0); @@ -1070,78 +1053,6 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND) - public void registerService_bindingDied_rebindIsClearedOnUserSwitch() throws Exception { - Context context = mock(Context.class); - PackageManager pm = mock(PackageManager.class); - ApplicationInfo ai = new ApplicationInfo(); - ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - - when(context.getPackageName()).thenReturn(mPkg); - when(context.getUserId()).thenReturn(mUser.getIdentifier()); - when(context.getPackageManager()).thenReturn(pm); - when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); - - ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm, - APPROVAL_BY_PACKAGE); - service = spy(service); - ComponentName cn = ComponentName.unflattenFromString("a/a"); - - // Trigger onBindingDied for component when registering - // => will schedule a rebind in 10 seconds - when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> { - Object[] args = invocation.getArguments(); - ServiceConnection sc = (ServiceConnection) args[1]; - sc.onBindingDied(cn); - return true; - }); - service.registerService(cn, 0); - assertThat(service.isBound(cn, 0)).isFalse(); - - // Switch to user 10 - service.onUserSwitched(10); - - // Check that the scheduled rebind for user 0 was cleared - mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS); - mTestableLooper.processAllMessages(); - verify(service, never()).reregisterService(any(), anyInt()); - } - - @Test - public void registerService_bindingDied_rebindIsExecutedAfterTimeout() throws Exception { - Context context = mock(Context.class); - PackageManager pm = mock(PackageManager.class); - ApplicationInfo ai = new ApplicationInfo(); - ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - - when(context.getPackageName()).thenReturn(mPkg); - when(context.getUserId()).thenReturn(mUser.getIdentifier()); - when(context.getPackageManager()).thenReturn(pm); - when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); - - ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm, - APPROVAL_BY_PACKAGE); - service = spy(service); - ComponentName cn = ComponentName.unflattenFromString("a/a"); - - // Trigger onBindingDied for component when registering - // => will schedule a rebind in 10 seconds - when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> { - Object[] args = invocation.getArguments(); - ServiceConnection sc = (ServiceConnection) args[1]; - sc.onBindingDied(cn); - return true; - }); - service.registerService(cn, 0); - assertThat(service.isBound(cn, 0)).isFalse(); - - // Check that the scheduled rebind is run - mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS); - mTestableLooper.processAllMessages(); - verify(service, times(1)).reregisterService(eq(cn), eq(0)); - } - - @Test public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, @@ -1300,65 +1211,6 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND) - public void testUpgradeAppNoIntentFilterNoRebind() throws Exception { - Context context = spy(getContext()); - doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any()); - - ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, - mIpm, APPROVAL_BY_COMPONENT); - - List<String> packages = new ArrayList<>(); - packages.add("package"); - addExpectedServices(service, packages, 0); - - final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1"); - final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2"); - - // Both components are approved initially - mExpectedPrimaryComponentNames.clear(); - mExpectedPrimaryPackages.clear(); - mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); - mExpectedSecondaryComponentNames.clear(); - mExpectedSecondaryPackages.clear(); - - loadXml(service); - - //Component package/C1 loses serviceInterface intent filter - ManagedServices.Config config = service.getConfig(); - when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())) - .thenAnswer(new Answer<List<ResolveInfo>>() { - @Override - public List<ResolveInfo> answer(InvocationOnMock invocationOnMock) - throws Throwable { - Object[] args = invocationOnMock.getArguments(); - Intent invocationIntent = (Intent) args[0]; - if (invocationIntent != null) { - if (invocationIntent.getAction().equals(config.serviceInterface) - && packages.contains(invocationIntent.getPackage())) { - List<ResolveInfo> dummyServices = new ArrayList<>(); - ResolveInfo resolveInfo = new ResolveInfo(); - ServiceInfo serviceInfo = new ServiceInfo(); - serviceInfo.packageName = invocationIntent.getPackage(); - serviceInfo.name = approvedComponent.getClassName(); - serviceInfo.permission = service.getConfig().bindPermission; - resolveInfo.serviceInfo = serviceInfo; - dummyServices.add(resolveInfo); - return dummyServices; - } - } - return new ArrayList<>(); - } - }); - - // Trigger package update - service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); - - assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent)); - assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent)); - } - - @Test public void testSetPackageOrComponentEnabled() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, @@ -1371,21 +1223,6 @@ public class ManagedServicesTest extends UiServiceTestCase { "user10package1/K", "user10.3/Component", "user10package2/L", "user10.4/Component"})); - // mock permissions for services - PackageManager pm = mock(PackageManager.class); - when(getContext().getPackageManager()).thenReturn(pm); - List<ComponentName> enabledComponents = List.of( - ComponentName.unflattenFromString("package/Comp"), - ComponentName.unflattenFromString("package/C2"), - ComponentName.unflattenFromString("again/M4"), - ComponentName.unflattenFromString("user10package/B"), - ComponentName.unflattenFromString("user10/Component"), - ComponentName.unflattenFromString("user10package1/K"), - ComponentName.unflattenFromString("user10.3/Component"), - ComponentName.unflattenFromString("user10package2/L"), - ComponentName.unflattenFromString("user10.4/Component")); - mockServiceInfoWithMetaData(enabledComponents, service, pm, new ArrayMap<>()); - for (int userId : expectedEnabled.keySet()) { ArrayList<String> expectedForUser = expectedEnabled.get(userId); for (int i = 0; i < expectedForUser.size(); i++) { @@ -1447,90 +1284,6 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND) - public void testSetPackageOrComponentEnabled_pkgInstalledAfterEnabling() throws Exception { - ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, - mIpm, APPROVAL_BY_COMPONENT); - - final int userId = 0; - final String validComponent = "again/M4"; - ArrayList<String> expectedEnabled = Lists.newArrayList("package/Comp", "package/C2", - validComponent); - - PackageManager pm = mock(PackageManager.class); - when(getContext().getPackageManager()).thenReturn(pm); - service = spy(service); - - // Component again/M4 is a valid service and the package is available - doReturn(true).when(service) - .isValidService(ComponentName.unflattenFromString(validComponent), userId); - when(pm.isPackageAvailable("again")).thenReturn(true); - - // "package" is not available and its services are not valid - doReturn(false).when(service) - .isValidService(ComponentName.unflattenFromString("package/Comp"), userId); - doReturn(false).when(service) - .isValidService(ComponentName.unflattenFromString("package/C2"), userId); - when(pm.isPackageAvailable("package")).thenReturn(false); - - // Enable all components - for (String component: expectedEnabled) { - service.setPackageOrComponentEnabled(component, userId, true, true); - } - - // Verify everything added is approved - for (String component: expectedEnabled) { - assertTrue("Not allowed: user: " + userId + " entry: " + component - + " for approval level " + APPROVAL_BY_COMPONENT, - service.isPackageOrComponentAllowed(component, userId)); - } - - // Add missing package "package" - service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); - - // Check that component of "package" are not enabled - assertFalse(service.isComponentEnabledForCurrentProfiles( - ComponentName.unflattenFromString("package/Comp"))); - assertFalse(service.isPackageOrComponentAllowed("package/Comp", userId)); - - assertFalse(service.isComponentEnabledForCurrentProfiles( - ComponentName.unflattenFromString("package/C2"))); - assertFalse(service.isPackageOrComponentAllowed("package/C2", userId)); - - // Check that the valid components are still enabled - assertTrue(service.isComponentEnabledForCurrentProfiles( - ComponentName.unflattenFromString(validComponent))); - assertTrue(service.isPackageOrComponentAllowed(validComponent, userId)); - } - - @Test - @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND) - public void testSetPackageOrComponentEnabled_invalidComponent() throws Exception { - ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, - mIpm, APPROVAL_BY_COMPONENT); - - final int userId = 0; - final String invalidComponent = "package/Comp"; - - PackageManager pm = mock(PackageManager.class); - when(getContext().getPackageManager()).thenReturn(pm); - service = spy(service); - - // Component is an invalid service and the package is available - doReturn(false).when(service) - .isValidService(ComponentName.unflattenFromString(invalidComponent), userId); - when(pm.isPackageAvailable("package")).thenReturn(true); - service.setPackageOrComponentEnabled(invalidComponent, userId, true, true); - - // Verify that the component was not enabled - assertFalse("Not allowed: user: " + userId + " entry: " + invalidComponent - + " for approval level " + APPROVAL_BY_COMPONENT, - service.isPackageOrComponentAllowed(invalidComponent, userId)); - assertFalse(service.isComponentEnabledForCurrentProfiles( - ComponentName.unflattenFromString(invalidComponent))); - } - - @Test public void testGetAllowedPackages_byUser() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, @@ -2191,7 +1944,7 @@ public class ManagedServicesTest extends UiServiceTestCase { metaDataAutobindAllow.putBoolean(META_DATA_DEFAULT_AUTOBIND, true); metaDatas.put(cn_allowed, metaDataAutobindAllow); - mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas); + mockServiceInfoWithMetaData(componentNames, service, metaDatas); service.addApprovedList(cn_allowed.flattenToString(), 0, true); service.addApprovedList(cn_disallowed.flattenToString(), 0, true); @@ -2236,7 +1989,7 @@ public class ManagedServicesTest extends UiServiceTestCase { metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false); metaDatas.put(cn_disallowed, metaDataAutobindDisallow); - mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas); + mockServiceInfoWithMetaData(componentNames, service, metaDatas); service.addApprovedList(cn_disallowed.flattenToString(), 0, true); @@ -2275,7 +2028,7 @@ public class ManagedServicesTest extends UiServiceTestCase { metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false); metaDatas.put(cn_disallowed, metaDataAutobindDisallow); - mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas); + mockServiceInfoWithMetaData(componentNames, service, metaDatas); service.addApprovedList(cn_disallowed.flattenToString(), 0, true); @@ -2346,8 +2099,8 @@ public class ManagedServicesTest extends UiServiceTestCase { } private void mockServiceInfoWithMetaData(List<ComponentName> componentNames, - ManagedServices service, PackageManager packageManager, - ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException { + ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas) + throws RemoteException { when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer( (Answer<ServiceInfo>) invocation -> { ComponentName invocationCn = invocation.getArgument(0); @@ -2362,39 +2115,6 @@ public class ManagedServicesTest extends UiServiceTestCase { return null; } ); - - // add components to queryIntentServicesAsUser response - final List<String> packages = new ArrayList<>(); - for (ComponentName cn: componentNames) { - packages.add(cn.getPackageName()); - } - ManagedServices.Config config = service.getConfig(); - when(packageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())). - thenAnswer(new Answer<List<ResolveInfo>>() { - @Override - public List<ResolveInfo> answer(InvocationOnMock invocationOnMock) - throws Throwable { - Object[] args = invocationOnMock.getArguments(); - Intent invocationIntent = (Intent) args[0]; - if (invocationIntent != null) { - if (invocationIntent.getAction().equals(config.serviceInterface) - && packages.contains(invocationIntent.getPackage())) { - List<ResolveInfo> dummyServices = new ArrayList<>(); - for (ComponentName cn: componentNames) { - ResolveInfo resolveInfo = new ResolveInfo(); - ServiceInfo serviceInfo = new ServiceInfo(); - serviceInfo.packageName = invocationIntent.getPackage(); - serviceInfo.name = cn.getClassName(); - serviceInfo.permission = service.getConfig().bindPermission; - resolveInfo.serviceInfo = serviceInfo; - dummyServices.add(resolveInfo); - } - return dummyServices; - } - } - return new ArrayList<>(); - } - }); } private void resetComponentsAndPackages() { 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 2c645e0ca353..6eb2f718a0e9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -17,6 +17,9 @@ package com.android.server.notification; import static android.os.UserHandle.USER_ALL; import static android.service.notification.Adjustment.KEY_IMPORTANCE; +import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION; +import static android.service.notification.Adjustment.TYPE_NEWS; +import static android.service.notification.Adjustment.TYPE_PROMOTION; import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENTS; @@ -28,7 +31,6 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNull; - import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; @@ -58,6 +60,8 @@ import android.testing.TestableContext; import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; +import android.util.Log; +import android.util.Slog; import android.util.Xml; import androidx.test.runner.AndroidJUnit4; @@ -198,8 +202,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testWriteXml_userTurnedOffNAS() throws Exception { int userId = ActivityManager.getCurrentUser(); - doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId)); - mAssistants.loadDefaultsFromConfig(true); mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true, @@ -435,10 +437,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception { ComponentName component1 = ComponentName.unflattenFromString("package/Component1"); ComponentName component2 = ComponentName.unflattenFromString("package/Component2"); - - doReturn(true).when(mAssistants).isValidService(eq(component1), eq(mZero.id)); - doReturn(true).when(mAssistants).isValidService(eq(component2), eq(mZero.id)); - mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true, true, true); verify(mNm, never()).setNotificationAssistantAccessGrantedForUserInternal( @@ -584,7 +582,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testSetAdjustmentTypeSupportedState() throws Exception { int userId = ActivityManager.getCurrentUser(); - doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId)); mAssistants.loadDefaultsFromConfig(true); mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true, true, true); @@ -608,7 +605,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception { int userId = ActivityManager.getCurrentUser(); - doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId)); mAssistants.loadDefaultsFromConfig(true); mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true, true, true); @@ -632,7 +628,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase { public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception { int userId = ActivityManager.getCurrentUser(); - doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId)); mAssistants.loadDefaultsFromConfig(true); mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true, true, true); @@ -690,4 +685,47 @@ public class NotificationAssistantsTest extends UiServiceTestCase { assertThat(mAssistants.getAllowedAssistantAdjustments()) .containsExactlyElementsIn(DEFAULT_ALLOWED_ADJUSTMENTS); } + + @Test + @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public void testSetAssistantAdjustmentKeyTypeState_allow() { + assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList() + .containsExactly(TYPE_PROMOTION); + + mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true); + + assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList() + .containsExactlyElementsIn(List.of(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION)); + } + + @Test + @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public void testSetAssistantAdjustmentKeyTypeState_disallow() { + mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false); + assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).isEmpty(); + } + + @Test + @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public void testDisallowAdjustmentKeyType_readWriteXml() throws Exception { + mAssistants.loadDefaultsFromConfig(true); + mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false); + mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true); + mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true); + + writeXmlAndReload(USER_ALL); + + assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList() + .containsExactlyElementsIn(List.of(TYPE_NEWS, TYPE_CONTENT_RECOMMENDATION)); + } + + @Test + public void testDefaultAllowedKeyAdjustments_readWriteXml() throws Exception { + mAssistants.loadDefaultsFromConfig(true); + + writeXmlAndReload(USER_ALL); + + assertThat(mAssistants.getAllowedAdjustmentKeyTypes()).asList() + .containsExactly(TYPE_PROMOTION); + } }
\ No newline at end of file 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 cbfdc5f61e3f..48308a409036 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -43,8 +43,8 @@ import static android.app.Notification.FLAG_PROMOTED_ONGOING; import static android.app.Notification.FLAG_USER_INITIATED_JOB; import static android.app.Notification.GROUP_ALERT_CHILDREN; import static android.app.Notification.VISIBILITY_PRIVATE; -import static android.app.NotificationChannel.NEWS_ID; import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationChannel.NEWS_ID; import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; @@ -78,7 +78,6 @@ import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED; import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED; import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG; -import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.PackageManager.FEATURE_TELECOM; import static android.content.pm.PackageManager.FEATURE_WATCH; @@ -114,6 +113,7 @@ import static android.service.notification.Condition.STATE_TRUE; import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION; import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT; import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING; +import static android.service.notification.Flags.FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION; import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; @@ -336,12 +336,12 @@ import com.android.server.utils.quota.MultiRateLimiter; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; -import com.google.android.collect.Lists; -import com.google.common.collect.ImmutableList; - import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; +import com.google.android.collect.Lists; +import com.google.common.collect.ImmutableList; + import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -366,7 +366,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; -import java.io.OutputStream; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -2685,6 +2684,41 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testAggregateGroups_RemoveAppSummary_onClassification() throws Exception { + 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); + + // Regroup first child notification + r1.setOverrideGroupKey("newGroup"); + // Check that removeAppProvidedSummaryOnClassificationLocked is null + // => there is still one child left in the original group + assertThat(mService.removeAppProvidedSummaryOnClassificationLocked(r1.getKey(), + originalGroupKey)).isNull(); + + // Regroup last child notification + r2.setOverrideGroupKey("newGroup"); + // Check that removeAppProvidedSummaryOnClassificationLocked returns the original summary + // and that the original app-provided summary is canceled + assertThat(mService.removeAppProvidedSummaryOnClassificationLocked(r2.getKey(), + originalGroupKey)).isEqualTo(summary); + waitForIdle(); + verify(mWorkerHandler, times(1)).scheduleCancelNotification(any(), eq(summaryId)); + assertThat(mService.mSummaryByGroupKey).doesNotContainKey(originalGroupKey); + } + + @Test @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING) public void testUngroupingAggregateSummary() throws Exception { final String originalGroupName = "originalGroup"; @@ -7480,6 +7514,63 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testClassificationChannelAdjustmentsLogged() 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); + + // Set up notifications that will be adjusted + final NotificationRecord r1 = spy(generateNotificationRecord( + mTestNotificationChannel, 1, null, true)); + when(r1.getLifespanMs(anyLong())).thenReturn(234); + + r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); + // Enqueues the notification to be posted, so hasPosted will be false. + mService.addEnqueuedNotification(r1); + + // Test an adjustment for an enqueued notification + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS); + Adjustment adjustment1 = new Adjustment( + r1.getSbn().getPackageName(), r1.getKey(), signals, "", + r1.getUser().getIdentifier()); + mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment1); + assertTrue(mService.checkLastClassificationChannelLog(false /*hasPosted*/, + true /*isAlerting*/, 3 /*TYPE_NEWS*/, 234)); + + // Set up notifications that will be adjusted + // This notification starts on a low importance channel, so isAlerting is false. + NotificationChannel mLowImportanceNotificationChannel = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_LOW); + final NotificationRecord r2 = spy(generateNotificationRecord( + mLowImportanceNotificationChannel, 1, null, true)); + when(r2.getLifespanMs(anyLong())).thenReturn(345); + + r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); + // Adds the notification as already posted, so hasPosted will be true. + mService.addNotification(r2); + // The signal is removed when used so it has to be readded. + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS); + Adjustment adjustment2 = new Adjustment( + r2.getSbn().getPackageName(), r2.getKey(), signals, "", + r2.getUser().getIdentifier()); + mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment2); + assertTrue(mService.checkLastClassificationChannelLog(true /*hasPosted*/, + false /*isAlerting*/, 3 /*TYPE_NEWS*/, 345)); // currently failing + + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_PROMOTION); + Adjustment adjustment3 = new Adjustment( + r2.getSbn().getPackageName(), r2.getKey(), signals, "", + r2.getUser().getIdentifier()); + mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment3); + assertTrue(mService.checkLastClassificationChannelLog(true /*hasPosted*/, + false /*isAlerting*/, 1 /*TYPE_PROMOTION*/, 345)); + } + + @Test public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception { NotificationManagerService.WorkerHandler handler = mock( NotificationManagerService.WorkerHandler.class); @@ -14365,9 +14456,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } - private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage, - boolean isImageBitmap, boolean isExpired) { - Notification.Builder builder = new Notification.Builder(mContext); + private Notification createBigPictureNotification(boolean isBigPictureStyle, boolean hasImage, + boolean isImageBitmap) { + Notification.Builder builder = new Notification.Builder(mContext) + .setSmallIcon(android.R.drawable.sym_def_app_icon); Notification.BigPictureStyle style = new Notification.BigPictureStyle(); if (isBigPictureStyle && hasImage) { @@ -14383,12 +14475,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification notification = builder.setChannelId(TEST_CHANNEL_ID).build(); + return notification; + } + + private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage, + boolean isImageBitmap, boolean isExpired) { long timePostedMs = System.currentTimeMillis(); if (isExpired) { timePostedMs -= BITMAP_DURATION.toMillis(); } StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0, - notification, UserHandle.getUserHandleForUid(mUid), null, timePostedMs); + createBigPictureNotification(isBigPictureStyle, hasImage, isImageBitmap), + UserHandle.getUserHandleForUid(mUid), null, timePostedMs); return new NotificationRecord(mContext, sbn, mTestNotificationChannel); } @@ -14400,6 +14498,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testRemoveBitmaps_canRemoveRevokedDelegate() throws Exception { + Notification n = createBigPictureNotification(true, true, true); + long timePostedMs = System.currentTimeMillis(); + timePostedMs -= BITMAP_DURATION.toMillis(); + + when(mPermissionHelper.hasPermission(UID_O)).thenReturn(true); + when(mPackageManagerInternal.isSameApp(PKG_O, UID_O, UserHandle.getUserId(UID_O))) + .thenReturn(true); + mService.mPreferencesHelper.createNotificationChannel(PKG_O, UID_O, + mTestNotificationChannel, true /* fromTargetApp */, false, UID_O, + false); + mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice( + Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel))); + + StatusBarNotification sbn = new StatusBarNotification(PKG_O, "old.delegate", 8, "tag", + UID_O, 0, n, UserHandle.getUserHandleForUid(UID_O), null, timePostedMs); + + mService.addNotification(new NotificationRecord(mContext, sbn, mTestNotificationChannel)); + mInternalService.removeBitmaps(); + + waitForIdle(); + + verify(mWorkerHandler, times(1)) + .post(any(NotificationManagerService.EnqueueNotificationRunnable.class)); + } + + @Test public void testRemoveBitmaps_notBigPicture_noRepost() { addRecordAndRemoveBitmaps( createBigPictureRecord( @@ -17067,6 +17192,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManagerService.WorkerHandler handler = mock( NotificationManagerService.WorkerHandler.class); mService.setHandler(handler); + when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); Bundle signals = new Bundle(); signals.putInt(KEY_TYPE, TYPE_NEWS); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java index 07d25dfd814e..ba91ca2323af 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java @@ -52,6 +52,14 @@ public class TestableNotificationManagerService extends NotificationManagerServi } public SensitiveLog lastSensitiveLog = null; + private static class ClassificationChannelLog { + public boolean hasPosted; + public boolean isAlerting; + public long classification; + public long lifetime; + } + public ClassificationChannelLog lastClassificationChannelLog = null; + TestableNotificationManagerService(Context context, NotificationRecordLogger logger, InstanceIdSequence notificationInstanceIdSequence) { super(context, logger, notificationInstanceIdSequence); @@ -211,4 +219,29 @@ public class TestableNotificationManagerService extends NotificationManagerServi public interface ComponentPermissionChecker { int check(String permission, int uid, int owningUid, boolean exported); } + + @Override + protected void logClassificationChannelAdjustmentReceived(boolean hasPosted, boolean isAlerting, + int classification, int lifetimeMs) { + lastClassificationChannelLog = new ClassificationChannelLog(); + lastClassificationChannelLog.hasPosted = hasPosted; + lastClassificationChannelLog.isAlerting = isAlerting; + lastClassificationChannelLog.classification = classification; + lastClassificationChannelLog.lifetime = lifetimeMs; + } + + /** + * Returns true if the last recorded classification channel log has all the values specified. + */ + public boolean checkLastClassificationChannelLog(boolean hasPosted, boolean isAlerting, + int classification, int lifetime) { + if (lastClassificationChannelLog == null) { + return false; + } + + return hasPosted == lastClassificationChannelLog.hasPosted + && isAlerting == lastClassificationChannelLog.isAlerting + && classification == lastClassificationChannelLog.classification + && lifetime == lastClassificationChannelLog.lifetime; + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 0019b3e45e7b..4b94e103b9f4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -493,6 +493,22 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + public void testZenOn_RepeatCallers_CallTypesBlocked() { + mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); + // Any call allowed but no repeat callers + mZenModeHelper.mConsolidatedPolicy = new Policy(PRIORITY_CATEGORY_CALLS, + PRIORITY_SENDERS_ANY, 0, 0, 0); + mZenModeHelper.applyRestrictions(); + + verifyApplyRestrictions(true, true, + AudioAttributes.USAGE_NOTIFICATION_RINGTONE); + verifyApplyRestrictions(true, true, + AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST); + } + + + @Test public void testZenOn_StarredCallers_CallTypesBlocked() { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); @@ -501,7 +517,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_CONVERSATIONS | PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_EVENTS | PRIORITY_CATEGORY_REMINDERS - | PRIORITY_CATEGORY_SYSTEM, + | PRIORITY_CATEGORY_SYSTEM | PRIORITY_CATEGORY_REPEAT_CALLERS, PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_ANY, 0, CONVERSATION_SENDERS_ANYONE); mZenModeHelper.applyRestrictions(); diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index 25a8db6e6b9c..1e9038ee1769 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -397,7 +397,7 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - @EnableFlags(Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL) + @EnableFlags(Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testToggleTalkbackPress() { testShortcutInternal("Meta + Alt + T -> Toggle talkback", @@ -745,7 +745,7 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - @EnableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL) + @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) public void testKeyGestureToggleTalkback() { Assert.assertTrue( sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK)); diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java index 30cc002b4144..bfce3d276a2d 100644 --- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java +++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java @@ -168,32 +168,28 @@ public class BatteryUsageStatsPerfTest { builder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) - .setConsumedPower(123) - .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 10100) - .setConsumedPower( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) - .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 10300) - .setUsageDurationMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400); + .addConsumedPower(123) + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 10100) + .addConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) + .addUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CPU, 10300) + .addUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400); for (int i = 0; i < 1000; i++) { final UidBatteryConsumer.Builder consumerBuilder = builder.getOrCreateUidBatteryConsumerBuilder(i) .setPackageWithHighestDrain("example.packagename" + i) - .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, i * 2000) - .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000); + .setTimeInProcessStateMs(UidBatteryConsumer.STATE_FOREGROUND, i * 2000) + .setTimeInProcessStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000); for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; componentId++) { - consumerBuilder.setConsumedPower(componentId, componentId * 123.0, + consumerBuilder.addConsumedPower(componentId, componentId * 123.0, BatteryConsumer.POWER_MODEL_POWER_PROFILE); - consumerBuilder.setUsageDurationMillis(componentId, componentId * 1000); + consumerBuilder.addUsageDurationMillis(componentId, componentId * 1000); } consumerBuilder - .setConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234) - .setUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321); + .addConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234) + .addUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321); } return builder.build(); } diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 6c9f764bbdee..1574d1b7ce6f 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -1260,24 +1260,102 @@ class KeyGestureControllerTests { testKeyGestureInternal(test) } + class TouchpadTestData( + val name: String, + val touchpadGestureType: Int, + val expectedKeyGestureType: Int, + val expectedAction: Int, + val expectedAppLaunchData: AppLaunchData? = null, + ) { + override fun toString(): String = name + } + + @Keep + private fun customTouchpadGesturesTestArguments(): Array<TouchpadTestData> { + return arrayOf( + TouchpadTestData( + "3 Finger Tap -> Go Home", + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ), + TouchpadTestData( + "3 Finger Tap -> Launch app", + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, + AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest") + ), + ) + } + + @Test + @Parameters(method = "customTouchpadGesturesTestArguments") + fun testCustomTouchpadGesture(test: TouchpadTestData) { + setupKeyGestureController() + val builder = InputGestureData.Builder() + .setKeyGestureType(test.expectedKeyGestureType) + .setTrigger(InputGestureData.createTouchpadTrigger(test.touchpadGestureType)) + if (test.expectedAppLaunchData != null) { + builder.setAppLaunchData(test.expectedAppLaunchData) + } + val inputGestureData = builder.build() + + keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData) + + val handledEvents = mutableListOf<KeyGestureEvent>() + val handler = KeyGestureHandler { event, _ -> + handledEvents.add(KeyGestureEvent(event)) + true + } + keyGestureController.registerKeyGestureHandler(handler, 0) + handledEvents.clear() + + keyGestureController.handleTouchpadGesture(test.touchpadGestureType) + + assertEquals( + "Test: $test doesn't produce correct number of key gesture events", + 1, + handledEvents.size + ) + val event = handledEvents[0] + assertEquals( + "Test: $test doesn't produce correct key gesture type", + test.expectedKeyGestureType, + event.keyGestureType + ) + assertEquals( + "Test: $test doesn't produce correct key gesture action", + test.expectedAction, + event.action + ) + assertEquals( + "Test: $test doesn't produce correct app launch data", + test.expectedAppLaunchData, + event.appLaunchData + ) + + keyGestureController.unregisterKeyGestureHandler(handler, 0) + } + private fun testKeyGestureInternal(test: TestData) { - var handleEvents = mutableListOf<KeyGestureEvent>() + val handledEvents = mutableListOf<KeyGestureEvent>() val handler = KeyGestureHandler { event, _ -> - handleEvents.add(KeyGestureEvent(event)) + handledEvents.add(KeyGestureEvent(event)) true } keyGestureController.registerKeyGestureHandler(handler, 0) - handleEvents.clear() + handledEvents.clear() sendKeys(test.keys) assertEquals( "Test: $test doesn't produce correct number of key gesture events", test.expectedActions.size, - handleEvents.size + handledEvents.size ) - for (i in handleEvents.indices) { - val event = handleEvents[i] + for (i in handledEvents.indices) { + val event = handledEvents[i] assertArrayEquals( "Test: $test doesn't produce correct key gesture keycodes", test.expectedKeys, @@ -1309,16 +1387,16 @@ class KeyGestureControllerTests { } private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) { - var handleEvents = mutableListOf<KeyGestureEvent>() + var handledEvents = mutableListOf<KeyGestureEvent>() val handler = KeyGestureHandler { event, _ -> - handleEvents.add(KeyGestureEvent(event)) + handledEvents.add(KeyGestureEvent(event)) true } keyGestureController.registerKeyGestureHandler(handler, 0) - handleEvents.clear() + handledEvents.clear() sendKeys(testKeys) - assertEquals("Test: $testName should not produce Key gesture", 0, handleEvents.size) + assertEquals("Test: $testName should not produce Key gesture", 0, handledEvents.size) } private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) { diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index be5c84c0353c..ac96ef28f501 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -53,6 +53,7 @@ public class TestableLooper { private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; private static final Field MESSAGE_NEXT_FIELD; private static final Field MESSAGE_WHEN_FIELD; + private static Field MESSAGE_QUEUE_USE_CONCURRENT_FIELD = null; private Looper mLooper; private MessageQueue mQueue; @@ -63,6 +64,14 @@ public class TestableLooper { static { try { + MESSAGE_QUEUE_USE_CONCURRENT_FIELD = + MessageQueue.class.getDeclaredField("mUseConcurrent"); + MESSAGE_QUEUE_USE_CONCURRENT_FIELD.setAccessible(true); + } catch (NoSuchFieldException ignored) { + // Ignore - maybe this is not CombinedMessageQueue? + } + + try { MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); @@ -146,6 +155,15 @@ public class TestableLooper { mLooper = l; mQueue = mLooper.getQueue(); mHandler = new Handler(mLooper); + + // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing. + if (MESSAGE_QUEUE_USE_CONCURRENT_FIELD != null) { + try { + MESSAGE_QUEUE_USE_CONCURRENT_FIELD.set(mQueue, false); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } } /** diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java index e6eabd804294..1bcfaf60857d 100644 --- a/tests/utils/testutils/java/android/os/test/TestLooper.java +++ b/tests/utils/testutils/java/android/os/test/TestLooper.java @@ -90,20 +90,6 @@ public class TestLooper { * and call {@link #dispatchAll()}. */ public TestLooper(Clock clock) { - Field messageQueueUseConcurrentField = null; - boolean previousUseConcurrentValue = false; - try { - messageQueueUseConcurrentField = MessageQueue.class.getDeclaredField("sUseConcurrent"); - messageQueueUseConcurrentField.setAccessible(true); - previousUseConcurrentValue = messageQueueUseConcurrentField.getBoolean(null); - // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing. - messageQueueUseConcurrentField.set(null, false); - } catch (NoSuchFieldException e) { - // Ignore - maybe this is not CombinedMessageQueue? - } catch (IllegalAccessException e) { - throw new RuntimeException("Reflection error constructing or accessing looper", e); - } - try { mLooper = LOOPER_CONSTRUCTOR.newInstance(false); @@ -114,15 +100,19 @@ public class TestLooper { throw new RuntimeException("Reflection error constructing or accessing looper", e); } - mClock = clock; - - if (messageQueueUseConcurrentField != null) { - try { - messageQueueUseConcurrentField.set(null, previousUseConcurrentValue); - } catch (IllegalAccessException e) { - throw new RuntimeException("Reflection error constructing or accessing looper", e); - } + // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing. + try { + Field messageQueueUseConcurrentField = + MessageQueue.class.getDeclaredField("mUseConcurrent"); + messageQueueUseConcurrentField.setAccessible(true); + messageQueueUseConcurrentField.set(mLooper.getQueue(), false); + } catch (NoSuchFieldException e) { + // Ignore - maybe this is not CombinedMessageQueue? + } catch (IllegalAccessException e) { + throw new RuntimeException("Reflection error constructing or accessing looper", e); } + + mClock = clock; } public Looper getLooper() { |